Skip to content

fix: keep the continuity digest within harness injection limits - #422

Merged
levifig merged 3 commits into
mainfrom
claude/wizardly-wing-91ee03
Oct 22, 2026
Merged

levifig merged 3 commits into
mainfrom
claude/wizardly-wing-91ee03

Conversation

@levifig

@levifig levifig commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Summary

The continuity digest grew past what harnesses inject. Claude Code saves hook context over 10,000 characters to a file and gives the agent only a 2,000-character preview, and Codex does the same past about 2,500 tokens. On 21 October this repository's digest was 11.1 KB and spwn/infra's 15.4 KB, so agents started with a preview of one old wrap.

  • The default loaf journal context, every SessionStart hook and post-compaction resumption now print one compact digest. Each entry is one line with its date, id and a shortened message; loaf journal show <id> prints it in full.
  • Pagination cursors and empty layers are gone. A layer showing fewer items than it has names a command with an explicit --limit that lists them. Repeated diagnostics collapse to one line per code.
  • The digest, including any drift nudge, stays under an 8,000-byte budget. The canonical layer order is the priority order: when the budget is tight, every layer keeps its first items before any layer gets more. The digest says how many entries it left out, even when a hard cut is needed.
  • config/target-capabilities.json records each target's injection limit, its unit, source and the date it was checked. Cursor, OpenCode and Amp document none and are held to the tightest documented limit.
  • --json and single---layer output are unchanged.

Closes #345

Native work

Verification

All checks below ran on the final head, d551bb6: #345 rebased onto main 02f36e9, plus the #418 assertion fix and the Ship review fixes. The first round of evidence on 5b6b257 is superseded.

  • Golden tests. These tests pin exact digests:
    • TestJournalContextDigestRendersCompactLayers covers every layer kind, an empty layer, an unavailable layer and a diagnostic.
    • TestJournalContextDigestPriorityFollowsCanonicalLayerOrder uses a budget that admits only the first items of the leading layers.
    • TestJournalContextDigestFitsBudgetAndKeepsEachLayersFirstItems runs at 8,000, 4,000 and 2,000 bytes.
    • Further tests cover byte-identical repeats, one-line UTF-8 shortening, folded titles and scopes, legacy spark ids, and the 8-checkpoint list command (--limit 8).
    • Grouped diagnostics: 200 repeats collapse to one line, and entries survive.
    • A hard cap keeps the cut marker and the omission notice.
  • Every injecting target stays under its recorded limit. TestJournalContextHookPayloadsFitRecordedInjectionLimits logs 51 oversized entries and renders the Claude Code, Codex, both Cursor records' and OpenCode payloads, including OpenCode's post-compaction resumption. It holds each to the limit in config/target-capabilities.json and asserts that only Amp, which delivers no automatic context, goes unchecked. With the old renderer the Claude Code payload is 47,923 bytes against 10,000.
  • Fresh Claude Code session in this repository. Claude Code 2.1.303 ran headless with a d551bb6 build first on PATH. The session's hook_additional_context attachment holds the 6,023-character digest inline, naming this worktree's branch.
  • After compaction. /compact in the same session: the SessionStart:compact attachment again holds the 6,023-character digest inline.
  • Second project with a large journal. spwn/infra (4,393 journal entries): the installed build emits 15,367 bytes, d551bb6 emits 6,541. A headless Claude Code 2.1.303 session there received the 6,497-character digest inline.
  • --json and --layer paging unchanged. The existing JSON and layer tests pass untouched. The only changed assertions concern the human digest: the empty project-synthesis layer is left out (two tests), and fix: scope journal context to the invoking worktree's branch #418's tests now match the new branch header (three assertions).
  • Full local gate. env -u LOAF_DB make verify-local passed on d551bb6: 60 Go packages, typecheck, vet, CGO-free build, 39 adapter runner tests and generated-content drift. loaf check issue-changelog passes for Fit the continuity digest to each harness's injection budget #345 against main.
  • Isolate Git test fixtures from the developer's global git config #425. TestWorktreeStorageMissingMainPreservesCanonicalIdentity fails on this machine when run alone, because of worktree.useRelativePaths in the global git config, on main as well. It passed inside the full gate. CI does not hit it.

Ship

Approved on d551bb6: verdict.

Migration / risks

  • config/target-capabilities.json gains a required injection_limit on every record. The contract stays at version 3, and a code comment says why: the file is repo-local and only this binary reads it, while bumping the version would churn every installed target manifest.
  • The digest's human format changed. Nothing in the repository parsed it; --json remains the machine surface.

Out of scope

Claude Code saves hook context over 10,000 characters to a file and
injects only a 2,000-character preview; Codex does the same past about
2,500 tokens. The digest had grown past both, so agents started with a
preview of one old wrap.

The default `loaf journal context`, every SessionStart hook and
post-compaction resumption now render one compact digest: one line per
entry with its date, id and shortened message, no cursor tokens, no
empty layers, and a cursor-free command for any layer showing fewer
items than it has. It stays under an 8,000-byte budget; when that is
tight, layers give up their oldest items first, one round at a time,
and the digest says how many entries it left out.

