Skip to content

Latest commit

 

History

History
183 lines (158 loc) · 16.8 KB

cache-dump.md

File metadata and controls

183 lines (158 loc) · 16.8 KB

Offline cache dumps

broca-session cache-dump reads the state root's live and archived WALs without a daemon or write handles on the source. It reconstructs requests with the shared offline renderer and writes prompt-bearing bodies only after their SHA-256 matches the completed attempt's recorded digest.

broca-session cache-dump --session alfonso:example --out ./private-dump
broca-session cache-dump --since 2026-10-01T00:00:00Z --out ./private-dump

Repeat --session for multiple session names or bare task ids. Name selection uses the same read-only session-ref inventory lookup as trajectory and read: try the supplied name first, then alfonso:<id> for an unprefixed name. It scans only the compact inventory table with a SQL name predicate, never run-index history, and adds no database index or migration. Unknown names and ambiguous names refuse before output creation, listing the searched names or candidate bind triples. An incomplete inventory cannot prove absence or uniqueness and asks for explicit bind flags. The command does not scan all WALs as a fallback when name lookup cannot resolve a session. To bypass lookup, provide both --project-root <path> and --harness <name> with the full recorded --session name; explicit selection keeps the name literal. The shared session_addr derives each triple's storage address, and the WAL store reads that session's live WAL or its archived copy; no other WAL is read. --since preselects runs by the index's state_changed_ms, which is written on a terminal transition after WAL sync and therefore bounds that run's last activity. Active and paused rows are always included because their state timestamp is not a completion bound. Terminal rows with no timestamp are legacy rows from before state_changed_ms existed; they are excluded from time selection and counted as undated_runs_not_selected. --session still reaches them. The selected bind triples are deduplicated by address. A final WAL check requires each call's selected attempt to have ts_ms >= since; only those calls are exported. Earlier history is still replayed so their bodies remain exact. Calls excluded by this cutoff are counted as calls_before_since. Unlike --since, --session exports full histories. --since accepts RFC3339 timestamps or epoch milliseconds. --state-root overrides normal state-root resolution. Do not pass daemon connection arguments to this offline command.

Each completed model step uses its last completed attempt with a response. The filename prefix uses the live transport's shared request_dump_filename function: the sanitized run id and -s<step>. Production run ids already contain the run-sid- prefix. Attempts are recorded in metadata, not filenames. Metadata records the original run id, UTC attempt timestamp and filenames. Bodies are the renderer's original bytes, not pretty-printed JSON. Usage is restored to the family's own wire field names and semantics under usage; the WAL's normalized usage is also carried unchanged under broca_usage. Unreported counts are omitted in both.

Family usage fields
Anthropic Messages input_tokens (fresh input only), cache_read_input_tokens, cache_creation_input_tokens, output_tokens
OpenAI Responses and ChatGPT input_tokens (fresh input plus cache reads and writes), input_tokens_details.cached_tokens, output_tokens
Gemini and Google Code Assist (Antigravity) camelCase usageMetadata names: promptTokenCount (fresh input plus cached content), cachedContentTokenCount, candidatesTokenCount (visible output, excluding thoughts), thoughtsTokenCount

Code Assist responses wrap Gemini's body in {response: ...}, but the exported usage is the inner usageMetadata shape, the same as Gemini's. Broca stores Gemini output as candidates plus thoughts, so candidatesTokenCount is that output minus thoughtsTokenCount. When the two are equal, the response reported thoughts without candidates; Google's JSON omits zero counts, so candidatesTokenCount stays omitted. A prompt count needs a stored input count; a cache count alone exports only cachedContentTokenCount.

On Unix, new output directories use mode 0700 and files use mode 0600. Existing directories with any group or world permissions, symlinks and existing output files are refused. Output parents must already exist. Treat dumps as sensitive prompt data; Windows filesystem ACLs must be managed by the operator. No prompt or body is printed in diagnostics or the JSON summary.

Digest mismatches produce metadata with verified: false, both digests and no body or response. The reason is transformed_request when the attempt carries input_transformations, otherwise digest_mismatch. Prompt-reduction transforms are not rerun: their output is not durably available, and their requests can only be exported if the raw replay happens to match exactly. A response-bearing step without durable usage counts as examined and, when no stronger failure applies, skipped under missing_step_usage; it gets metadata and a body only when the digest verifies, never a response file. Failed retries of completed steps are folded into the final completed attempt. Other families get metadata but no body or response. Renderer/replay failures are counted as render_unavailable; unsupported families as family not exported.

