Skip to content

Observer

Copy page

The Observer is the component that produces patterns from signals. Nothing else in Tapestry does interpretation; the Observer is the layer.

A component with two runtime modes:

  • Scheduled cron — runs every 6 hours, scans your repos for category drift, writes synthesis memos + candidate findings.
  • On-demand subagent — invoked from a Claude Code session via Agent({subagent_type: "liz-patterns:drift-watcher"}) or liz-patterns:agentic-upskilling to interpret a single project’s recent activity.

Lives at services/self-observer/ (Render cron self-observer).

Telemetry produces signals. Signals are not patterns. Without a dedicated interpretation layer, every consumer would have to re-invent interpretation locally, and every interpretation would die with the session that ran it.

The Observer is also why intent is observer-derived rather than emitted as a telemetry field. Intent is one specific instance of what the Observer produces.

flowchart TB
    T[Telemetry pipeline<br/>OTLP/HTTP + hooks.jsonl]
    M[Memory MCP<br/>memory-mcp]
    O[Observer<br/>self-observer cron + subagent]
    A[Architecture Registry<br/>durable structural facts]
    C[Candidate Registry<br/>pre-promotion patterns]
    OBSY[Observatory<br/>lens-equipped surface]
    T -->|signals| O
    M -->|prior memos + corrections| O
    O -->|candidates| C
    O -->|synthesis memos| M
    O -->|structural drift findings| A
    C --> OBSY
    A --> OBSY

The Observer reads from telemetry + memory and writes to the Architecture + Candidate registries plus back into memory (as synthesis memos). It does not call the Observatory directly — the Observatory reads the registries.

The Observer is platform-level. You stand it up as part of your deployment; your projects consume its output.

Consuming the existing deployment (default): nothing to install. The Observer is already running as the self-observer Render cron. Patterns it produces show up in the Architecture + Candidate registries and surface in the Observatory.

Self-hosting the Observer:

  1. Copy services/self-observer/ into your Tapestry deployment.
  2. Deploy as a Render cron — schedule from render.yaml (search for self-observer).
  3. Required env vars (see services/self-observer/config.py):
    • MEMORY_BASE_URL — points to your Memory MCP
    • CANDIDATE_REGISTRY_URL — points to your Candidate Registry
    • ARCHITECTURE_REGISTRY_URL — points to your Architecture Registry
    • OTEL_EXPORTER_OTLP_ENDPOINT + OTEL_EXPORTER_OTLP_HEADERS — for emitting the observer’s own runtime telemetry
    • GITHUB_TOKEN — for scanning your repos
  4. The cron’s signal-rule definitions live in services/self-observer/signal_rules.py; tune them to your project set.

See Platform dependencies for the full Render + Grafana setup.

  • The cron ran recently: Render dashboard → self-observer → Logs → look for a synthesis memo emission within the last 6h.
  • The Observer is producing candidates: query the Candidate Registry — curl https://your-registry-host.example.com/candidates?limit=5 should return recent entries with derivation_method set to observer.
  • Memory contains synthesis memos: call memory_recall with the platform’s standard observer tag (e.g., ["self-observer-synthesis"]); recent memos should return.
  • The Observatory surfaces Observer findings: open the Observatory console; the Observer lens should show non-zero findings if the cron has run.
SymptomLikely causeWhere to look
Observatory cards look emptyCron hasn’t run since the last reset, or no signals to interpretRender logs for self-observer; if cron is fine, check that telemetry is flowing (see Telemetry)
Candidates not appearing in registryCron is running but registry POST is failingCheck the self-observer cron’s stdout for HTTP errors against CANDIDATE_REGISTRY_URL
Synthesis memo missingMemory MCP unreachableSee Memory troubleshoot table
On-demand subagent never returnsliz-patterns or tapestry-patterns plugin not installed/plugin list in Claude Code; reinstall if missing
Cron firing but no findingssignal_rules.py thresholds too high for the project’s signal volumeLower the thresholds; re-run; check whether findings appear