insigz docs
Engineering

Agent Architecture

How the AI agents are built server-side - the roster, the grounding contract, tool use, and the human-commit gates.

How the AI agents are built server-side: the roster, the grounding contract, tool use, and the human-commit gates. Product docs 05 (chat), 13 (autonomy), 16 (chat-first) describe behavior; this is the implementation.

The agents (all server-side, never in the browser)

AgentJobWrites?
Analystanswer grounded questions over the canonical model; draft cases/reportsdrafts only; human commits
Ontologysource onboarding: profile feed → propose field mapping → dry-runstages mapping (Q9)
Adjudicatorwargame: draft consequences of a cell's actiondrafts; faculty approves
After-Actionreplay a session/case → draft the signed reportdrafts; human signs (Q8)
Watchstanding monitor: detect → draft a Proposalinternal-reversible only (Q10)

All share one grounding contract and one tool layer; only their system prompt, tools allowed, and output schema differ.

The grounding contract (enforced, not requested)

A claim about the world must cite ≥1 observation by id ([OBS-####]) or it does not ship. Enforcement is two-sided:

  1. Prompt: the system prompt requires citations and forbids ungrounded assertions.
  2. Validator: a server-side checker parses every claim's citations, resolves each id against Postgres for the tenant, and rejects + regenerates any draft with a dangling or cross-tenant citation. The model cannot talk past the validator because the validator reads the citation graph, not the prose. (See ai/31 for the validator.)

Tool layer (Claude tool-use)

Agents don't get raw DB access; they get a typed tool surface that already enforces tenant scope, RLS, and classification:

tools = [
  query_observations({ entity?, property?, since?, until?, bbox? }) -> Observation[]   // RLS-scoped
  get_entity({ id }) -> Entity
  search_corpus({ query, entity?, k }) -> Chunk[]        // RAG retrieval (ai/31)
  list_events({ entity?, case?, since? }) -> Event[]
  base_rate({ metric, entity_type, context }) -> Distribution   // for "unusual vs what"
  // write tools are PROPOSE-only:
  propose_case_update({ case, patch }) -> ProposalId
  propose_report_section({ report, section }) -> ProposalId
]

Every tool call runs as the requesting user's tenant + role context, so an agent can only ever see/act on what that user could. Write tools propose; they never commit. This is the Q8/Q10 spine in code.

Confidence & base rates

The analyst is told to express "unusual" as a number against a base rate, not an adjective — the base_rate tool returns the historical distribution so output reads "loiter 4h12m vs 90th-pctile 38min [OBS-…]". Where the sample is too small for a defensible interval, the agent emits the point estimate + sample size + an explicit "interval withheld" note rather than fake precision.

Run records

Every agent invocation writes an agent_runs doc (Mongo): the prompt, retrieved context (chunk + obs ids), tool calls, raw model output, parsed citations, validator result, and final output. This is the audit + debugging surface and feeds Trust Evals (product §13).

Orchestration

  • Chat/analyst calls run inline (request/response, streamed).
  • Watches and After-Action run async (Cloud Tasks / Pub-Sub jobs) and stage Proposals.
  • A single packages/agents module defines tools + validators so every entry point shares identical enforcement.

Decided

  • Model routing: a Sonnet-class model for the analyst chat + high-volume agents; an Opus-class model for the Adjudicator + After-Action (quality-critical, lower-volume). Resolve current GA model strings at build time (don’t hardcode).
  • Output schemas + citation grammar are pinned in 80-contracts-citation-and-schemas.md — the validator is built against them.

Gaps to close

  • Finalize each proposal patch shape per type (the rest of the schemas live in 80).
  • Decide model routing (which Claude model per agent; cost vs. capability) and token budgets.
  • Specify the base-rate service data sources and minimum-sample thresholds.
  • Wire agent_runs into Trust Evals scoring (defensibility scorecard).