The summary reports candidate sessions read, undated terminal runs excluded from time selection, calls examined, excluded by the attempt-time cutoff, exported and verified, digest mismatches, and skips by reason. The legacy sessions_not_found field remains present but empty on successful selection; missing names now refuse before a summary or export is produced. Each skipped call has exactly one reason matching its metadata: rendering unavailable first, then digest mismatch/transformed request, then unsupported family, then missing step usage. The verified count includes unsupported-family calls whose request digests match; the exported count includes only verified calls in supported families with a body, response and metadata file. Zero examined calls exits nonzero even if the selection or output directory is valid. Source read failures also exit nonzero. A concurrent writer may advance after the read snapshot; rerun to a fresh output directory to include later calls.

Offline cache diagnosis

broca-session cache-diagnose alfonso:example
broca-session cache-diagnose example --state-root ./copied-state --out ./diagnosis.jsonl

cache-diagnose uses the same read-only session/task lookup, explicit --project-root/--harness binding, retention checks, and live/archive WAL reader. It prints one JSONL cache_attempt row for every ModelAttemptFinished, including failed retries, rather than selecting one response per step. No daemon, network, request-body export, or WAL writes are involved. --out creates a new owner-only file; stdout is the default. The output contains account references and other diagnostic identifiers, but no prompt text, thinking text, signatures, or body bytes.

Live model_attempt_finished records optionally carry cache_evidence in the same shape as the offline comparison. The first attempt, or one without a verified predecessor, omits it. Per-session memory keeps only the immediately preceding request's segment hashes/coordinates, breakpoint, send time, and diagnostic identity/policy/view metadata, never its body. On the first run after restart, or a run without a fingerprint, broca reconstructs the last request once through last_recorded_request and accepts it only if its canonical digest matches. Unavailable reconstruction is a gap, never a comparison against an older attempt. The optional admission-checkpoint fact has_model_attempt avoids reading an older prefix when a complete fold proves there were no attempts. Absent means unknown; that session rebuilds lazily once, not in a startup sweep. A proven attempt sets the fact to true permanently, across episode boundaries. Complete supplied snapshots are reused rather than read again; an unproven checkpoint tail never stands in for the missing prefix. Hashing runs on the blocking pool after transport handoff, concurrently with the response, and is joined before writing the attempt. It never changes request bytes.

The shared broca_provider::cache_evidence::fingerprint API underlies both the live comparison and compare_rendered_requests. It borrows raw JSON segments from deterministic renderer output and hashes their bytes. Only segments with cache markers or the named rotating settings need owned JSON normalization. The segment vector grows geometrically; it does not allocate a JSON tree per message/content part. Bodies with different arbitrary whitespace/key order are not an input contract: these comparisons consume broca's canonical renderers.

cache-diagnose prefers recorded evidence even when replay is unavailable; evidence_source is recorded or offline, while verified describes only the reconstruction. Live divergences omit the optional field detail because the previous bytes are not retained. Absent means not recorded, never "no field changed". When verified rebuilt bodies are available, the tool compares them and parses only the first divergent segment to fill that detail, marking a successful resolution with field_resolved_offline: true. Recorded coordinates and all other evidence remain authoritative. Legacy attempts without evidence still use the complete offline comparison below. full_input_reason is always the recorded, open-string transport reason, never inferred from elapsed time by the offline tool.

The shared broca_provider::cache_evidence::compare_rendered_requests compares canonical logical requests reconstructed by wal_render, after checking their recorded SHA-256. A missing or mismatching reconstruction makes cache_evidence null with a reason, including on the following comparison; it never silently compares against an older valid attempt. The first attempt has null evidence and reason: "first_attempt". compared_to is the preceding attempt's <lineage_id>:<wal_seq> in this lineage, across run boundaries. No verdict is emitted: MC retains its shared attribution classifier.

Evidence coordinates and prefix rules

  • extends_previous means the previous normalized request is a prefix of the current request, with all settings and tool/system segments unchanged. A clean message-tail append is (true, null) for (extends_previous, first_divergence). An edit, removal, insertion before the end, or changed setting is false with its first divergence, even after the previous cache marker. The classifier, not broca's comparison, decides whether that coordinate is inside cached input.
  • Message divergences additionally carry optional message_ordinal, the durable transcript ordinal used by session.read and the m<ordinal> component of compaction-view addresses. It comes from the actual emitted row/part's source Message.ordinal, never from a rendered index or parsed prompt text. A view can move m2 to wire position zero; the ordinal stays 2. The current request's source is reported when a current segment exists; a removed tail uses the previous request's source. System, tools, provider settings, and content without a unique durable source omit it. Grouped Gemini tool rows resolve part-level origins, while their ambiguous row header has no single ordinal. The wire part_index is still a provider-array position, not automatically a durable #<part> key. The optional member is absent by default and older evidence readers ignore it.
  • previous_last_breakpoint uses the same coordinate space as first_divergence: {region, message_index?, part_index?}. Regions are system, tools, messages, and provider_state. There are no synthetic message indexes. Indexes are zero-based wire-array positions (including system/developer items embedded in Chat/Responses arrays). Tool indexes use part_index; Bedrock's standalone cachePoint uses its own block index. Non-positional settings have a region and optional changed field. prefix_tokens_before is omitted: JSON segments do not establish tokenizer boundaries.

