# Claude Code Mod

The package also ships `plugins/claude/`, an early-access Claude Code Mod that registers five notes tools as `mcp__notesoup__notes_*` and three history tools as `mcp__notesoup__history_*` and attaches one host-scoped notes boot block through Claude's conversation-scoped `prompt.context` event; Claude persists it across resume and reload. The sandboxed hooks module delegates filesystem and shared-domain work to the plugin-local `plugins/claude/dist/claude/helper.js` through Claude's bounded `$.process.run` capability.

In interactive sessions it resets automatically when the measured context reaches notesoup's reset point, checking after `session.measure` and `turn.complete` and waiting for a running turn to complete. That point is derived from Claude's own auto-compact threshold instead of a fixed percent, in `plugins/claude/src/thresholds.ts`, and reaches the sandboxed hooks module through the helper's `limits` operation:

1. `$.session.usage({breakdown:"summary"}).context.autoCompactThreshold` when it is a positive number, with `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE` applied the way Claude applies it (it can only lower the point);
2. otherwise computed from `$.session.usage().context.window` plus `CLAUDE_CODE_AUTO_COMPACT_WINDOW`, `modelSettings.<model>.autoCompactWindow` and `autoCompactWindow` (the smallest configured window wins, capped at the model window), with Claude's 20000-token output reserve and 13000-token summary buffer;
3. otherwise the percent fallback, which expresses the same reserves on the classic 200K window.

Notesoup resets one `resetMarginTokens` (default 8192) below that point, and warns one `warningRunwayTokens` (default `WARNING_RUNWAY_TOKENS`, 16384) below the reset. With automatic compaction off (`autoCompactEnabled: false` in the merged settings, or `DISABLE_AUTO_COMPACT=1`) the point becomes the effective window minus the output reserve, so the same margins still apply. Every undocumented field is type-guarded and degrades to the next source; an unresolvable point, or missing usage, logs once and skips the automatic reset. `DISABLE_COMPACT=1` is detected at `session.start`, logs once, disables the automatic reset, and makes `/fresh-window` report that it cannot run. The margins are read from the `notesoup` object of Claude's merged `$.settings.read()` (`resetMarginTokens`, `warningRunwayTokens`); any other value is ignored.

Once per window, when the measured context crosses the warning point, `prompt.context` adds a `notesoup:warning` block carrying the shared warning text with Claude's closing instruction ("end your turn; the reset runs after it"). The warned flag resets with the window ordinal. Its own `session.compact` hook answers native compaction in the main conversation instead of letting it summarize: an automatic compaction (threshold or prompt-too-long) becomes the freshly built notes boot followed by one randomly selected paragraph from the shared forced-reset pool, selected once for the new window and reused on retry — the same boot-then-handoff order as Pi — while `/compact` and precompute are vetoed with a one-line notice. `prompt.context` still recomputes but does not duplicate that retained boot. The boot's protocol block tells the model to end its turn and ask the human to run `/fresh-window`, replacing that one sentence through `BootToolNames.wipeSentence`; Pi's boot text is unchanged. Boot window IDs end in the ordinal `:0`, `:1`, etc.; retained boots restore the ordinal on resume/reload. Original transcript rows remain on disk. `session.end` forgets the old window, and identity is refreshed lazily from `$.session.id()`, `$.session.cwd()` and the documented `$.session.model()` (slugified, with `claude` only as a fallback). `/clear`, `/resume` and `/branch` therefore acquire a boot again without needing another `session.start`. A failed boot build falls back to Claude's own summary, which invalidates a removed retained boot; vetoes, subagent compaction and precompute leave it alone.

`/fresh-window` requests the same reset. While idle it submits one hidden normal-model close-out turn to let the model save notes, then resets at `turn.complete`; while busy it waits for the current turn instead. The close-out prompt is the same shared warning text the automatic path shows. This is necessary in Claude Code 2.1.295: `command.run` holds a turn, and calling compact from a detached timer skips the plugin's own compact hook. The timer only schedules the close-out prompt, never compaction. The headless (`-p` / SDK) compact capability is unsupported: automatic attempts are caught and logged once, and the manual command skips without submitting a model turn. Boot acquisition failure vetoes reset, leaving the current window intact. A Claude dream backend is not implemented; budget reporting (`get_context_remaining`) remains Pi-only, while the reset points and the one warning per window now follow Claude's own configuration.

The notes tools also serve subagent calls, using the same session identity and shared operations. The plugin exposes the shared memory and dream skills and the dream playbook; the build keeps these copies byte-identical to the canonical files.

## Persisted history

`history_list`, `history_read` and `history_windows` use the same schemas, literal search (`history_list.query`), paging, folding, pairing and text rendering as Pi. They read only the current main session's persisted JSONL, via the documented `classic.SessionStart.transcript_path`; no path is guessed and `$.session.messages()` is not treated as full history. The classic binding survives typed `session.start`, which fires later. Without that path, history calls refuse and the boot says history is unavailable. Subagent history calls refuse rather than silently exposing the main transcript.

The active head comes from the main loop's successful `session.append` result UUID, including attachments and notices. On startup/resume, before such an append, the public API supplies no authoritative selected leaf: history explicitly refuses instead of assuming the last file row or `last-prompt.leafUuid` is current. The append result means the engine kept a row, but its disk write may lag; a head missing on disk is a retryable refusal, not permission to switch branches. Session end clears the binding; a new classic start rebinds it.

Seqs are allocated over first-occurrence persisted append order, before active-branch filtering: one address per logical message/attachment/notice and additional addresses for each tool use/result. Repeated UUID records do not allocate again. Results pair by `tool_use_id`; pending calls remain unfinished, and a paired result's seq redirects to its call. Only text blocks and recorded attachment renderings become visible text; thinking and raw, unrendered attachment payloads are never exposed. Known bookkeeping metadata is deliberately ignored; unknown message/block schemas and malformed committed JSONL lines refuse explicitly. An incomplete final unterminated write is tolerated. Valid forward parent references are resolved after reading the snapshot, without reallocating addresses.

The selected tree follows the latest native `parentUuid` links. Compact boundaries also join their `logicalParentUuid` and recorded preserved segments through original ancestry, retaining old events instead of presenting only retained inference context. Auxiliary preservation UUIDs that were never persisted are skipped. Windows use `claude:<sessionId>:<ordinal>`, matching the boot: native compact boundaries advance the ordinal, and retained notesoup boot markers name that same ordinal rather than advancing it again. Preserved duplicate rows keep their original window; new children of their rewritten versions inherit the new window. `@previous` resolves through this oldest-first window list. A missing required ancestor is an explicit refusal.

## Validation

`npm run typecheck` includes the mod against the vendored Claude Code 2.1.295 contract. `npm test` also typechecks it and runs `claude plugin validate plugins/claude --strict`, skipping validation with a clear message only if `claude` is not installed. Scanner regression tests deliberately reintroduce each of the three forbidden call shapes and require the gate to fail. From a built checkout, check the real load with `claude --plugin-dir plugins/claude -p "ok"`; a model/API failure after load is separate from a hooks-module refusal. The package build and `npm pack --dry-run` include the manifests, hooks module, helper, skills, and playbook.
