Story-3189 Walkthrough

Outcome: Vani's converse route accepts a matter-less turn — matter_id=None, firm-scoped — validates it, and completes it sanely: she can talk, and she asks which matter counsel means instead of answering from nothing. This is lane 1 of 2 for D17 and it unblocks lane 2 (Story-3191, the seven-token router in pramaan-functions), which until now would have sent a run the brain rejected at validation.

2026-07-31, staging. counsel: "is the Sugar Pipes hearing tomorrow?"
  → deterministic front door: a menu whose line 2 read
    "M-013 — Rahul Rathode vs Sugar Pipes Industries delhi"
  → three English messages, three identical menus, zero agent runs.
  The front door rejected the sentence containing its own answer.

D17 (Vani's design lens, PR #406) retires that layer: the deterministic surface shrinks to seven exact tokens and everything else reaches Vani. It could not ship first — MiraConverseInput.matter_id was str, required, no default. This story is the brain half.

The contract: matter and frame are a PAIR

MiraConverseInput now takes matter_id: str | None and current_frame: FrameOutput | None, bound by a validator: a pinned matter ships its frame, a matter-less turn ships neither. The two dishonest states fail closed at validation, before run.started:

And the pair rule alone was not enough (review P1): matter_id=None used to still accept a matter-scoped bundle, matter_meta, frame_version, memory_pack.session_fold and resolver/record tool_results — and build_converse_prompt inserts whatever it is given, so the contract claimed "no record is attached" while the prompt contained one. A second validator now rejects all of them: a broker that omits the matter but sends its data has a bug, and it fails loudly here rather than being silently honoured. Counsel's CORE personalisation (cross-matter, firm-scoped) and case-law results (public corpus, no matter reference by design) remain valid on a matter-less turn.

The envelope rule is unchanged: when the broker pins meta.matter_id it still wins over the nested input, and the matter still arrives only from the verified envelope, never from the model.

What a matter-less turn looks like

stable prefix:  Surface: whatsapp.
                  Writing surface: WhatsApp. …
                  Matter: NONE PINNED — this is a firm-scoped turn. …
per-run instructions: NO MATTER IS PINNED ON THIS TURN … (the matter-less contract)
suffix (uncached):  CONVERSATION_SO_FAR / COUNSEL_MESSAGE / reply line

There is no <<MATTER_FRAME_…>> block and no matter list. The list stays out of the prompt by design (Spike-3126): candidates come from a tool when needed, never a pre-loaded block paid for on every turn.

The four prohibitions, and where each lives

A matter-less Vani must NOT…Where it is enforced
answer a matter question from nothing the matter-less contract (converse.py _MATTERLESS_CONTRACT): no record is attached, so a record question "gets the ask, never the answer"
guess a matter — from history, or the only likely one same contract: "NEVER GUESS THE MATTER … chosen by counsel, or asked for"
leak the matter list into the prompt construction: no candidate block exists in build_converse_prompt; a test asserts neither frame nor list reaches the matter-less prompt
pass None to something expecting a UUID the matter-scoped tools (resolve_matter_document, request_frame_update) become LOCAL stubs returning MATTERLESS_UNAVAILABLE — a true reason ("no matter is pinned"), not the "not built" of the Story-3101 stubs; deferred_tool_intents drops any matter-scoped deferred call by omission when the scope is None
What stays live. The six case-law reads (verify_citation … court_weight) remain on the deferred rail in a matter-less turn: they query public Indian Kanoon and carry no matter reference by design. Public case law is not a matter record.

Caching: pinned turns are byte-untouched; the new wire is measured

The matter-less contract ships only in matter-less per-run instructions, and the matter-less agent is a separately-cached variant. A matter-pinned turn's prompt bytes, tool surface, and instructions are unchanged (prompt v15; the reply line is the exact v14 string — both pinned by tests). Re-measured tool-schema deltas (tiktoken o200k_base, OpenAI wire, vs the resolver-only baseline):

WireToken deltaByte deltaAsserted in
matter-pinned (re-measured — unchanged)2,145 9,903test_unavailable_tools.py
matter-less (new — measured)1,918 8,967test_matterless_converse.py

What this story is NOT

Verification