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.
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.
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.
| 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 |
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.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):
| Wire | Token delta | Byte delta | Asserted in |
|---|---|---|---|
| matter-pinned (re-measured — unchanged) | 2,145 | 9,903 | test_unavailable_tools.py |
| matter-less (new — measured) | 1,918 | 8,967 | test_matterless_converse.py |
disambiguation.py or any router — that is lane 2, a
different repo (pramaan-functions, Story-3191).command / map_command — both are
matter-scoped by nature and keep matter_id required.uv run ruff check . — clean.uv run pytest -q — 457 passed (root governance suite).uv run pytest agents/mira/tests — the mira suite (376 → 378 with the two
new validator proofs), including the 13 test_matterless_converse.py cases:
validates-and-completes through the runtime; a matter question gets the ask; matter
data on a matter-less turn is rejected (bundle, meta, frame version, fold,
resolver/record results) while CORE personalisation and case-law results stay valid;
matter-scoped tools return the exact unavailable contract with a true reason; no broker
intent ever carries a None scope; matter-pinned prompt bytes, tool
surface, and envelope pinning unchanged; both measured wire deltas asserted.
(uv run --package mira pytest does NOT run this suite — it keeps the root
rootdir and silently runs the governance suite again.)