Outcome: Vani's agent-side schemas, bounded phase logic, scope
pinning, and stream guard are ready for send_record_link,
record_card, and record_insights. All three remain
honest UNAVAILABLE stubs until the broker registry, executors, and
delivery half exists. Vani therefore has one live resolver and 16 stubs.
first invocation → resolve_matter_document → broker resolves under counsel authz/RLS one unambiguous resolved document → exactly one record-stub phase is available → only the three record schemas are exposed → direct model prose and reasoning are rejected and withheld from turn.delta → the matching stub returns exact UNAVAILABLE future broker record result → tool-free answer pass → no third tool leg, no loop
send_record_link returns only which document belongs to which matter.
It does not mint or return a URL and accepts no channel. The broker separately mints a
panel wrapper or fetches bytes for WhatsApp.{"status": "UNAVAILABLE", "reason": "<why>"}
This exact two-key shape is shared by local stubs and every future broker cannot-answer result. Empty values and malformed unavailable objects are rejected. More importantly, the stream detects broker results before the first model delta and installs the same output validator on the answer pass. False “nothing found” prose or reasoning is therefore withheld; only the corrected unavailable answer can reach counsel.
These strict views are agent-side plumbing, not a claim that the broker can execute the tools today.
| Tool | Whitelisted result |
|---|---|
send_record_link |
Matter id and document id only. URL, token, storage, bytes, and delivery fields are rejected at the prompt boundary. |
record_card |
The existing document_card.v1 top level, including cited values and
the enrichment/built-from stamp. Nested citations are preserved. |
record_insights |
The existing Story-2328 brief fields. Each non-empty broker-verified pin dictionary passes through intact; Vani never recreates a pin the broker dropped as unverifiable. The persisted broker-side pin DTO is not defined in this CodeZip, so the companion Story must freeze that exact DTO rather than making Vani guess its internal fields. |
This PR deliberately stops at the agents boundary. The broker companion must provide:
document_id inputs;mint_fetch_link and the existing
open handler unchanged: restricted means no mint; wrapper life is about 24 hours while
the S3 credential is minutes-lived; the opener supplies their own Clerk session and passes
own-matter RLS; denials stay uniform 404; only the token hash is stored; mint, open, and
real-token denial audits still land;The exact eager resolve-phase definitions are serialized to the OpenAI wire shape and
counted with o200k_base. Relative to the resolver-only baseline, the 13
non-record stubs add 1,952 tokens and
8,913 UTF-8 bytes. The three record schemas are exposed only in the
separate bounded record phase, so they do not inflate that cached resolve prefix.
| Requirement | Test |
|---|---|
| The dormant intent plumbing pins matter and accepts only the resolved document id | test_record_intent_pins_matter_and_accepts_only_resolved_document_id |
| Prepared reference, card, and insight views exclude delivery data | test_each_record_tool_exposes_real_data_through_its_whitelisted_view |
| A resolved request cannot silently settle as direct model prose | test_resolved_document_cannot_silently_finish_without_a_record_tool |
A broker false negative never reaches turn.delta |
test_broker_unavailable_finding_never_reaches_the_delta_stream |
| All 16 stubs remain exact UNAVAILABLE, never empty | test_every_stub_returns_unavailable_and_vani_says_it_is_not_built |
| Resolve and record phases expose disjoint tool catalogs; measured delta is pinned | test_converse_registers_the_exact_specced_tools_and_measured_schema_delta |
agents/mira tests → 314 passed uv run ruff check . → All checks passed uv run pytest -q → 444 passed, 3 dependency warnings