feat(mcp): every result carries the envelope (P5)
Agent reason: an agent should never have to guess what follows a call. One place (call_tool, after the tool answers) now gives every tool result the same four fields: next_steps (what to do next, each with why and whether it is required), guards (the named refusal and the tool that remedies it), warnings, and, on any tool that is not read-only, head (where the journal ends after the call's last write). A tool's own field of the same name is kept, never overwritten; the text block says what the structured content says. Next steps come from the tool's own result (a preview gets its confirm step with the digest; a post is followed by showing the entry and, only if it was in error, its reversal; a draft by its lines; a statement by marking its lines and reconciling; a period list by the close) and, after a change to an entry or an invoice, from the viewer's own Needs-attention items about that subject, so the agent and the human read one list. guards.rs is the registry of refusals (stale_preview, format_upgrade_pending, receipt_required, approval_required, party_shares, party_required, open_drafts, untagged_control_line, duplicate_vendor_ref, first_number_locked), each naming a tool that exists. A refusal whose text carries a guard gets it as structuredContent.guards; its text is unchanged. Tests: every_mutating_result_has_head_and_next_steps (the whole router), the_envelope_keeps_what_a_tool_said_and_carries_a_refusal_guard, every_guard_names_an_existing_tool, a_refusal_becomes_its_guard, every_step_a_tool_names_exists. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
1 parent
169b914e1f
commit
cf73d0bda5
5 files changed
+803
No files matched your search
@@ -446,6 +446,34 @@ every tool over every book in ledger-store's `tests/fixtures/old-books`.
|
||||
`books.purge_tin`, `books.consolidate_duplicates` and
|
||||
`books.discard_draft`. (`books.void_document`, `books.run_depreciation`
|
||||
and `books.run_recurring` post at once and have no `confirmed`.)
|
||||
- **The result envelope (P5, plan 4.6).** Every tool result, whatever the
|
||||
tool, is given the same four fields in one place (`call_tool`,
|
||||
`next_steps.rs::apply`), after the tool has answered; a tool's own field of
|
||||
the same name is kept, never overwritten:
|
||||
`next_steps: [{ tool | prompt | command, args?, why, required }]` (what to
|
||||
do next: after a preview, the same call with `confirmed: true` and the
|
||||
returned `preview`; after a post, `books.show` of the entry, plus the
|
||||
Needs-attention items about that entry or invoice, the viewer's own,
|
||||
`required` when they are problems; after a close, `books.verify`; after
|
||||
`books.export_cpa_package`, a `command` step carrying the download
|
||||
command; every other tool that can change the books ends with
|
||||
`books.digest`), `guards: [{ code, message, remedy_tool, remedy_args? }]`
|
||||
(empty on success; on a refusal that is a guard, the refusal's
|
||||
`structuredContent` is `{ guards: [..] }` and its text is unchanged),
|
||||
`warnings: [..]` (a tool's own `warnings` is that field), and, on every
|
||||
tool that is not read-only, `head: { seq, hash }`, the journal's head
|
||||
after the call's last write (or as it stands, when the call wrote
|
||||
nothing). The guard codes are the enum in `guards.rs`, each with its
|
||||
remedy tool: `stale_preview` (the tool again, previewed),
|
||||
`format_upgrade_pending` (`books.start`), `receipt_required`
|
||||
(`books.attach_receipt`), `approval_required` (`books.approve_entry`),
|
||||
`party_shares` and `party_required` (`books.set_entry_party`),
|
||||
`open_drafts` (`books.list_drafts`), `untagged_control_line`
|
||||
(`books.control_check`), `duplicate_vendor_ref` (`books.search_entries`)
|
||||
and `first_number_locked` (`books.settings`). Tests:
|
||||
`every_mutating_result_has_head_and_next_steps`,
|
||||
`the_envelope_keeps_what_a_tool_said_and_carries_a_refusal_guard`,
|
||||
`every_guard_names_an_existing_tool`.
|
||||
- **Errors are typed strings** mirroring `LedgerError` variants, with the
|
||||
offending values included (e.g. unbalanced totals).
|
||||
|
||||
|
||||
Reference in new issue
Block a user