Each target's injection limit, unit, source and checked date is now
recorded in config/target-capabilities.json, and a test holds every
adapter's payload under it. --json and single --layer output are
unchanged.

Closes #345
#418's tests looked for the old "  branch: <name>" header line; the
compact digest names the branch in its first line instead.
@levifig

levifig commented Oct 22, 2026

Copy link
Copy Markdown
Owner Author

Ship: request changes on 5b6b257. The design holds up: one renderer with an 8,000-byte budget sits behind every hook path, and nothing is stored. Three gaps keep it from meeting #345 as written, and the head that will land still needs its checks.

  • Head: the rebased head with the fix: scope journal context to the invoking worktree's branch #418 test fix isn't pushed yet. make verify-local and the three live-session checks must run on the head that lands.
  • "More" command: for exploration checkpoints, the digest's "more" command shows the same 5 items again whenever there are 6 to 10.
  • Diagnostics: they have no size bound. Enough unmatched unblocks push every entry out and cut off the note that says what was left out.
  • Priority order: no test shows which layers win when the budget is tight, and the docs don't say the layer order is the priority order.

Agent notes

Ship review of #422 found three gaps:

- Exploration checkpoints page at five with no cursor, so a "more"
  command without --limit showed the same five again. Every list
  command now names --limit min(total, 100).
- Diagnostics had no size bound, and a hard cut dropped the omission
  notice. Diagnostics now collapse to one line per code, and a cut keeps
  the cut marker and the notice when they fit.
- No test pinned the priority order. A golden test now shows which
  layers win at a tight budget, and the docs say the canonical layer
  order is the priority order, and why.

Also: the drift nudge now shares the budget; titles, scopes and labels
fold onto one line; legacy deferrals name their spark; the payload test
checks that only Amp goes unchecked; and the contract notes why
injection_limit stays under version 3.
@levifig
levifig force-pushed the claude/wizardly-wing-91ee03 branch from 5b6b257 to d551bb6 Compare October 22, 2026 00:34
@levifig

levifig commented Oct 22, 2026

Copy link
Copy Markdown
Owner Author

Ship: approve d551bb6. Every finding from the first review is fixed, and the full local gate passes on this exact head. #345's Verification boxes are all ticked, each with a result recorded for this head, live Claude Code checks included. In this repository the digest is now 6,601 bytes, down from 11.1 KB.


Agent notes
  • Candidate: d551bb64 (tree b1368e7e) = 02f36e96 + 4dc532b9 (range-diff identical to the reviewed 5b6b2576) + 67968670 (three fix: scope journal context to the invoking worktree's branch #418 header assertions, test-only) + d551bb64 (review fixes).
  • Delta review (Ship):
    • list commands always carry --limit min(total,100);
    • diagnostics group per code, and the omission notice is kept through a hard cut;
    • a priority golden test plus a docs paragraph on why canonical order is priority order;
    • the drift nudge shares the budget (budget - len(nudge) - 2);
    • titles, scopes, labels and branches fold onto one line; legacy sparks show their id with a loaf spark show hint;
    • the payload test asserts only Amp is skipped; the v3 comment is on TargetInjectionLimitRecord.
    • No new defects found. Nit, not blocking: a nudge over about 8 KB would make the budget negative and panic the cap. Nudges are fixed text of about 90 bytes.
  • Evidence on this head:
    • Ship's own env -u LOAF_DB make verify-local in a detached worktree: exit 0.
    • CI build and Issue changelog coverage pass, and loaf check issue-changelog passes for Fit the continuity digest to each harness's injection budget #345 against origin/main.
    • The real digest, read-only, is 6,601 bytes.
    • The author's live checks are recorded in the PR: Claude Code 2.1.303, a 6,023-character digest inline at start and after /compact; spwn/infra (4,393 entries) a 6,497-character digest inline.
  • First-round findings: all resolved (see the earlier Ship comment).
  • Follow-on: feat: resolve journal blockers by entry id and stop listing stale ones #424 (Let agents resolve journal blockers by entry and stop listing stale ones #420) rebases onto this and ports its stale-blocker collapse into renderJournalContextDigest.

@levifig
levifig marked this pull request as ready for review October 22, 2026 00:42
@levifig
levifig merged commit 5905ca5 into main Oct 22, 2026
5 checks passed
levifig added a commit that referenced this pull request Oct 22, 2026
After #422 the default digest, every SessionStart hook and post-compaction resumption render through renderJournalContextDigest, so the 30-day blocker collapse moves there: aged-out blockers are counted next to the layer's list command instead of listed, and listed blockers carry the resolve-by-id hint. The --layer read lists every blocker in #422's [date] id order. A Runner clock lets a test age real blockers and drive the CLI and Claude Code hook paths end to end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fit the continuity digest to each harness's injection budget

1 participant