Why Agent 0 Exists

The original Signal Collector generated fictional signals — asking an LLM to invent plausible client emails and events rather than reading real ones. This was identified as the single most critical flaw in the system: every downstream score, diagnostic, and drafted email was analysis of data that didn't exist. Worse, the generator was instructed to continue declining trends, creating a doom loop where a drifting client could never recover because each run manufactured more evidence of decline.

Agent 0 replaces fiction with real structured input. Architecturally, it is best understood as a scheduled ingestion pipeline with one LLM step (summarization), not an autonomous reasoning agent — polling for real data and resolving identity is deterministic work; only condensing email prose needs a model's judgment.


V1 Scope

Email (via BCC/forward) + Calendar + Manual notes. Slack, Teams, and WhatsApp were deliberately excluded from V1 — each is a separate OAuth regime and failure surface, and together they cover a minority of consultancy-client communication compared to email.


The Ingestion Path

Account managers BCC a dedicated address on outbound client emails. An inbound-parse webhook (Postmark/Mailgun-style) converts the raw email into clean JSON and posts it to POST /ingest/email. Calendar events and manual observations post to their own lightweight endpoints. No OAuth, no IMAP polling, no MIME parsing code — the entire ingestion surface is one HTTP handler per source plus the resolution function.


Identity Resolution

The hardest real problem in the whole build: a consultancy's inbox contains every client mixed with vendors and internal mail. Nothing tells the system which client an email belongs to by default.

Resolution order:

  1. Exact email address match (always wins — solves the personal-Gmail-contact case)
  2. Domain match (solves the common case — one domain per client)
  3. Multiple clients matched → tiebreak on sender, else queue as ambiguous
  4. No match → written to sentinel_unmatched_events, never dropped

The mapping table (sentinel_client_identity_map) enforces a UNIQUE(identifier_type, identifier_value) constraint — one identifier can never point to two clients. Ambiguity is made structurally impossible at write time rather than handled at read time.

Honest limitation: BCC only captures outbound mail. It does not solve identity resolution on its own — it defers it entirely to the mapping table, and it only captures the firm's side of the conversation reliably. True silence detection ("the client stopped replying") stays weak in V1 and is the clearest justification for OAuth-based full-mailbox ingestion in V2.