Story-3130

Vani reads the record, and hands the Brief to Vega

Three finished capabilities sat in the record with no way for the agent counsel actually talks to to reach them, and a fourth — asking for the Brief to change — had no path at all. This connects what already existed. It invents nothing: document_card.v1, the Story-2328 insight brief, and Vega's runtime were all built and unreachable.

What counsel can now do

Counsel saysWhat happensState
"What's in the rent agreement?" record_card reads the stored card — cited type, dates, parties, amounts, and the enrichment stamp, citations intact. LIVE
"What does that order actually give us?" record_insights reads the insight brief — the verified reading, positions, silences and authorities. LIVE
"Send me the rent agreement." send_record_link answers which document, in which matter. Delivery is the broker's, and it is not built. DORMANT — Story-3177
"Tighten the issues section." Vani says "I've asked Vega" and her turn ends. The broker carries the request to Vega, who proposes a change counsel confirms. LIVE

The tenant boundary: matter_id is not the model's to name

Every record read takes a document_id and derives the matter from the verified invoke envelope. The brain already pins it in code and filters the model's arguments to a whitelist; the broker does not rely on that cooperation. If a matter_id arrives in the params and disagrees with the envelope, the read refuses — it does not quietly answer about the envelope's matter, because a disagreement means something upstream is wrong and the safe move on a tenant boundary is to stop, not to pick a winner.

The refusal is decided before any query runs. The test proves that by passing a bare object() as the database session: a read that touched the database would raise AttributeError instead of passing.

UNAVAILABLE, never empty

An empty result is a claim. [] from a record read makes Vani tell counsel the document is clean when nobody looked — fabrication by omission in a legal record.

Every cannot-answer path returns byte-exactly:

{"status": "UNAVAILABLE", "reason": "<why>"}

That exact shape is what the brain's delta-stream guard keys on to stop a rejected finding reaching counsel mid-stream. Any other shape silently bypasses that guard. There are five such paths: no card yet, no brief yet, no matter scope, no document named, and a document that is not in this matter.

A document you cannot see reads like one that does not exist

A document in a sibling matter of the same firm returns a reason string byte-identical to a document that never existed. The firm predicate would admit it; the envelope's matter scope does not. If the two reasons differed, the difference itself would be an oracle for probing what a firm holds.

The insight brief is re-verified at read, not trusted

The producer byte-verifies every record pin against real page bytes and drops what will not verify. This applies that gate again at read, because the pages a brief was verified against can move under it — a re-OCR or re-extraction shifts the bytes a stored span points at.

Vani asks; Vega writes

request_frame_update is delegation, not a write. Vani holds no write capability at all, which is what makes an injected document harmless: at worst it can cause a request.

terminal converse turn (Vani: "I've asked Vega.")
      -> broker enqueues kind='frame_update' on the clerk rail
      -> clerk worker builds Vega's input, invokes Vega's runtime
      -> Vega's frame lands as a PROPOSED version
      -> counsel sees the diff and Confirms or Rejects

This is the same shape as the kind='compact' rail: a broker-initiated follow-on turn with nobody waiting on it. Counsel's instruction rides FramerInput.counsel_note and the scope becomes locked_fields — both fields Vega already accepts, so no new agent contract was needed.

There is no reply to Vani, by design

She does not wait and never learns the outcome directly. The next invoke envelope carries the new frame_version and she simply has it. Founder, 2026-07-29: "It is not Vani's job to give any reply back… we are trying to build a decoupled system — if we start coupling so much, we won't be able to build this system."

The human gate is on the apply

Asking Vega changes nothing, so nothing gates the asking. The frame does not move until counsel confirms a diff they can see. Scope is enforced twice on the way: we lock every field outside the scope before Vega runs, and the landing independently recomputes the diff and rejects a change outside scope. The first is an instruction to a model; the second is the check that holds when the model ignores it.

What is deliberately NOT declared

send_record_link's executor is complete and the tool is unreachable — hidden from every catalog, denied at the leash, and off the converse round-trip allowlist.

D16 puts delivery in the broker: mint a wrapper token for the panel, or fetch bytes and attach the file on WhatsApp. Neither rail exists yet. The panel mint needs a decided answer to what revokes a panel-minted token (today every token's revocation is derived from a channel binding a panel session does not have), and WhatsApp has no outbound media path at all.

A tool a brain can see is a capability we have promised. Declaring this one now would mean counsel asks Vani to send a document and nothing arrives. So it stays in DORMANT_TOOL_NAMES, and Story-3177 flips it when the delivery is real.

Hiding it from the catalog alone was not enough, and review caught that: a stale, hallucinated, or injected intent naming it would still have executed and handed the model a reference nothing could deliver. Dormant now means denied at the leash, so the deferred state is inert rather than a live path waiting for part two. The handler and its tests stay, so the delivery Story flips one constant. If a reference is somehow still produced with no rail behind it, the broker logs it loudly rather than returning quietly — a trace that ends early looks exactly like one that finished.

How this was proved

Nothing here counts as green until it has been observed red. Each property below was broken on purpose, the run watched to fail, and the break reverted:

  1. dropped the matter predicate from the document lookup → the cross-matter probe stopped being byte-identical;
  2. returned {} instead of UNAVAILABLE for a missing card;
  3. kept pins that no longer byte-resolve;
  4. advertised the dormant tool in the catalog;
  5. made the no-delivery-rail return silent;
  6. took matter_id from the model's params;
  7. silently skipped the dispatch when the queue was unwired;
  8. stopped confining Vega to the requested scope;
  9. deleted the dispatch call from the runner.

Automated review then found four more, each of which is now its own test and its own planted negative: an archived document still reading out through its retained card; the dormant tool executing from an injected intent; a redrive stranding a delegation whose reservation had committed but whose send had not; and the landing diffing Vega's output against an older frame than the one Vega was given.

Follow-ups filed, not deferred quietly