feat(mcp): books.start v2 and the agent's self-report (P5)

Agent reason: the first call of a session should say who is asking, what is
waiting and what to do, in under 8 KiB, from the same lists the viewer
draws. books.start now carries book.id and book.url (the book's path on the
server, never a path on a disk), you {principal, kind, scopes, client_id},
drafts {building, awaiting_evidence}, reconciliation {tracked,
never_reconciled, behind}, backup {last_at, through_seq, stale}, the
prompts, and needs_attention (the twelve most urgent items, problems first,
with every item counted by kind), beside next_steps now in the envelope's
shape (tool or prompt, why, required; attention problems are required). A
new book is told to run the onboard prompt first, then to record the
company's facts and set up the chart; an active one is offered a draft
entry. The tool carries _meta anthropic/alwaysLoad, so a client that defers
tools loads it up front. The size fit shortens attention items and waiting
drafts after the convention summaries and before the lists, as before.

agent {model, reasoning_effort, harness} is accepted and written to the
call's access record (the access log shows it as agent), self-reported and
never checked, and used for nothing else: not in the answer, not in any
decision. Who asked is the request's alone: it is cleared when the request
ends, so a later resource read on the same thread cannot see it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
swaitsandClaude Fable 5.1 committed 2026-10-29 15:48:27 -06:00
1 parent 996c5f40f4
commit 453abb62ea
7 files changed
+459 -25

No files matched your search

+3
View File
@@ -1206,6 +1206,7 @@ async fn refused_at_door(
args_digest: None,
result: Outcome::Refused(code.into()),
head_after: None,
agent: None,
};
let written = tokio::task::spawn_blocking(move || access.record(&record)).await;
if !matches!(written, Ok(Ok(_))) {
@@ -1435,6 +1436,7 @@ async fn upload(
args_digest: None,
result: outcome.clone(),
head_after: None,
agent: None,
};
let recorded = access.record(&record).is_ok();
if !recorded && received.is_ok() {
@@ -1609,6 +1611,7 @@ async fn fetch(
Err((_, code, _)) => Outcome::Refused((*code).into()),
},
head_after: None,
agent: None,
};
let recorded = access.record(&record).is_ok();
(found, recorded)