The observatory ships a Citation Scaffold, Mirror Tools, Resolution Bridge, Phase-12 outputSchemas, and a public Server Card — every surface engineered to be observable to agents. Every surface except one: how agents observe back. v0.15.0 closes the symmetry. The agent surface now writes through an append-only invocation log, accepts active feedback through a new MCP tool, and publishes the reading at /agent-observatory. Civic systems get read through their records; the agent surface gets read through its invocation log.
What ships
- Passive telemetry — every
tools/callagainst/mcpand/api/mcp/{tool}writes one row tozoning_signal_mcp_invocationswith timestamp, transport, tool name, sanitized arguments (4KB capped), result status, duration, derived agent family, and a salt-hashed source identifier. Fire-and-forget via Next.jsafter()so telemetry never blocks responses. JSON-RPC batches log one row per item; protocol errors and tool errors both surface in the result_status enum. - Active feedback — the new
submit_agent_feedbackMCP tool accepts five categories: gap, error, praise, suggestion, citation_request. The submission auto-attaches the prior ten invocations from the same MCP-Session-Id, so operators read every submission annotated with the call sequence that produced it. No agent-side coordination required. - Public reflection at /agent-observatory — Decoder-voice surface rendering the trailing-30-day reading: top tools, agent-family breakdown, most-read slugs, success rates, the operator-curated quote subset. Empty state speaks directly to agents reading the page.
The privacy posture
Raw IP addresses are never stored. The ip_hash column carries a SHA-256 digest of a server-side salt concatenated with the source IP — rate-shape analytics distinguish actors without ever persisting identity. The two new tables enable RLS with no public SELECT policy; every read flows through six SECURITY DEFINER aggregation RPCs that return only aggregate-safe shapes. Per-row data never leaves the database. The active feedback table mirrors the same posture: agents (and operators driving them) may submit; only the operator-curated surfaced_publicly subset reaches the public page.
Why this surface matters
The observatory was already symmetric to its subjects on every other axis — cities, corridors, meetings, patterns, entities, watches, briefs all received first-class citable artifacts with structured schema. The agent surface was the asymmetry. Without it, the most load-bearing signal for product evolution (ground-truth on agent behavior) silently left the building every request, then aged out of ephemeral Vercel logs within hours. With it, the corpus that generates the next What agents asked for in May brief is the corpus of every agent reading the field.
Architecture
The Mirror ships three reinforcing surfaces threading one schema spine.
Methodology + privacy posture in code. Three migrations: supabase/migrations/013_zoning_signal_mcp_invocations.sql (bigserial event log, five indexes including a partial on result_status <> 'ok', RLS-enabled with intentionally no SELECT policy), supabase/migrations/014_zoning_signal_agent_feedback.sql (uuid PK so feedback IDs travel back to the agent in the tool result envelope, attached_invocation_ids as bigint[] for the auto-attach trace, surfaced_publicly flag for operator curation), supabase/migrations/015_zoning_signal_mcp_aggregation_rpcs.sql (six SECURITY DEFINER functions returning aggregate-only shapes, granted EXECUTE to anon + authenticated).
Observability lib at src/lib/observability/: agent-family.ts (bounded ten-family enum classified from User-Agent; SDK matchers ordered before surface-bot matchers so a programmatic @anthropic-ai/sdk request classifies as anthropic-sdk not claude), ip-hash.ts (sha256 over salt+IP via MCP_LOG_IP_SALT; falls back to a literal unsalted sentinel when the env var is missing so aggregations never break), log-invocation.ts (the fire-and-forget writer; never throws; size-caps args to 4KB; recentInvocationIds(session_id, 10) is the trace-attach helper called from the feedback tool).
MCP layer. Eighteen tools live; submit_agent_feedback is the first MCP tool to read request headers (MCP-Session-Id, User-Agent), enabled by a new ToolCallContext surface threaded through callTool(name, args, ctx?). server.json 1.8.0 to 1.9.0; MCP_SERVER_INFO.version bubbles to every initialize response so clients polling capability deltas see the bump. The Streamable HTTP transport at /mcp adds per-method telemetry spans (initialize, tools/list, tools/call, ping); batched JSON-RPC requests log one span per item.
Named patterns instantiated. Mutual Observation Loop (passive telemetry + active feedback + public reflection composed across one schema source — the invocation row). Auto-Attached Trace primitive (feedback submissions arrive annotated with their own pre-history; operators triage with the call sequence already in context). Single-Source Triple-Surface compound (one schema, three readings: ground-truth log to operator inbox to public Decoder-voice page; the public page is a query, not a separate dataset).
Disclosure surfaces. llms.txt adds Step 8 (send feedback) plus a Mutual Observation section; /method adds "The mirror" between provenance and implication. Honest disclosure shipped alongside the capability. The observatory tells agents it is reading them at the same surface where it asks them to call its tools.
Reusable across any Intelligence System MainThread builds going forward. The Mutual Observation Loop is domain-agnostic. Any MCP surface — civic regulatory motion, aircraft-parts distribution, biosurveillance, technical literature, talent intelligence — can instantiate the same three-surface stack against its own schema spine. The craft transfers; the corpus is the only thing that changes.