The trace-aware render entry points accept an optional borrowed provenance sink. The existing entry points use the same builders and emitters with no sink. With provenance enabled, actual row/part emission records only integer coordinates and source ordinals in flat vectors, including filtering, tool-item splitting and grouping, synthetic instructions, and prepared-media rendering. No identity is inserted into request JSON, and fingerprints never retain transcript bodies. When an older recorded divergence has no ordinal, cache-diagnose can resolve it from the reconstructed current and preceding requests after their recorded SHA-256 digests match, and marks message_ordinal_resolved_offline.

  • A null breakpoint with ttl_kind: "explicit" means the previous request had no explicit marker. With ttl_kind: "implicit", a null breakpoint is normal. TTL policy is taken from the previous request's frozen config, including omitted-on-wire Anthropic default TTLs; unknown implicit lifetimes stay null.
  • Explicit prefix region order is tools → system → messages, for both Anthropic Messages and Bedrock Converse, regardless of JSON key order. Anthropic documents this hierarchy in Prompt caching: structuring your prompt. AWS states “Cache checkpoints are processed in this order: tools → system → messages” and that earlier-section edits invalidate later sections in Prompt caching for faster model inference. Broca's Converse renderer builds toolConfig.tools, system, and messages[].content separately (families/bedrock_converse.rs, build_request_body_with_media); its marker planner inserts standalone cachePoint blocks in system/messages (apply_bedrock_marker_policy_to_body). Serialization order is not the cache-prefix order. Provider settings are separate evidence; they are not another position in this three-region prefix.

Segmentation and normalization

Family System / tools / messages
Anthropic system[] / tools[] / messages[].role and content[]
Bedrock Converse system[] / toolConfig.tools[] / messages[].role and content[]
OpenAI Chat system/developer messages[] / tools[] / other messages[] and content
OpenAI Responses, ChatGPT instructions and system/developer input[] / tools[] / other input[] and content[]
Gemini systemInstruction.parts[] / tools[] / contents[].role and parts[]
Code Assist Gemini segmentation inside request; remaining envelope fields are provider state

Remaining request settings and opaque reasoning items are preserved. The only normalizations are cache markers (cache_control, Bedrock cachePoint), output caps (max_tokens, max_completion_tokens, max_output_tokens, inferenceConfig.maxTokens, generationConfig.maxOutputTokens), Code Assist's per-step requestId, the Anthropic billing-header line (body signature and request/episode/turn correlation), metadata.user_id's device/session IDs, and ChatGPT's named client-metadata routing/turn IDs. Comments at each normalization name the fields and why they rotate. Output caps are not cached prompt input; broca intentionally clamps them near the window limit. Model, cache keys, tool choice, reasoning settings/budgets, signed thinking (including thinking.signature), provider text state, and unknown settings remain raw.

ChatGPT comparison uses its canonical full logical input, whose digest excludes socket framing. Rows separately expose the recorded continuation and full_input_reason; a continuation's transmitted suffix is not mistaken for a deleted prefix. The offline command does not reconstruct socket receipts.

idle_gap_ms uses send times, never response-end times. Required numeric evidence uses zero when timestamps are missing; the row's idle_gap_known: false prevents interpreting that as a measured zero gap. Similarly, account_comparison_known distinguishes unavailable account identity from known equality. Attempt billing usage/corrections take precedence; only the final complete attempt of a legacy step without a billing envelope inherits step usage. Failed retries never borrow that meter. Retention is unmetered when needed counts are missing.

retention.expected is prev.cache_read + prev.cache_write when the previous request wrote cache tokens, otherwise prev.cache_read + prev.input. The ratio is current cache read divided by expected: stable ≥95%, warning ≥80% and <95%, bust <80%, and full_bust for zero read after cache existed. Before a cache existed the status is base. Ratios are fractions, not percentages.