SLOPSHOPPER

context-engine-trigger

Context Engine for Claude Code: compacts at each user-turn boundary so the context-engine mod can answer with the Working Context (Full Replacement per user…

promptprocesstimer
A shopper browsing a rack in a slop shop
README

Context Engine for Claude Code

Release candidate: tool-schema prose and fixtures are independently authored here. Publication still requires review of this exact snapshot and its fresh history; see PROVENANCE.md.

Source approval and installation acceptance: changes target dev, then move to main after the applicable acceptance gates and owner authorization. Verify the exact head commit, its CI results and independent review before an installation trial. A branch name or an earlier passing commit does not establish acceptance of this snapshot.

Ordinary-profile persistent plugin loading, interactive delivery and bounded long-session functional acceptance remain unverified. Claude also requires acceptance of the interactive bridge between its two installed plugins. These checks must prove the documented next-request and read-back behavior on the supported host. Comparative performance research is outside these release gates. Offline source checks do not make provider calls or change host configuration.

Shared-core correction: Working Context writes now use an anchored, verified parent directory. Synthetic Linux parent-swap regressions cover swaps before open, temporary-file creation and rename. Both plugin candidates pin this correction; these checks do not establish real-host installation acceptance.

An experimental, opt-in plugin that lets your agent edit its Working Context with ordinary tools. This repository ships only the Claude Code adapter and a pinned shared core. Projects without shared opt-in start off. Installing or reinstalling can activate projects with retained or inherited opt-in, including opt-in set by the sibling adapter. No default-on recommendation or accuracy gain is claimed.

Full Replacement per user turn. Explicit read-back within a turn. An accepted Working Context edit produces a static revision and digest notice with a command for the retained core CLI. The notice contains no editable content. Use --framed and read every part with the same digest. A later continuation of the same user turn can receive the edit as ordinary tool output. This addition retains earlier history and does not guarantee a smaller request. Full Replacement per user turn remains at the existing user-turn or compaction boundary.

The native host places tool-hook context in a system message. Context Engine therefore uses it only for validated metadata and fixed read instructions, never Working Context bytes. An edit does not automatically load the full file into the immediate next model call. The adapter preserves the original tool result and reference. It waits for the root tool batch to finish, then gives each accepted edit revision one notice. Initialization, ordinary events and unchanged revisions produce no edit notice.

The validated packet must fit 32,000 UTF-8 bytes including its carrier and the Working Context budget. Refused or oversized packets are never truncated. A notice or completed file read is not proof of request delivery.

Later compaction is a separate boundary. The existing guard stands aside before recording when the native tail contains Bash and the Working Context revision changed since the previous replacement. The CLI read uses Bash. A successful within-turn read does not count as replacement or clear this guard. Do not infer later automatic replacement from a successful read-back test.

Give this prompt to your agent to set it up

Copy the following prompt, replacing the project placeholder before sending it:

Set up Context Engine for Claude Code from
https://github.com/Jeff-Kazzee/context-engine-claude
for this project: <absolute project path>.
Select the exact commit approved for this installation trial. Verify its CI,
independent source review and owner authorization before installation. Do not
infer approval from a branch name or reuse evidence for a different commit. Read the CURRENT README.md, AGENTS.md, PROVENANCE.md, SOURCE.json and
adapters/claude/README.md before executing anything. Verify the actual OS,
Node version, claude version and available plugin/mod commands against those
docs and their CLI help. The inherited compatibility baseline is Claude Code 2.1.289 (mods early access);
do not silently install/downgrade a runner or assume another version works.
If access, licensing or runtime support is blocked, stop and report the reason.
Explain config changes and hook trust, then use normal approval controls.
Never read/copy authentication files, tokens or raw private captures. Use the
runner's existing login without inspecting it; no API-key or proxy fallback.
Use a fresh disposable test project FIRST. Install only this runner's plugin,
leave per-step/stale-refs experiments off, enable only the test project, and
run context-engine-claude status there. An installed plugin or a file write is not proof.
Using non-sensitive sentinels, prove a Working Context edit changes what the
NEXT REQUEST contains at the documented delivery boundary. If request-level
evidence is unavailable, explicitly report that acceptance as unverified;
do not expand logging/permissions or claim success. Do not run costly evals.
After the test passes and I approve the target-project scope, enable only the
named project and report exact mode, versions, commands, config/backups changed,
evidence, gaps and disable/uninstall steps. Preserve unrelated work.

The CI workflow checks the exact PR source commit on three hosts with Node 24.21.0. Its platform coverage is:

HostMaintained coverageStateful support
LinuxCore, setup and adapter suitesSupported with the documented Linux requirements
WindowsPure operations and refusal before state or setup mutationUnsupported
macOSPure operations and refusal before state or setup mutationUnsupported

Actual Windows and macOS jobs passed on 2026-10-08 in the initial CI run at af9b638f3c9725b1b0d7246df6f35949d0fcf351. These baseline results do not validate later commits. Each updated PR head requires its own successful CI. Offline contract checks do not prove provider delivery, model behavior or interactive acceptance.

1. Check compatibility before installing

The shared core separates host operations through core/platform-contract.ts. Its Linux backend inspects actual handles and targets, reports ownership and private-access facts, and owns private creation and permission changes. Context parsing, rendering and token estimates remain usable without selecting a host backend. A future backend must preserve directory confinement, exclusive lock publication, process identity, private access and durability before stateful use. Extracting this contract does not implement Windows or macOS adapters or establish live delivery acceptance.

Shared core 0.1.5 uses the full SHA-256 of the canonical project path for private state and participation keys. Legacy 0.1.0 directories and backups are preserved, but are not automatically migrated or merged. Uninstall an old installation using its original checkout/CLI first; retain its backups and session data. After coordinated runner updates, re-enable a fresh disposable project and start a fresh session. Do not mix 0.1.0 with 0.1.5 under a shared CONTEXT_ENGINE_STATE_DIR; use a separate, consistently configured state root for a trial. Existing legacy data remains available through the old checkout with its old state root. Valid full-digest keys from 0.1.1 remain unchanged in 0.1.2, 0.1.3 and 0.1.5.

  • Linux with /proc mounted and Node 24 or newer is the supported stateful target. Native Windows and macOS have no verified host backend. Stateful storage, locking, install and uninstall refuse with CE_UNSUPPORTED_PLATFORM before mutation. Use the original supported Linux environment and checkout to remove a prior installation. Do not relax confinement checks or sandbox permissions.
  • Working Context publication requires hard links on the project filesystem. A bounded private probe refuses unsupported filesystems before the materializing commit writes its snapshot or HEAD. Preserved predecessor artifacts require an inode-bound private-state marker. An older artifact or a kill between link creation and marker publication is retained and refused. Keep the original checkout for older artifacts, and review the exact artifact and marker before repair. Storage repair alone does not authenticate an unknown backup.

Internal probe and retirement paths belong to the core after exclusive creation, including their directory names in the managed parent. Mode 0700 excludes other OS users, but another process running as the same OS user can still replace those paths. Active same-user replacement of these internal paths is unsupported. Rename, unlink and directory removal there remain pathname operations, with no atomic inode-conditional deletion guarantee. Preexisting name collisions refuse before adoption or overwrite. Working Context edits and unrelated public backup names retain the documented preservation rules and final-sample limits.

  • Inherited compatibility baseline: Claude Code 2.1.289 (mods early access). These are observed baseline versions, not a guarantee that every machine or newer version works. Inspect claude --version, claude --help and its plugin help first.
  • A custom CONTEXT_ENGINE_STATE_DIR must be an absolute path, identical in the setup shell and runner launch environment. Relative state roots are refused rather than resolved differently for each project or hook cwd.
  • Setup now requires each ledger's backup copies to remain inside that ledger's own snapshot. Older project-repair ledgers that reference sibling snapshots are refused. Before updating an existing installation, uninstall with its original checkout and preserve its backups; do not edit a refused ledger to bypass this check.
  • Use a dedicated state directory owned by your user with private permissions (0700). Existing shared or linked state directories are refused without changing their permissions; never point the override at /tmp itself or a shared mount root.
  • Existing normal runner login. Setup never opens runner authentication files or uses their credentials. It reads tracked configuration and refuses recognized credential-bearing keys before making backup copies. This is not a universal secret detector: secrets under innocuous keys or in arbitrary text may escape detection. Never inspect ~/.claude/.credentials.json or ~/.codex/auth.json; use normal runner authentication if needed.
  • Persistent install changes runner configuration through its own plugin commands. Review changes and backups first; ordinary tool permissions and user approvals remain in control.
node --version
claude --version
claude plugin --help

2. Install this adapter and try a disposable project

These commands change configuration. Run them only after the preflight above, in a supported environment where you approve plugin installation. Keep the checkout at its installed path: hook commands refer to it. If the sibling adapter is already installed, first choose shared or isolated participation as described below; do not run enable until that scope is approved.

git clone https://github.com/Jeff-Kazzee/context-engine-claude.git
cd context-engine-claude
# Replace APPROVED_COMMIT with the exact reviewed commit approved for this trial.
git checkout --detach APPROVED_COMMIT
git rev-parse HEAD
npm ci
npm link
context-engine-claude install
# Replace this path with a NEW disposable project, not the repo checkout.
mkdir -p /tmp/context-engine-claude-trial
cd /tmp/context-engine-claude-trial
context-engine-claude enable
context-engine-claude status

No runner binary is installed by these commands. install defaults to Claude Code only and refuses the other runner flag. enable covers the selected directory and its subdirectories. This repository does not install or change Codex configuration. Shared participation can activate an already-installed sibling adapter; check the scope below before enabling.

Both distributions vendor the same core. They retain context-engine for compatible session read/recall/show commands. Linking the second checkout replaces that generic PATH alias; always use context-engine-claude for install, enable, status, disable and uninstall. The runtime aliases remain distinct. Default state/participation storage is shared; if both plugins are installed, enabling a project may activate both. Use a consistent, separate CONTEXT_ENGINE_STATE_DIR in each runner's launch environment and matching setup shell when you require separate participation. Never mix core versions under a shared generic alias.

Start a new Claude session after enable/disable. Persistent install registers the main mod and interactive companion trigger. Headless -p/SDK hosts must send /compact between turns. Leave CONTEXT_ENGINE_CLAUDE_MODE=per-step unset for the first trial.

Uninstall retains shared project participation as well as session data. Reinstalling can therefore activate a previously enabled project without another enable, including subdirectories inheriting that opt-in. Run context-engine-claude status in each intended project before starting a new session. To revoke participation, run context-engine-claude disable for that scope before uninstalling; this also stops the sibling adapter's hooks in the same shared scope. Run context-engine-codex disable there as well to restore Codex project settings. Uninstall alone removes Claude's installation without revoking the sibling's participation. Use consistently separate state roots when you require independent participation. Context Engine for Codex differs: for a project enabled through Codex, its uninstall writes a project retirement record, so reinstalling it leaves that project inactive until an explicit enable. These configuration checks do not prove live hook loading or delivery.

The companion now requires the main adapter's composed section and a fresh read-only setup-status check of scoped participation plus both installed/enabled plugins before requesting compaction. This guard does not establish host action-to-hook integration or real request-level delivery. status reports current configuration, and explicitly leaves live hook loading unverified. A pre-existing .context-engine/.gitignore must be a regular, unlinked file whose last effective rule is *; otherwise startup refuses without changing that file. Review and fix that rule locally before retrying.

The visible session frame key is not authentication. Multiple keyed frames, or a keyed frame later in the conversation, make the adapter record nothing and stand aside; start a fresh session. This conservative rule and persistent host delivery still need runtime acceptance. Text resembling <system-reminder> is retained because API text alone cannot prove runner authorship. On a post-open core fault, scheduled plugin compaction is skipped; explicit manual and automatic runner compactions retain native fallback.

3. Prove delivery, then enable your intended project

Use harmless, unique strings such as CE_OLD_TEST and CE_NEW_TEST. Preserve the current human request and the runner instructions, tools and permissions. Test the intended delivery mode explicitly.

For within-turn read-back, first establish a committed Working Context, then edit it while the same user turn continues. The immediate next native request must contain only the static revision-and-digest notice in hook context. It must not contain editable file bytes in a system or developer message. Run the notice's digest-bound CLI command with --framed and read every part with the same digest. Read exactly the payload byte count declared in each versioned frame, then check its fixed footer. Body text that resembles a footer is still payload. Inspect the following native request for the complete edited file in ordinary tool output. Earlier native history remains.

An unchanged write and an ordinary tool call must not add another edit notice.

For Full Replacement per user turn, cross the existing user-turn or compaction boundary. Verify that the replacement frame contains the new Working Context at user-message authority and removed history no longer occupies the rebuilt input. Headless hosts must send /compact between turns.

Inspect only authorized, scoped request evidence from a disposable project. A changed file, active status, prepared packet, hook return or model statement does not establish delivery. If safe request inspection is unavailable, report delivery as unverified and keep the test scope.

After you accept the result, move to the intended project and run context-engine-claude enable, then context-engine-claude status. Preserve the original task, user control and ordinary approval boundaries. Editable context is user data and can retain prompt injections; it does not gain system/developer authority.

Useful agent instructions in an enabled session:

Keep the current request, verified decisions and next step in your Working
Context. Preserve source pointers when offloading large tool outputs. After a
reset, read the current file completely and continue without repeating done work.

From that project, replace placeholders with the actual session and event IDs:

context-engine read --session <session-id>
context-engine recall --session <session-id> decision
context-engine show --session <session-id> <event-id>

read names every next part when a file needs paging. It refuses files above 16 MiB before loading their payload; offload large content with source pointers instead of relying on unbounded paging. Invalid UTF-8 is refused by reads and citations. recall and show are limited to the caller's project, with bounded output. Recall may report accounting: skipped if the Event Log cannot be written in a sandbox; that path is unit-tested, not established by a real sandbox session.

4. Roll back or uninstall

# Run in the project enabled above:
context-engine-claude disable
CONTEXT_ENGINE=off claude
# Removes this runner's adapter; retains session data:
context-engine-claude uninstall
# Run in its checkout only when removing its PATH links:
npm unlink --global context-engine-claude

Claude enable/disable and kill-switch changes apply to new sessions; Codex checks participation at each hook. Backups precede config writes. Configuration with recognized credential-bearing keys is refused before any before/after backup copies. Conservative TOML inspection also refuses escaped keys, inline tables and multiline nonstring forms; keep secrets in normal runner authentication rather than tracked configuration. Failed installation reverses known Context Engine configuration fields while preserving concurrent unmanaged edits, and removes newly created Context Engine namespaces. Unknown changed files are retained with their before-backups rather than overwritten. New files outside those namespaces may belong to concurrent work, so they are retained and reported on failure and uninstall rather than deleted. Their contents are not copied into the ledger. Unchanged configuration is restored byte for byte; if other tools changed it, uninstall removes only Context Engine entries and reports the backup location. If a required before backup is lost, uninstall skips native runner removal and uses direct ledger cleanup. It removes only values matching the installation writes, keeps other managed values and containers, and reports them. Native-only effects remain unverified. An originally absent file needs no before copy and retains the normal native uninstall path. Review partial failures before retrying. Unlinking the CLI alone does not uninstall plugin configuration.

Working Contexts live in <project>/.context-engine/<session>/, with managed directories private (0700) and the file private (0600). Existing user-owned managed directories/files are tightened on open; unsafe links, credentials and shared state roots are refused. Setup also refuses nonprivate state roots before changing runner configuration. If an interrupted runner append committed a revision before writing the file, recovery delivers that revision and preserves an intervening stale-file edit in the Event Log with a restore notice. Revisions, the Event Log, participation and install backups live under $XDG_STATE_HOME/context-engine (default ~/.local/state/context-engine; CONTEXT_ENGINE_STATE_DIR overrides). Runner homes honor CLAUDE_CONFIG_DIR/CODEX_HOME. Uninstall retains those session records. Deletion from Working Context only removes future model input; prior text remains in runner transcripts and the Event Log. Delete retained data only with your own exact-path approval.

Restore notices created by this version carry unique durable intent IDs. A restore intent is written before file publication, so replay says restoration was requested and requires a reread. It does not claim that the following rename completed. The rejected edit is retained separately. Sync and unmaterialized runner-append recovery both reoffer pending notices even when the file already matches HEAD, without rewriting it just to replay a notice. Older ID-less restored rows remain readable, but their historical lost notices and delivery outcomes are not reconstructed.

Each new restore attempt has a distinct notice ID, including identical rejected bytes at one revision. Confirmation of an earlier attempt cannot clear a later one. Local samples in one materialization are deduplicated. An ambiguous crash retry can create another attempt and retain another rejected-edit row. Retention in that case is at least once, not exactly once. Invalid-sync recovery of an already-current file needs no new restore attempt. An unmaterialized append can still recover its preserved predecessor and create another independently confirmable intent, even when the public file already matches HEAD.

Each result represents at most 64 pending IDs and reports the total pending count. Model-facing output uses one bounded static summary. The remaining IDs stay durable and advance after successful confirmation of the represented batch. CLI stdout confirms its own transport by default. Adapters defer that confirmation and confirm only carried IDs against the offered session generation after their transport succeeds. Acknowledgment validates the durable HEAD/log boundary and IDs without syncing, materializing, repairing the ignore file, or rewriting Working Context. Unknown IDs or a stale owner generation refuse before confirmation. A repeated confirmed ID is harmless. The disposable recovery checkpoint moves from version 4 to 5 to cache pending intents. Older checkpoints rebuild from the Event Log. HEAD and historical log records are not migrated.

Claude per-step confirms after successful sendStep transport. Compact and tool-return carriers have no corroborating host callback in this patch. Their represented batch remains pending and can repeat across later calls and restarts. Later notices remain queued. A visible frame key does not prove retention. Delivery is at least once under retry. No exactly-once display or model ingestion guarantee is made. Offline synthetic controls do not establish installed host acceptance.

Working Context predecessor capture now samples the same verified descriptor up to four times after publication and during preserved-artifact recovery. Another sample is taken only when changed-byte logging completed durably. Stable or locally deduplicated bytes stop the loop. A logging failure keeps the verified artifact and marker and refuses completion. This can capture a suffix completed during earlier logging, but it cannot establish writer com

Source 1 files
hooks/register.ts 77 lines
1// Context Engine trigger: compacts at each user-turn boundary of an interactive session, so the
2// context-engine mod can answer that compaction with the Working Context (Full Replacement per user
3// turn; Injection within a turn).
4//
5// Why a plugin of its own: a plugin's own `$.session.compact` skips that plugin's `session.compact`
6// hook as re-entry, and Claude Code would run its native summarizer instead. Why `turn.complete` +
7// `$.clock.after(0)`: Claude Code refuses a compaction from `prompt.submit` (it would compact under
8// the turn being held). Headless sessions (-p, SDK) refuse `$.session.compact` altogether; there
9// the host sends `/compact` between turns, which the mod answers the same way.
10import type { EngineInterface, Register } from 'claude-code';
11
12let interactive = false;
13let coreCliOption: unknown;
14let composeInput: Parameters<EngineInterface['prompt']['compose']>[0] | undefined;
15let generation = 0;
16
17async function compactNow($: EngineInterface, scheduled: number, input: NonNullable<typeof composeInput>): Promise<void> {
18  const current = () => interactive && scheduled === generation;
19  try {
20    // Positive handshake with the main adapter; a trigger installed on its own stays inert.
21    if (!current()) return;
22    const composed = await $.prompt.compose(input);
23    if (!current()) return;
24    if (!composed.sections.some(s => s.id === 'context-engine:working-context')) return;
25    const projectRoot = await $.session.root();
26    if (!current()) return;
27    // Recheck participation at callback time, even if the main adapter has cached its open state.
28    const segments: string[] = [];
29    for (const part of `${$.plugin.root}/../../../core/cli.ts`.split('/')) {
30      if (part === '..') segments.pop();
31      else if (part && part !== '.') segments.push(part);
32    }
33    const cli = coreCliOption === undefined || coreCliOption === 'checkout-relative' ? `/${segments.join('/')}` : coreCliOption;
34    if (typeof cli !== 'string' || !cli.startsWith('/') || /[\u0000-\u001F\u007F]/.test(cli)
35        || cli.includes('//') || cli.split('/').some(part => part === '.' || part === '..') || !cli.endsWith('/core/cli.ts')) {
36      throw new Error('coreCli must name the canonical absolute checkout/core/cli.ts file; reinstall Context Engine from its retained checkout.');
37    }
38    const checked = await $.process.run(['node', cli, 'status', '--project', projectRoot, '--json'], { cwd: projectRoot, timeoutMs: 60000 });
39    if (!current()) return;
40    if (checked.exitCode !== 0) throw new Error(`configured core CLI could not be loaded: ${cli}`);
41    if (JSON.parse(checked.stdout).claude?.active !== true) return;
42    await $.session.compact({});
43  } catch (err: unknown) {
44    $.ui.log(`Context Engine trigger: compaction refused: ${err instanceof Error ? err.message : String(err)}`, { to: 'debug' });
45  }
46}
47
48export const register: Register = (on, options = {}) => {
49  coreCliOption = options.coreCli;
50  on('session.start', async ($, e, next) => {
51    generation++;
52    interactive = e.isInteractive;
53    composeInput = undefined;
54    return next(e);
55  });
56
57  on('session.end', async (_$, e, next) => {
58    generation++;
59    interactive = false;
60    composeInput = undefined;
61    return next(e);
62  });
63
64  on('prompt.compose', async (_$, e, next) => {
65    composeInput = e;
66    return next(e);
67  });
68
69  on('turn.complete', async ($, e, next) => {
70    const scheduled = generation;
71    const r = await next(e);
72    const input = composeInput;
73    if (interactive && scheduled === generation && input && !e.agentId) $.clock.after(0, () => compactNow($, scheduled, input));
74    return r;
75  });
76};
77