docs(core): surface the config-relative recomposition caveat

Elision reads config/context.json at compose time, so the composed
payload is no longer a pure function of the durable log — inspecting an
old turn after a config edit can show different prefix bytes than were
transmitted. Document the exception against §8.3 and make the inspect
CLI print the policy in effect so divergence is visible instead of
silent. The gold fix (recording the applied policy on the turn) is
noted for when exact-bytes replay becomes a hard requirement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ramnique Singh 2026-07-07 20:26:22 +05:30
parent 8a952a4b7e
commit 4b4e6af2ea
2 changed files with 25 additions and 1 deletions

View file

@ -409,6 +409,15 @@ in `config/context.json` (`elideHistoricToolResults`, default true;
`elideHistoricImages`, default true; `elideHistoricMiddlePaneContent`,
default true). The inspect CLI composes through the same decorated resolver.
Caveat: elision makes the composed payload a function of the durable log
PLUS the config in effect at compose time — the one deliberate exception to
"recomposition from durable state alone" (§8.3). Within a turn this is
harmless (policy is loaded once per execution, at resolve time), but
inspecting an old turn after a config change may show different prefix
bytes than were transmitted; the inspect CLI prints the active policy so
the divergence is visible. If exact-bytes replay ever becomes a hard
requirement, record the applied policy on the turn and compose from that.
### 6.7 Agent snapshot inheritance
Session turns whose resolved system prompt and tool set are byte-identical

View file

@ -8,6 +8,10 @@
// messages (user-message context, attachments, tool-result envelopes) are
// visible; the file itself stores only structural facts and references.
//
// Caveat: historic-context elision reads config/context.json at compose
// time, so recomposition is exact only while that config matches what was
// live when the call ran. The header prints the policy in effect NOW.
//
// Session mode prints the session overview (title, turns, statuses, sizes);
// pass --turns to cascade full turn inspection for every turn.
//
@ -27,7 +31,7 @@ import { convertFromMessages } from "../agents/runtime.js";
import { WorkDir } from "../config/config.js";
import { FSSessionRepo } from "../sessions/fs-repo.js";
import { composeModelRequest } from "./compose-model-request.js";
import { createContextResolver } from "./context-elision.js";
import { createContextResolver, loadElisionPolicy } from "./context-elision.js";
import { FSTurnRepo } from "./fs-repo.js";
const turnRepo = new FSTurnRepo({
@ -81,6 +85,17 @@ async function inspectTurn(
console.log(
`agent ${agent.agentId} model ${agent.model.provider}/${agent.model.model} calls ${state.modelCalls.length}${inherited}`,
);
const policy = loadElisionPolicy();
console.log(
`context elision (per config/context.json NOW; transmitted bytes reflect the config live at call time): ` +
[
policy.toolResults
? `toolResults>${policy.toolResultThresholdChars}`
: "toolResults off",
policy.images ? "images" : "images off",
policy.middlePaneContent ? "notes" : "notes off",
].join(", "),
);
for (const call of state.modelCalls) {
if (onlyIndex !== undefined && call.index !== onlyIndex) continue;