🛰️ Satellite. PRAMAAN is building a firm of AI agents. Atlas (agent #1) frames the matter; Blake
is agent #2 — the Document Registrar who makes the Record usable after files arrive (clean names,
descriptions, document types, tags, filing links). This Story does not make Blake think yet. It builds his
body: a self-contained agents/registrar/ unit that mirrors agents/atlas/ file-for-file,
imports and builds, passes the brain-purity gate, and reserves zero live tools. The build stories
(broker, effect-ledger, trigger) fill it.
✈️ Airplane. Atlas is read-only: he proposes a Matter Frame, counsel commits. Blake's whole job is to change the Record — and a brain must never hold database credentials. ADR-128 opens the third path:
# ADR-128 — the only way a PRAMAAN brain ever touches business truth Blake brain (AgentCore, network_mode = PUBLIC, no DB/VPC, no creds) → agent_event_stream.v1 (typed tool intent / structured registration output) → pramaan-functions broker (already in-VPC; holds the creds + safety machinery) policy · firm/matter RLS · permission · idempotency · effect ledger · audit → documents.* / business.* / agents.* effect
So Blake's truth boundary is one line, encoded everywhere in the scaffold: proposes only, never writes
canonical. It lives in registrar.yaml (truth_boundary.statement), as constants in
agent.py (WRITES_CANONICAL = False, REQUIRES_BROKER_MEDIATION = True), and is
stamped onto every proposal on the wire (runtime.py _wrap_proposal).
| File | Role (WHY it exists) |
|---|---|
| main.py | @app.entrypoint — fail-closed: missing input raises (no demo fallback). Registered in agentcore/agentcore.json. |
| runtime.py | AgentCore cloud shell + the Bug-1370 thread-isolated boundary watchdog; routes classify / govern; emits agent_event_stream.v1 only. |
| agent.py | Thin orchestrator around two Pydantic AI agents; one-retry; the anti-hallucination completeness gate (_require_complete_all_clear). |
| models.py | Hand-owned Pydantic (ADR-117): a FLAT generation DTO (RegistrarClassifyOutput) the model emits → assembled RegistrarTurn — DeepSeek never sees a nested schema (Bug-1357 class). |
| prompts/ | The soul, one module per capability (classify.py, govern.py) + _render.py (the §H quoted-evidence injection fence). Skeletons. |
| provider.py | OpenRouter-only model + ZDR (data_collection="deny") + the baggage egress stripper. |
| observability.py | Logfire LLM lane + the ADR-124 §D10 allowlist baggage processor + trace-correlated logs. firm_id never in baggage. |
| manual_span.py | The closed agents.registrar.* manual-span registry (skeleton). |
| config.py · model.json · registrar.yaml | Behaviour vs model split (REGISTRAR_* env wins). EMPTY tool allowlist. DeepSeek via OpenRouter, no Bedrock. |
| identity.py | Persona = Blake, role Document Registrar, avatar slot. |
| errors.py · events.py · secrets_bootstrap.py | Typed taxonomy · the v1 event builders · the ONE boto3 secretsmanager file (cold-start secret read). |
| evals/ · tests/ | pydantic-evals skeleton (6 seed cases, 3 deterministic gates, LLM judge) · 51 offline unit tests, no model calls. |
scripts/check_brain_egress.py hard-coded
the baggage/tracer helper homes to atlas/observability.py and atlas/manual_span.py. Drop a
new agent's observability.py beside it — which legitimately constructs a TracerProvider,
writes allowlisted baggage, and uses AwsXRayIdGenerator — and the gate would have failed Blake for doing
exactly what Atlas is allowed to do.The fix recognises the helper home per agent, by basename, so every future agent inherits the carve-out with
no edit, and the allowlist-literal invariant (ALLOWED_BAGGAGE_KEYS == {run_id, form_id}) is now globbed
across all <agent>/observability.py — Blake's baggage posture is validated, not just Atlas's.
# before: atlas-only, hard-coded BAGGAGE_HELPER_PATHS = frozenset({"atlas/observability.py"}) # after: per-agent, by basename — registrar + every future agent inherit it BAGGAGE_HELPER_BASENAME = "observability.py" for observability_file in sorted(root.glob("*/observability.py")): ...assert ALLOWED_BAGGAGE_KEYS is exactly {run_id, form_id}, firm_id rejected
| Gate | Result |
|---|---|
python scripts/check_brain_egress.py | clean ADR-110 §6 / ADR-121 D5 / ADR-124 §D10 |
ruff check agents/ tests/ scripts/ | all checks passed |
cd agents/registrar && pytest tests | 51 passed offline, no model calls |
Regression — agents/atlas + repo governance | 204 + 69 passed (egress refactor safe) |
[]; Blake's broker-mediated effects are added by later
stories via the static registry (ADR-128 / ADR-070).One-sentence recall: Story-1537 gives Blake a doctrine-correct body — Atlas's shape, an empty allowlist, proposes-only — so the EPIC-1536 build stories have a keystone to stand on.