auto-context-handover
Measured soft-limit handover plugin for OpenCode V2 (verified against 2.1.4).
Also includes a Pi root-session adapter (0.73.1, manual command rollover), a Codex child adapter and a Claude child adapter, plus a Crush root adapter and an AiderDesk root adapter and a Cline CLI/SDK root adapter, plus an Amp focused-root adapter, a Goose root adapter, a Kilo root adapter and a Qwen child adapter, plus an OpenHands SDK adapter (not a CLI plugin).
The session's own model writes the handover; a linked fresh session continues it automatically, or lock mode stops without a successor. Subagents return only a summary to the parent.
The plugin only measures, warns, hard-gates, and rotates.
compaction.auto stays on and untouched. Activation
is runtime only: the plugin registers hooks and tools on host load, no model
calls. The bundled TUI companion follows successful root rollovers when you
are still viewing the old session. It never switches you away from another
session or shows review/confirmation UI. Desktop and web do not auto-follow.
Install (local directory)
{
"$schema": "https://opencode.ai/config.json",
"plugins": [
{ "package": "/absolute/path/to/auto-context-handover", "options": {
"soft": 150000
} }
]
}
Run npm install once in this directory (runtime deps for the plugin host).
No build step: the host resolves the root index.ts shim and loads source/
directly. Node >= 22.18 is required for the .ts tests.
The root tui.ts companion loads automatically from the same plugins entry;
no separate install, cli.json entry, or additional option is required.
Local source changes are watched for automatic plugin reload. Keep related
edits coherent: a failed import can leave the previous generation active.
In one live check, a stale missing-export failure persisted across saves until
a clean host restart; inspect load errors before accepting live test evidence.
Config reference
| Key | Default | Meaning |
|---|---|---|
soft | 150000 | Warn threshold: positive integer tokens or a percentage string. No ratios. Integers resolve as min(value, floor(0.7 * window)); percentages use their requested percentage without the cap. Precedence: child prompt marker > agent override > "provider/model" > bare "model" > this value, resolved against the request's model only. |
hard | unset | Opt-in hard gate, same formats. Past it, every tool except context_handover is removed for that request. |
modelOverrides | {} | e.g. {"anthropic/claude-opus-4-6": "60%", "gpt-5": 90000}; validated independently. |
agentOverrides | {} | e.g. {"build": "80%"} (highest configured precedence); validated independently. |
stages | [0, 0.1, 0.2] | Nudge boundaries relative to soft: warn at soft, then soft*(1+offset). Must be 3 ascending offsets starting at 0; stage texts are fixed. |
onHandover.primary | "new-session" | "new-session" starts a linked successor; "lock" stops with no tools. Other values (including removed actions, reset or the old flat string) are setup errors listing the supported actions. |
onHandover.subagent | "return" | Only "return" is supported. Anything else is a setup error. |
handoverDescription | built-in | Overrides the context_handover tool description template wholesale. |
Full example:
{
"$schema": "https://opencode.ai/config.json",
"plugins": [
{ "package": "/absolute/path/to/auto-context-handover", "options": {
"soft": 150000,
"hard": "95%",
"modelOverrides": { "anthropic/claude-opus-4-6": "60%" },
"agentOverrides": { "build": "80%" },
"stages": [0, 0.1, 0.2],
"onHandover": { "primary": "new-session", "subagent": "return" }
} }
]
}
Low-threshold testing: { "soft": 2000, "hard": 4000 } forces warn/hard
quickly. Thresholds resolve against the model catalog window (limit.input
preferred, else limit.context). Integer soft values resolve as
min(value, floor(0.7 * window)), so the default is 150000 tokens or 70% of
the window, whichever is smaller. The same cap applies to configured integers;
explicit percentages remain uncapped. Hard limits are uncapped and take priority
when reached, even if below soft. With an unknown window, integer soft still
applies, percentage soft fails open, and hard gating is disabled. All-zero
provider usage counts as unknown; without usable usage there is no nudge or gate.
Per-child test override
Start a new subagent's spawn prompt with a namespaced marker line:
[auto-context-handover-test soft=4096]
Complete the bounded task below...
soft accepts the same capped integer tokens or uncapped percentage strings as
configuration, with the same unknown-window behavior.
The whitespace-separated key=value syntax reserves room for future options;
only soft is implemented. Unknown keys, duplicate keys, malformed pairs and
invalid values reject the initial child prompt with an explicit error.
The plugin recognizes only the beginning of a real child's initial prompt,
allowing OpenCode's standard subagent banner. It awaits session-keyed storage
before removing the marker from admitted text. Root prompts, later prompts,
quoted material and tool results are not scanned. The override outranks all
configured soft values without changing hard, nudges or child return behavior.
It needs no configuration edit or restart, survives reload and compaction,
and is deleted with the child. Descendants and successors do not inherit it.
The first request still needs usable assistant usage before it can be nudged:
give tests at least two model turns, for example by requiring successive tool
calls before completion.
Behavior
- Measure the latest assistant's normalized input, cache read/write, output, and reasoning once, plus trailing text at chars/4, including settled tool results. No usage anchor means no warning or hard gate.
- Append a stable, per-request stage nudge to the END of messages, not the
initial system prompt. The model finishes its unit and calls
context_handover({handover, summary}); text is stored verbatim. - Primary rollover creates a ROOT with the active agent/model and original
directory. Readiness, links, and source lock precede successor prompting.
The seed carries the full handover and backlink; the old session records
continued in session <id>. The TUI follows only if it is still showing the source when the successful rollover arrives and after successor lookup. - Initial child continuation uses reference seeding: the parent reads the full
handover, then spawns a same-agent-type successor with a prompt instructing
it to call
read_handoverwith the predecessor session ID and continue from "Prioritized next steps"; the handover is never pasted into the spawn prompt. Both treat the original task as reference context: completed steps are done and must not be repeated; predecessor facts are trusted unless marked unverified, without relabelling their evidence as the successor's direct verification. Targeted re-reads of already-covered material may resolve open questions, not repeat completed steps. Continue from "Prioritized next steps", independent of section numbering. - Lock mode creates no successor. A child handover replaces its required
final report for that session; before storage, read-only task instructions
still allow the handover tool because it writes only plugin storage. The
tool result tells the child to end its turn now with exactly the following
continuation text as its final message and then stop. The native parent
receives the child's latest completed assistant text, not the tool result.
The reply carries the short summary, child session ID, and exact
read_handovercall plus the reference-seeding successor instruction. A successor continues and replaces its predecessor as the same logical subagent, not an additional subagent under subagent-count limits. Ask only about handover-listed user-owned decisions, except checking in is allowed if progress stalls or remaining work grows. The child stop-lock refuses later questions or work explicitly, with the exact reader call and a successor/fresh-subagent continuation hint, not the old summary. Claude nativeSubagentHandbackdelivery is unchanged. - The default handover template requires plain disclosure of deviations, triggered stop conditions, failed calls and unverified claims. Unresolved deviations/stops preclude PASS or completion; summaries must not overclaim. Keep open questions explicitly. Do not author your own agent/model/chain provenance: the plugin records it; its record takes precedence over prose.
- EXPERIMENTAL loop guard: three consecutive successors adding <25% of soft beyond their seed lock instead of rotating. Productive handovers reset the streak; the first root handover never counts. Seed >50% of soft only warns.
Kilo (root-session adapter)
Targets Kilo CLI 7.8.23 (@kilocode/cli) and the current CLI-backed
Kilo Code VS Code extension 7.8.23 plugin contract. CLI loading/registration
is observed; extension loading and model compliance are unverified. Legacy
Roo-derived Kilo extension releases are not supported. The OpenCode V2 root
entry does not load on Kilo's different API; use kilo/index.ts instead.
Run npm install in this intact checkout, then merge into project kilo.json
(or the Kilo user config). No build, separate MCP server or marketplace install:
{
"$schema": "https://app.kilo.ai/config.json",
"plugin": [
["file:///absolute/path/to/auto-context-handover/kilo/index.ts", { "soft": 150000 }]
]
}
-
Keep the checkout and dependencies together; do not copy
kilo/alone or install the repository root as a Kilo plugin.KILO_PURE=1disables external plugins. Kilo's key is singularplugin, unlike OpenCode V2plugins. -
Root sessions only: host tool context supplies session/agent identity;
session.getmust confirm a matching parentless session before writing. Children cannot write; any equipped caller may read any retained id. -
Latest ordinary assistant's normalized input/cache/output/reasoning plus trailing text and settled tool outputs at chars/4 estimates the active compaction-filtered history. This is not cumulative billing or exact tokens; later host overhead/media/pruning changes are not modeled. Summary-worker usage and pre-summary retained-tail anchors are excluded; after compaction measurement waits for fresh ordinary usage. Unknowns produce no notice.
-
softdefaults to 150000; integers cap at 70% of the request model's catalog context window. Percentage strings use their explicit percentage. Unknown window: integers uncapped, percentages silent. The tuple option overridesAUTO_CONTEXT_HANDOVER_SOFT; invalid values disable notices, not tools. -
Intro text is added to the first root user message per plugin registration; a reload can introduce it again. Stable transient tail notices escalate at soft, soft x 1.1 and soft x 1.2. Native compaction is unchanged and may happen first; an observation-only hook skips its separate summarizer transform.
-
The model calls
context_handover({handover, summary})and ends its turn. No automatic rollover, source lock, hard gate or UI following. Open a fresh equipped root, not a resumed history, and submit:Call read_handover({"sessionID":"SOURCE_ID"}) and continue from "Prioritized next steps". -
Full verbatim text returns once with shared no-repeat/evidence framing. Handover-only chars/4 above 50% of recorded soft warns/advises compression, never rewrites or gates. No original-task capture, child lineage or model-override preservation promise. Diagnostics record notice append, not model receipt; transient loss after reload stays unknown.
-
Immutable hashed-id records live under
~/.kilo/auto-context-handover/. New directories/files use 0700/0600; first-writer atomic publication is not fsync durability or a same-user sandbox. No quota/TTL/eviction/deletion cleanup; retention is manual and interrupted writes can leave temporary files.
Capabilities, pinned citations and non-model checks. Actual hook dispatch, model compliance and manual fresh-root continuation remain unverified. The host's older Zod preserves required string schemas but drops per-argument descriptions; the complete tool descriptions remain intact.
Goose (root-session adapter)
Targets Goose v1.56.0. Requires Node >=22.18 and npm install in this
intact checkout; no build. Install only the Goose subdirectory, not the
repository root (its manifests serve other harnesses). For project scope:
mkdir -p .agents/plugins
ln -s /absolute/path/to/auto-context-handover/goose .agents/plugins/auto-context-handover
For user scope use ~/.agents/plugins/auto-context-handover instead; with
GOOSE_PATH_ROOT, use its .agents/plugins/ directory. Do not rename the
plugin directory or copy goose/ alone: shared imports resolve through the
checkout. Goose discovers plugin.json, .mcp.json and hooks/hooks.json.
Do not install the whole repository using goose plugin install.
Stop denials are opt-in, not passive nudges. To enable measured notices, set these in the environment that launches Goose (including its CLI binary on PATH). The host-wide cap also affects other Stop hooks:
export AUTO_CONTEXT_HANDOVER_GOOSE_STOP=1
export AUTO_CONTEXT_HANDOVER_SOFT=150000
export GOOSE_STOP_HOOK_BLOCK_CAP=1
# Optional if goose is not on PATH:
export AUTO_CONTEXT_HANDOVER_GOOSE_BIN=/absolute/path/to/goose
-
Root
usersessions only. The writer binds the host's per-session MCP process identity and call metadata, then verifies a matching root via native JSON session export. Children/unknown attribution cannot write. Any equipped host-bound caller, including children, may read any retained id. -
Stopreads the last persisted provider usage, never cumulative billing. Fixed stages at soft, soft x 1.1 and soft x 1.2. Integer-only soft defaults to 150000, uncapped (window unknown). Invalid configuration logs an error and disables notices, not storage/readback. Unknown usage/export failures allow stopping. Export is bounded to 15 seconds/16 MiB. -
A denial forces another model turn; it is hidden from the user but visible to the model. The host limits consecutive blocks (default 8; setup above chooses 1). No PreToolUse gate or per-request injection. SessionStart's banner is user-visible, not model-visible. Native compaction is unchanged and may happen first.
-
The model calls
auto-context-handover_auto-context-handover__context_handover({handover, summary})and ends its turn. Storage is immutable; no source lock, automatic successor or UI following. Manually open a fresh equipped root, not resume/duplicate history, and submit:Call auto-context-handover_auto-context-handover__read_handover({"sessionID":"SOURCE_ID"}) and continue from "Prioritized next steps". -
Full text returns once with shared no-repeat/evidence framing; payload chars/4 above 50% of recorded soft warns only. No original-task capture, lineage or model-override preservation promise. Diagnostics reason remains unknown (current stage does not prove notice delivery).
-
Private hashed-id records live under
$XDG_DATA_HOME/auto-context-handover-goose, else~/.local/share/auto-context-handover-goose; override with absoluteAUTO_CONTEXT_HANDOVER_GOOSE_DATA. New directories/files are 0700/0600. No quota/TTL/eviction/deletion cleanup; retention is manual. Atomic publication is not fsync durability or a same-user sandbox; crash-leftover temps may remain.
Capabilities, citations and isolated non-model evidence. Actual Stop dispatch, model compliance and fresh-root continuation remain unverified; executable checks and fixtures do not establish them.
Amp (focused-root adapter)
Targets @ampcode/plugin@0.0.0-20261028101742-g93a0da1; offline load checked
with Amp CLI 0.0.1793203246-g5eab6d. Amp runs TypeScript directly; no build
or runtime package import is needed. Keep this checkout intact (shared imports
are outside amp/). Create this real forwarding file in
$XDG_CONFIG_HOME/amp/plugins/auto-context-handover/index.ts, or
~/.config/amp/plugins/auto-context-handover/index.ts when XDG is unset:
export { default } from "/absolute/path/to/auto-context-handover/amp/index.ts";
Use the plugins: reload palette command after changing it; amp plugins list
lists registered plugins when connected. Do not copy only amp/index.ts.
- Focused parentless threads only. Host thread handles bind writes; known children, background threads and unknown focus/parentage cannot hand over. Deleted parents can appear parentless; this is not universal child detection.
agent.startappends an introduction and any notice after user content;tool.resultappends notices to supported string/text-block outputs without changing their status. No system-priority claim. Active inference-view text, thinking, tool inputs/results use chars/4, never compacted-away history. System prompt, tool schemas, deferred modules and image tokens are excluded: this can undercount and is not a mathematical lower bound or exact usage.AUTO_CONTEXT_HANDOVER_SOFTaccepts positive integer tokens only, default150000, uncapped (window unknown). Set below the model's compaction budget before launching Amp. Invalid/empty values log and disable notices, not tools. Shared stages apply at soft, soft x 1.1 and soft x 1.2; unknowns fail open.- The model calls
context_handover({handover, summary})after a notice or explicit handover request, then ends its turn. First successful write wins; both strings remain verbatim. At a successfulagent.end, while still focused and parentless, the source agent creates a fresh root withshow:trueand receives one full seed automatically. No model is selected/overridden; exact setting preservation and client UI following remain unverified. - Storage:
~/.amp/auto-context-handover/<sha256(thread-id)>/, following HOME rather than XDG. New directories/files 0700/0600; immutable handover/claim, separate best-effort continuation status. No quota/TTL/eviction/deletion cleanup or fsync guarantee; same-user files are not a sandbox. - Reload, cancellation, focus changes or failures can require manual recovery:
in an equipped fresh thread use
Call read_handover({"threadID":"SOURCE_ID"}) and continue from "Prioritized next steps".Inspect recorded status/existing successors first: uncertain create/seed admission is never automatically retried. A claim prevents duplicate attempts. Any equipped caller can read any retained id. Oversized seeds warn only. - No source lock, forced stop, hard gate or child workflow. Native compaction is unchanged and may precede a notice. Offline load and fixtures do not prove authenticated routing, model compliance or continuation. Capabilities and evidence.
Cline CLI/SDK (root adapter)
Targets official cline@3.0.74 and @cline/sdk@0.2.0, not legacy clite.
Install this repository as a package, not just cline/index.ts (which imports
shared files). Node >=22.18; no build step. From this repository run npm install,
then:
npm install -g cline@3.0.74
cline plugin install /absolute/path/to/auto-context-handover --cwd /absolute/path/to/project
This installs a copy: reinstall after code changes; start a new session to load
it. Omit --cwd for global installation. For an existing SDK application, add
the absolute cline/index.ts path to config.pluginPaths on cline.start,
keeping its existing model/runtime configuration. Direct extensions users
must call createClineHandoverPlugin() once per session, never reuse an instance.
- Root-only, advisory/manual.
beforeRunintroduces the workflow;beforeModelappends transient staged notices without rewriting the system prefix. The complete exposed request's serialized chars/4 is a heuristic, not provider usage. No window is exposed:AUTO_CONTEXT_HANDOVER_SOFTaccepts positive integer tokens only, default150000, uncapped. Set it below the configured model's compaction budget before starting Cline. Invalid/empty explicit values log an error and disable notices, not tools. - The session's model calls
context_handover({handover, summary})after a notice or explicit handover request, not ordinary completion, then ends its turn. Both strings stay verbatim; first successful storage wins, no overwrite. Root host identity/snapshot is required; unsupported callers fail explicitly. - Continue manually: create a fresh session with this plugin and prompt it:
Call read_handover({"sessionID":"SOURCE_ID"}) and continue from "Prioritized next steps".Reader guidance preserves completed work and predecessor evidence; no pasted full seed is needed. Any equipped caller can read any retained id. Full text appears once with source identity and diagnostics; missing observations stay unknown. Handover-only seed >50% of recorded soft warns, never gates/rewrites. - Storage:
~/.cline/auto-context-handover/<sha256(session-id)>/handover.json, new directories/files 0700/0600. This path follows HOME, not host--data-diror XDG overrides. No quota/TTL/eviction/session-deletion cleanup; retention is manual. Same-user files are not a sandbox; interrupted writes may leave temps, and atomic publication is not fsync durability. - No automatic creation/UI switching, source lock, hard gate, stop interception, model/override preservation or child integration. Native compaction is unchanged and can run before measurement. Real model compliance/continuation remain unverified. Capabilities and non-model evidence.
Pi (root-session adapter)
Pi package @mariozechner/pi-coding-agent@0.73.1 loads pi/index.ts directly,
using the root package.json pi.extensions manifest. No build or OpenCode
process is needed. From this repository, run npm install, then:
pi install /absolute/path/to/auto-context-handover
pi --handover-soft 150000
For a single launch without package installation:
pi -e /absolute/path/to/auto-context-handover/pi/index.ts --handover-soft '70%'
- Root-oriented only: no integration with third-party Pi subagent extensions. Pi has no core subagent primitive. Tools register directly, with no loader.
- A
before_agent_startintroduction explains the installed workflow before each loop. Pi persists this custom message per user prompt; it is not a system-prefix rewrite or a system-priority guarantee. Every provider-requestcontexthook appends a transient stage notice at the end when over soft. --handover-softaccepts positive integer tokens or percentage strings. Default150000; integers cap at 70% of the host context window, percentages are uncapped. Fixed stages: soft, soft x 1.1, soft x 1.2. Unknown tokens suppress notices; unknown windows permit integers but not percentages. Invalid values report a session-start error and disable advisories, rather than silently substituting a threshold. Measurement uses Pi's own estimator.- The active model calls
context_handover({handover, summary})after a notice or an explicit user request, not ordinary task completion. Both strings are stored verbatim as a session custom entry.read_handover({})returns the latest active-branch handover plus diagnostics and host source-session/model metadata; it cannot read arbitrary files or other sessions. Reload and native compaction do not delete these entries. Ephemeral sessions store only in memory. - Rollover needs a user command: after storage, submit
/handoverwith no arguments. It waits for idle, creates a fresh session with a source backlink, seeds the full handover once and automatically sends a next-step continuation prompt using the replacement context. The command does not ask for review. Without stored text it reports an error and makes no model call. Cancellation preserves the source; partial replacement failures require inspecting the current session and recovering from the source's stored handover. - No automatic tool/event-driven rollover, lock, hard gate, original-task capture, child lineage or loop guard. No model selection is forced; Pi owns replacement model resolution. Native compaction stays enabled and unchanged. A seed estimate above 50% of soft warns only. Pi owns file retention; there is no adapter quota/eviction policy, and writes are not crash-atomic.
- Real Pi installation/load/registration is non-model-verified; 28 fake-API tests cover behavior. Model compliance and end-to-end rollover are unverified.
Pinned contracts, command-dispatch caveat, checklist and reproducible non-model checks: Pi assessment.
Codex CLI (subagent-first adapter)
Targets @openai/codex@0.165.0. Run npm install in this repository first;
Node >=22.18 is required. No build or OpenCode host is involved. Register the
existing repository marketplace and install the root plugin:
npx --yes @openai/codex@0.165.0 plugin marketplace add /absolute/path/to/auto-context-handover
npx --yes @openai/codex@0.165.0 plugin add auto-context-handover@auto-context-handover
npx --yes @openai/codex@0.165.0 mcp get auto_context_handover
Review and trust the installed hooks in Codex /hooks before expecting them to
run. Installation alone does not trust them. Code/definition changes can require
renewed hash review; do not bypass trust as an installation shortcut. Codex uses
an installed copy, not this working tree. Complete dependency installation before
trust review; if a distribution omitted node_modules, install dependencies in
that installed plugin copy as well.
- Pinned-source caveat: measurement depends on synchronous
PostModelRequest, present inrust-v0.165.0but not in the public hook event list. It observes latest-response input + output (cache/reasoning are subsets), then appends shared staged notices at supported tool boundaries. It does not poll JSONL or claim a pre-request injection/gate. Unknown usage suppresses notices; compaction clears measurements without changing native compaction. - Set
AUTO_CONTEXT_HANDOVER_SOFTin the environment before launching Codex. It accepts positive integer tokens, default150000, with fixed stages at soft, soft x 1.1 and soft x 1.2. No percentage/cap: the child window is unknown. Invalid values error and make hook-bound tools unavailable, not silently default. - Root SessionStart introduces the configured read-then-replace workflow;
roots can read but cannot store handovers and are not measured/nudged/rotated.
Thread-spawn children receive a start introduction. After a notice, finish
the current step and call
mcp__auto_context_handover__context_handover({handover,summary}), then end the turn with exactly its receipt as the final native message. The stored handover replaces that session's final report; plugin-storage writes remain permitted under read-only task instructions before handover. - Parent readback:
mcp__auto_context_handover__read_handover({"childId":"CHILD_ID"})returns compact diagnostics, host-observed provenance and next-step excerpt. Usefull:truefor the verbatim handover once; pending user decisions are unknown until full read. Pass the reader's short full-read prompt to the same-type successor, not the full handover. The parent retains its original task and requested model-override parameters; the adapter does not capture them or infer overrides from the effective model. No original-task capture or lineage. - Calls are bound to host identity by a synchronous PreToolUse single-use capability, not a model-supplied caller id. Unbound calls fail if hooks are absent/untrusted/disabled. Any live bound caller may read any stored child id; closed callers cannot read or work. This is not an OS-user filesystem sandbox.
- Verbatim text/summary and closure persist together in private PLUGIN_DATA files. No quota, TTL or automatic eviction. Consumed capabilities are removed; abandoned pending requests and crash-leftover temporary files can remain. Deleting plugin data also deletes history and closure. Atomic publication is not an fsync/crash-durability guarantee.
- No root rollover, automatic successor spawn, hard token gate or classifier handback. Closure denial covers supported tool paths only; final text and one stop correction are advisory, not deterministic parent delivery. Installation, MCP registration and fixtures are verified without a model; hook execution, model compliance and end-to-end continuation remain unverified.
Primary-source capability table, requirements checklist and reproducible isolated commands: Codex assessment.
Qwen Code (subagent-first adapter)
Targets Qwen Code 0.26.3. Run npm install in this repository first;
Node >=22.18 is required. No build or OpenCode host is involved. Link the
bundled qwen/ subdirectory as an extension (it imports shared files from
the intact checkout, so link the directory, not a copy):
npm install --global @qwen-code/qwen-code@0.26.3
qwen extensions link /absolute/path/to/auto-context-handover/qwen
Restart Qwen Code afterwards. The manifest (qwen/qwen-extension.json)
registers the auto_context_handover MCP server (node --experimental-strip-types ${extensionPath}/server.ts, so the model calls
mcp__auto_context_handover__context_handover /
mcp__auto_context_handover__read_handover); qwen/hooks/hooks.json
registers classic command hooks (${CLAUDE_PLUGIN_ROOT}/hook.ts), not Mods.
Workspace trust, hook/MCP settings and tool allowlists still apply.
- Child-only measurement from the child's own transcript. The hook
input's
transcript_pathis always the root transcript (<projectDir>/chats/<sessionId>.jsonl); the child transcript is derived as<projectDir>/subagents/<sessionId>/agent-<agentId>.jsonl(only strict[A-Za-z0-9_-]ids; otherwise unknown). The latest assistant record matchingsessionId+agentIdand carryingagentRunId+agentRoundsupplies the estimate aspromptTokenCount + candidatesTokenCountonly: never billing totals, cache, or thoughts. Invalid latest usage resolves to unknown with no fallback to older records. The root transcript is never parsed as a child. Child keys are the opaque compositesessionId:agentId, used for storage and as the reader'schildId. - Set
AUTO_CONTEXT_HANDOVER_SOFTin the environment before launching Qwen. Positive integer tokens only, default150000, uncapped (the child window is unknown); fixed stages at soft, soft x 1.1 and soft x 1.2. An invalid value disables staged notices but leaves reader/writer identity binding working; readback reports unknown soft/stage/estimate, not the default. Unknown usage suppresses notices. Pre/PostCompact checkpoints the child file length and measures only later records; native compaction is unchanged. - Root SessionStart introduces the configured read-then-replace workflow;
roots can read but cannot store and are not measured/nudged.
Subagent-spawn children receive a start introduction. After a notice,
finish the current step and call
mcp__auto_context_handover__context_handover({handover,summary}), then end the turn with exactly its receipt as the final native message. The stored handover replaces that session's final report; plugin-storage writes remain permitted under read-only task instructions before handover. - Parent readback:
mcp__auto_context_handover__read_handover({"childId":"SESSION:AGENT"})returns compact diagnostics, host-observed provenance and next-step excerpt; usefull:truefor the verbatim handover once. Pass the reader's short successor prompt to a same-type successor, never the full handover. The parent retains its original task and requested model-override parameters; the adapter does not capture them or infer overrides. No original-task capture, lineage, model selection, automatic successor spawn or root rollover. - Calls are bound to host identity by a synchronous PreToolUse single-use capability, not a model-supplied caller id. Both tools refuse unbound calls; roots cannot store. Any live bound caller may read any stored child id; closed callers cannot read or work. This is not an OS-user filesystem sandbox.
- Verbatim text/summary and closure persist together in immutable hashed-id
files under
~/.qwen/auto-context-handover/. No quota, TTL or automatic eviction. Consumed capabilities are removed; abandoned pending requests and crash-leftover temporary files can remain. Atomic publication is not an fsync/crash-durability guarantee. - SubagentStop applies one bounded final-text correction only after a stored handover, never a measurement denial. No Stop telemetry. Installation, MCP registration and fixtures are verified without a model; hook execution, model compliance and end-to-end continuation remain unverified.
Source contracts, storage/measurement limitations and non-model evidence: Qwen assessment.
Claude Code (subagent-only adapter)
The same repository root ships a Claude Code mod (hooks/, loaded via
hooks/hooks.json; manifest .claude-plugin/plugin.json, marketplace
.claude-plugin/marketplace.json). Install the repository root as the
plugin/marketplace source; no OpenCode host is involved.
-
Only subagent child loops are measured (latest per-request
turn.stepusage, never cumulative, never the root window) and nudged per batch with staged escalation at soft, soft1.1, soft1.2, reusing the OpenCode warn leads. Aclassic.SubagentStartintroduction explains the installed plugin before the child's first prompt; later notices appear next to tool results after a batch. Each stage references the introduction and retains the exactmcp__auto-context-handover__context_handovertool,ToolSearch select:loader, current-step boundary, full handover/handback action and intact parent readback. The introduction carries schema/template and read-only plugin-storage guidance. It is skipped only for positively identified teammates; unknown ids or unavailable listings still receive it. Teammates remain excluded from nudges and refused by the handover tool. -
Roots receive a short context-only introduction through
classic.SessionStartadditionalContext, before the first prompt rather than inside a tool result (placement contract). It establishes reading incomplete child handovers and continuing without confirmation as the user's configured default. A successor replaces its predecessor as the same logical subagent, not an additional subagent under subagent-count limits. Read the handover before replying or deciding; ask only about handover-listed user-owned decisions, never context size, except check in if progress stalls or remaining work grows across generations. There is no fixed generation cap: parent judgment stays in charge. If a child ends without a delivered report, read its handover before messaging or resuming it; an existing handover is incomplete work to continue in a successor. Never SendMessage to or resume a handed-over child. Pass the reader's short successor prompt verbatim; append parent redirection if needed, but do not paste the original task or full handover into the spawn. Startup/resume/clear/compact/fork context is supported; custom-agent roots qualify by absentagent_id. Roots are not measured, nudged, gated or rolled over and cannot hand over. Tools register session-wide; parent readback remains available. Native compaction is unchanged. -
soft(plugin userConfig, default150000): positive integer tokens only, unbounded (the child window is unknown). No percentages, caps, hard gates, loop guards, or per-child markers. -
The child calls
context_handoveronce, then must callSubagentHandbackand stop. The tool result addresses the child first and retains the short reply below; the adapter forwards that stored reply as the handback message through the native path without replacing its result or bypassing review. Host receipt and classifier outcomes are not independently observed. Plain text is a last resort only when the handback tool is absent: on the reported native path, unsent final text was not shown to the parent. The parent reads a compact view viaread_handoverwith thechildIdfrom the short reply. The notice tells the child to load the handover tool first (the deferred description is initially invisible) and then call it with the full text. Child summaries must includeUser decisions pending: noneorUser decisions pending: <list>. Default readback contains diagnostics, plugin-recorded provenance, generation/chain, next-step count, seed size, pending user decisions, the "Prioritized next steps" excerpt and successor pointer. Missing pending decisions or unsupported next-step formats are labelledunknown, with afull:truereader hint rather than invented claims. Requestread_handover({"childId":"CHILD_ID","full":true})for the observed original task and handover verbatim, once each, with reference-only/no-repeat/ predecessor-trust framing and native final-report guidance. The final short successor prompt starts with a lineage marker and explicitly requests full text, including when the child reads before spawn metadata is saved:[auto-context-handover:continue child:CHILD_ID] First call mcp__auto-context-handover__read_handover({"childId":"CHILD_ID","full":true}) to load the full handover and the original task, then continue from 'Prioritized next steps'.The marker is recognized only at the beginning of the observed spawn prompt, allowing leading whitespace. Quoted/mid-text examples are not scanned. Malformed reserved prefixes and missing predecessor handovers produce unknown lineage, never a silently reset original task. This marker is unrelated to the OpenCode per-child soft-limit test marker.
-
The successor loads the payload through
read_handover, not a largeSubagentStartinjection. Recorded successor callers also receive full text without the flag when their merged spawn/handover metadata identifies this target as the immediate predecessor. That link comes from the leading marker observed byagent.spawn; the pointer's explicitfull:truehandles the pre-persistence race. Existing open-reader access permits any live caller with tool access to read any stored handover by id, not only a parent or successor; there is no caller authorization or new read grant. Locked callers still cannot read or work. The short prompt itself contains the reader instruction, even if the generic start introduction is absent. Tool-output limits/placement remain host integration risks; this route avoids parent retranscription and does not claim system-prompt priority. -
Capture the original from the documented
AgentSpawnInput.prompt, not parent memory or inferred task text. Store it once under the origin child id; successors retain only that reference and their immediate predecessor id. Generation starts at 1 and increments from a stored predecessor's metadata. Readback shows generation, predecessor and origin ids, plus a top-level next-step item count when the title/list format is derivable, otherwiseunknown. These are progress cues, not a proof of productivity. Post-spawn metadata remains recoverable if the child already handed over before the spawn result returned; no stored handover is rewritten. -
The shared
seedTooLargecheck warns when the successor-facing payload estimate (ceil(chars/4), including framing but not reader headers) exceeds 50% of the current Claude soft limit. Ask the child to compress carried-over finished notes at its next handover, never rewrite stored text. This is advisory and excludes host/system/tool overhead; it does not gate work. -
Replies/readback record the agent type and requested model override from
agent.spawn, withSubagentStarttype fallback, and prescribe the same type/override for the successor. Observed absence isno override; unavailable or legacy provenance isunknown, use predecessor spawn params. Never infer an override from usage or the effective/root model. Metadata persists across reload in private storage; storage failures can lose provenance. It records parameters as this observer saw them, not downstream mod rewrites. The root introduction makes this plugin record authoritative over conflicting handover prose; the shared template tells children not to restate their own agent/model/chain provenance. -
Resumed locked children return an explicit refusal and continuation hint, including through native handback. Same-turn initial delivery retains the short summary; missing or legacy turn identity selects the locked reply. A closed child without an observed successful handback in the current turn receives at most one stop correction to call
SubagentHandback, even when its final text matches the short/locked reply. Existing stop blocks and active stop hooks are respected; errors fail open. Official tool documentation establishes native delivery/classification, not a universal plain-final-drop guarantee; the bounded correction conservatively addresses the live report. -
Full text persists in plugin storage (4 MiB JSON total, shared across sessions). Before payload/provenance writes, estimate the whole serialized store with the proposed replacement. Above 3 MiB, evict oldest eligible whole chains by last activity until it fits. No TTL or configuration; the remaining 1 MiB is estimate headroom, not reserved capacity. Reads and continuations refresh activity. Legacy/untimestamped records sort oldest; spawn-only orphan metadata participates in the same activity ordering. Legacy groups and groups with no timestamp form the oldest tier; within each tier, activity sorts ascending with chain-id ties. Protect the current chain, active/in-flight chains and uncertain membership rather than guessing lineage. The legacy/orphan exception means no lineage claims at all; malformed/conflicting claims, unresolved references and unreadable membership remain protected, including identifiable related chains. Unknown keys count toward size but are not reclaimed. Expired bodies/originals are permanently unavailable; compact closure tombstones keep handed-over children locked and reads explicitly report
expired. Insufficient safe space and failed/partial eviction remain explicit and retryable; failed handover storage never claims success or locks the child. Tombstones consume space too. Concurrent sessions can over-evict; store accounting and reclamation are not atomic. Eviction scans run on writes, not reads. The estimate sums UTF-8 bytes of JSON-escaped keys and serialized values, including object braces, colons and commas, after replacing the pending key's old value. It counts all enumerated key families, not just handovers; host-internal representation can differ. Incomplete scans refuse the pending persistence rather than treating unreadable entries as zero. New child records store the model's summary once and derive the short reply; historical records with persisted short replies remain readable. Active protection combines local in-flight reservations, nonterminal listings and persisted child pins. Only documentedcompleted,failedandkilledstatuses release listing protection; unknown statuses retain it. A matchingturn.completeretires the pin;session.endbest-effort removes only that session's pins, never payloads. Crashes or failed cleanup can leave stale pins indefinitely, reducing safely reclaimable space. Original-task write failures preserve a bounded error in spawn metadata when possible and report the cause in full readback; if all writes fail, reload can lose that cause and readback reports unknown. Handover storage failures are explicit errors, not successful handovers. Native output limits are unverified integration risks, as are tool availability under real allowlists, child feedback routing, classifier review of short returns, resumptions, and installed-package loading. Delivery guidance is advisory shaping, not a deterministic replacement. -
Per-child usage, baseline, growth, spawn-observation and nudge caches retire on closed-child stop or matching turn completion. Handback/correction latches survive a blocked stop and retire only at actual turn completion or session end. A genuinely new open turn re-enables measurement and re-pins before its first request; a lost baseline remains
unknown, not a fabricated restart. Auxiliary retired/provenance/error/seen-turn histories are capped at 1000 child ids, with at most 10 remembered turn ids per child. Session end clears transient measurement and correction state; durable locks remain. -
hooks/claude-types.tsis a narrow structural subset of the mod contracts, not full API verification, and carries no live version claim.
Adapter developers: docs/adapters.md lists the harness-independent requirements checklist with per-adapter status and evidence. Antigravity CLI feasibility is assessed docs-only (blocked, no implementation) in docs/research/antigravity-cli.md. AWS Kiro CLI 2.35.1 is also assessed, not shipped: hook-readable active-context measurement and MCP caller binding remain unestablished. No Kiro adapter installation is available.
OpenHands SDK adapter
Targets openhands-sdk==1.21.0, tested with Python3.12. This is a small
Python composition helper for a local SDK application, not a drop-in plugin
for openhands CLI1.16.0, its TUI/ACP or a remote agent server. No build or
replacement driver loop. Capabilities and CLI blockers.
python -m pip install -r openhands/requirements.txt
export PYTHONPATH="$PWD/openhands${PYTHONPATH:+:$PYTHONPATH}"
In your SDK application, equip a fresh uninitialized agent configured normally
with its own LLM/metrics object; keep the adapter object alive for that conversation:
from openhands.sdk import Conversation
from openhands.sdk.hooks import HookConfig
from handover_adapter import HandoverAdapter
adapter = HandoverAdapter()
conversation = Conversation(
agent=adapter.equip(agent),
workspace=workspace,
callbacks=[*existing_callbacks, adapter.on_event],
hook_config=HookConfig.merge([existing_hooks, adapter.hook_config]),
)
adapter.bind(conversation)
existing_callbacks and existing_hooks are your application's existing list
and HookConfig (use [] and HookConfig() if absent). Bind once before any
send/run; use normal SDK send/run calls afterwards. Keep handover_adapter.py
at its import location: the hook invokes that file with the current interpreter.
- Latest main-agent response prompt + completion drives shared stages, only on the next submitted user prompt. New prompt/tool-result growth is omitted; no notice inside autonomous runs. Condensation invalidates old usage until a new ordinary response; native condenser stays enabled/unchanged and can run first.
AUTO_CONTEXT_HANDOVER_SOFTorHandoverAdapter(soft=...): default150000, positive integer tokens capped at70% of a known response context_window; explicit percentages uncapped. Unknown window disables percentages; unknown usage is silent. Invalid/empty explicit values log and disable notices, not tools.- Direct
context_handover({handover,summary})stores model-authored strings verbatim;read_handover({sessionID})returns full text once and continuation framing. Both require their exact SDK-bound caller; any equipped caller may read any retained source id. No automatic root/child classification or delegation. - Manual continuation: create a fresh, independently equipped local Agent/Conversation (new id, not resume/fork) and submit the source reader reference. No automatic rollover/UI following, source lock, Stop interception, token gate or model/settings-preservation promise.
- HOME-based
~/.openhands/auto-context-handover/<sha256(session-id)>/uses private new directories/files and immutable first-writer handover publication. No quota/TTL/eviction/deletion cleanup/fsync guarantee; snapshot/temp files may remain. Same-user filesystem access is not a sandbox. Reloaded diagnostics begin unknown; emitted-nudge provenance survives later measurements/condensation, but does not prove model receipt. Failed snapshot writes log, keep fresh memory diagnostics and attempt stale-file removal; failed removal can leave stale notices. PYTHONPATH=openhands python openhands/test_handover_adapter.pychecks the pinned SDK without model execution. Node tests guard shared Python text drift. Real model compliance and continuation remain unverified.
GitHub Copilot CLI (subagent-first adapter)
Targets @github/copilot@1.0.105 experimental CLI extensions and Node >=22.18.
Install the standalone Copilot directory, not the repository root (which exposes
the Codex MCP server):
copilot plugin install ./copilot
copilot plugin list --json
copilot --experimental
From GitHub, use copilot plugin install beefsack/auto-context-handover:copilot.
Direct installs currently warn of future marketplace-only support. Use
/extensions manage to inspect extension startup; restart or /clear after
updates. No build or runtime SDK installation: Copilot supplies its SDK.
- Child-only measurement uses the latest attributed whole-window count
(including system messages and tool definitions), never billing totals.
AUTO_CONTEXT_HANDOVER_SOFTdefaults to150000, capped at 70% of a known window; explicit percentages such as70%use that fraction. Unknown/invalid usage stays silent. Staged notices appear after successful tools. - Both tools set
defer: "never"to keep their descriptions available.context_handoverstores model-authored text verbatim and closes the child;read_handover({"childId":"..."})returns full text and diagnostics. Native subagent-stop rewriting supplies the short parent receipt without another model turn. Parent guidance says read first, then replace with a same-type, same-requested-override successor using the original spawn parameters and a reader reference, never pasted handover text. - Readback warns when the stored handover's
ceil(chars/4)estimate exceeds 50% of its recorded soft limit; unknown soft skips the warning. This excludes reader headers/host overhead and only advises compressing finished notes at the next handover; stored text stays verbatim. - Persistent files use a dedicated directory under
COPILOT_HOME(otherwise~/.copilot);AUTO_CONTEXT_HANDOVER_DATAoverrides it. Use a trusted dedicated directory; directory/file modes are 0700/0600, not a same-user sandbox. No quota/TTL/eviction; transient usage is unknown after reload. Root reads are allowed, but roots are not nudged, handed over or rotated. Native compaction is unchanged. Ordinary hook closure reads fail open on storage errors, so corrupt/unreadable records can bypass hook denial; both direct adapter tools fail explicitly. - Experimental attribution must identify the child; absent attribution refuses handover. Built-in general-purpose lacks start/stop lifecycle support. No original-task capture, lineage or compact parent reader. Tool/extension permissions and disabled hooks can prevent the workflow. Installation is observed and policy fixture-tested; actual callbacks, model compliance, never-resume enforcement across all paths and parent receipt are unverified.
Capabilities and non-model checks.
Crush (root-session adapter)
Targets Crush v0.98.2 only. Run npm install here first; Node >=22.18,
no build. This is an explicit opt-in local-server integration, not a plugin
bundle: it can create a root and submit one continuation prompt on your behalf.
Use an absolute private Unix socket path for the server you intend to control;
the adapter never discovers a socket or opens TCP connections.
Set these variables before starting both the server and client:
export AUTO_CONTEXT_HANDOVER_ROOT="/absolute/path/to/auto-context-handover"
export AUTO_CONTEXT_HANDOVER_CRUSH_SOCKET="/absolute/private/run/crush.sock"
export AUTO_CONTEXT_HANDOVER_DATA="/absolute/private/auto-context-handover-data"
export AUTO_CONTEXT_HANDOVER_SOFT=150000
Create the socket's parent directory privately before starting the server. Use a dedicated adapter data directory, not a project or harness config directory.
Add to the project's crushrc (paths with spaces are supported):
option context-path "$AUTO_CONTEXT_HANDOVER_ROOT/crush/CONTEXT.md"
hook add PreToolUse --name auto-context-handover --timeout 30 \
--command 'node "$AUTO_CONTEXT_HANDOVER_ROOT/crush/hook.ts"'
mcp add auto_context_handover --type stdio --command node \
--args "$AUTO_CONTEXT_HANDOVER_ROOT/crush/server.ts" --timeout 60
Start a private server, then connect from a second terminal in the project with the same environment (Crush owns normal model/provider setup):
crush server --host "unix://$AUTO_CONTEXT_HANDOVER_CRUSH_SOCKET"
# In the second terminal:
crush --host "unix://$AUTO_CONTEXT_HANDOVER_CRUSH_SOCKET"
- Root tools:
mcp_auto_context_handover_context_handover({handover,summary})andmcp_auto_context_handover_read_handover({"sessionID":"SOURCE_ID"}). Static context introduces the workflow; stable staged PreToolUse notices reference it. Native children bypass hooks and are unsupported. - Measurement reads last-saved prompt + completion counters and workspace model window through the server, not session totals. Counters normally lag one response at tool boundaries and may retain older nonzero values. Estimated versus provider-measured usage provenance is unavailable through this API. No per-request injection or trailing-result estimate is claimed.
- Soft accepts positive integer tokens (default
150000, capped at 70% of a known window) or explicit percentages (uncapped). Unknown usage suppresses notices; unknown windows leave integers uncapped and percentages unresolved. Invalid configuration errors instead of silently selecting a default. - The active model supplies verbatim text. Storage and source closure precede root creation; successor id is persisted before one seed submission. The successor runs in the same workspace in the background. The TUI stays on the source. No model/variant preservation guarantee, child integration, hard token gate, original-task capture, loop guard or compaction changes.
- Any live hook-bound root can read any retained handover by id, including full diagnostics and recovery state; closed roots cannot use tools. Binding uses immutable original arguments and a single-use capability, not model-supplied identity. Same-user socket/filesystem trust is not a sandbox.
- Diagnostics preserve known estimate/stage/soft; reason remains
unknownbecause notice provenance is not persisted between hook processes. Unknown soft disables notices/seed warnings, not explicit handover or readback. - Private files use 0700 directories/0600 files; no quota, TTL or automatic eviction. Abandoned capabilities/temp files may remain. Deleting adapter data deletes history and closure. Publication is not an fsync durability guarantee. Ordinary-tool lock checks fail open on storage errors; MCP mutations are strict.
- Failed/uncertain creation or admission leaves the source closed with retained text. Inspect the successor/server before manual recovery; never blindly resubmit after a timeout. A crash between creation and link persistence can orphan an unrecorded successor. Admission is not proof of a successful model run. A seed estimate above 50% of soft warns only and never rewrites stored text.
- Release server reads/root creation, configuration loading and standalone MCP stdio listing are non-model checked. Fixtures are not real hook firing, model compliance, successful continuation or TUI navigation evidence; those remain unverified.
Pinned release/nightly capability table and reproducible non-model checks: Crush assessment.
AiderDesk (root Agent-task adapter)
Advisory/storage only; continuation is manual. Automatic rollover is disabled because fresh-task initialization is not established through the pinned public extension API. No end-to-end model run has been verified.
Targets AiderDesk 0.90.0-dev, source 831d10d, with shipped extension types
@aiderdesk/extensions@0.39.0. No build or OpenCode host. Run npm install
in this repository, keep it in place, and create a real project extension
directory with this index.ts (replace the absolute path):
<project>/.aider-desk/extensions/aiderdesk/index.ts
export { default } from "/absolute/path/to/auto-context-handover/aiderdesk/index.ts";
The same real aiderdesk/ folder can go under ~/.aider-desk/extensions/
for global installation; use one scope, not both. Do not symlink the folder:
the pinned host skips directory symlinks. Do not copy aiderdesk/ alone:
its shared imports require the repository. Restart AiderDesk after repository
updates; its extension watcher need not observe files outside the entry folder.
- Root Agent mode only; native subagents, compaction modes and Aider modes
are excluded. Model-visible tools are
context-handover({handover,summary})andread-handover({taskId?}); approval identifiers areaiderdesk---context-handoverandaiderdesk---read-handover. Normal host approval/disabled-tool policies still apply. - A per-run context introduction explains the installed workflow. Transient per-step notices use the host's whole-prompt token estimator, not billing totals or exact provider usage. It estimates system text, tool definitions and saved task messages; later extension changes may not be counted.
AUTO_CONTEXT_HANDOVER_SOFT, set before launching AiderDesk, accepts positive integer tokens or explicit percentages. Default150000; integers cap at 70% of a known model input window, percentages are uncapped. Fixed shared stages: soft, soft x 1.1, soft x 1.2. Zero/error estimates suppress notices; unknown windows leave integers uncapped and percentages unresolved. Invalid or empty explicit configuration logs an error and disables notices.- The active model writes the full handover through the tool, then ends its
turn. Create/open a fresh Agent-mode task through the normal AiderDesk UI in
the same project, then use the reader-reference prompt returned by the tool:
call
read-handoverwith the sourcetaskIdand continue from "Prioritized next steps". Keep the source task loaded and its working directory available. The adapter never creates or prompts another task, selects a model, transfers context files or records successor lineage. - Dedicated
<working-directory>/.auto-context-handover/<sha256(taskId)>/handover.jsonretains immutable verbatim handover/summary, first writer wins. The host'sgetTaskDir()is a shared project/worktree directory, not task-private storage; hashed task scopes prevent collisions. New directories/files use 0700/0600, not a same-user sandbox. No quota, TTL, automatic eviction or task-deletion cleanup; retained files need manual removal. Removing/changing a worktree can make its handovers unavailable. Atomic publication is not fsync durability; interrupted writes may leave temporary files. - Readback includes recorded diagnostics and full text once. Any root with these tools can read any loaded task in the current project through host lookup, never arbitrary paths; no parent-only authorization is claimed. Reload loses transient measurements, not stored text.
- No source lock, hard gate, child workflow or loop guard. Source reuse is not prevented. Native handoff/compaction remain unchanged and can still run; native handoff's separate generation call is not used to author this handover. No seed-size warning or automated continuation. Host-loader registration and 27 fake-API tests are non-model-verified; manual continuation, approvals and model compliance remain unverified.
Pinned capabilities and reproducible non-model checks.
Storage
OpenCode fallback B: everything lives in session-keyed KV, never in external files.
The host persists complete tool-call inputs (packages/schema/src/session-message.ts:148-154,
packages/core/src/session/sql.ts:84-96 at pinned 19d87a7), but server
plugins expose no full-history API and ctx.session.context is filtered
(packages/core/src/session/history.ts:149-179), so the full text cannot be recovered from the
transcript later. The handover body is therefore stored under
auto-context-handover:handover:<sessionID> (lock, loop state,
child soft override and forward/back links beside it) and removed with the session on session.deleted.
OpenCode's read_handover is a direct (codemode: false) tool returning the full
text with metadata: { truncated: false }, which bypasses the host's
tool-output truncation instead of spilling to a file. It returns only the
handover, not the old transcript. A full read can consume large context;
the soft-limit estimator sees the result after its anchor, which is
expected behavior, not a safety feature.
Any live caller with tool access can read any stored handover by session id;
the reader checks target existence and storage, not caller authorization.
Locked callers have no tools. Claude has the same open-reader policy but uses
childId and compact-by-default readback as described above.
Both adapters store call-time handover diagnostics and show one reader header:
Diagnostics: reason=nudged, stage=warn1, estimated-context-tokens=1050, soft-limit=1000
The reason is nudged or not-nudged, never inferred user intent. OpenCode
uses its pre-call request estimate (including trailing chars/4) and stage;
Claude derives stage from the child's latest per-request usage against soft
(the same boundaries as notices); reason reflects observed batch-nudge
appending independently, not proof the child read it. Claude readback also shows recorded successor
agent/model-override provenance and appends handback-delivered=yes|no|unknown
to the diagnostics line. New records start at no; a successful native
handback result (no denial/error) persists yes, including a locked reply
delivered after resume. This records observed tool success, not independent
parent receipt or classifier internals. Legacy status is unknown; a failed
status write can leave stale no even after delivery. The current-turn stop
check is in memory, so a reload can cause one extra correction. Unavailable
fields, including lost transient
state and older records, read as unknown. Diagnostics do not persist nudges
or change request text, thresholds, gating or rollover.
Claude also records the first measured request's context total after a newly observed child launch, and the latest consecutive request-to-request change at handover in a separate line:
Baseline: start-context-tokens=42000, last-batch-increase=6500.
These use the same per-request input/cache/output sum as context measurement,
not the task notification's cumulative tokens. A negative change is possible
after compaction. A start baseline requires a launch observed in the current
registration without previous child/spawn metadata (or a spawn observed here);
unobserved starts, metadata-read failures and reloads of existing children
report unknown, not the first later measurement as a fabricated start.
The last-batch delta needs two consecutive usable measurements, which may both
be after reload. Legacy/unavailable fields are unknown. They are
diagnostics only, not a new nudge point or overshoot control.
Manual test plan
Root-session checks run against an isolated project directory (never the live
config): create /tmp/opencode/ch-manual, put an opencode.jsonc with the
$schema property and this package in plugins, and start opencode there.
Use { "soft": 2000 } (or similar) so one short exchange crosses the
threshold; the model does the calling, you verify the outcomes.
Ask the user to choose the model explicitly for each live run, and pass it
as a required per-run argument or environment variable with no default.
Never hardcode or pick a model in test files, scripts, or configs. If the
chosen model is rejected, stop rather than substitute another model.
Scripts must reject both unset and empty model arguments/environment variables
before invoking OpenCode; an empty --model can otherwise select a default.
Give sessions explicit titles so automatic title generation cannot select
a different model for an auxiliary request.
Check a context observer's event.model against the requested model before
accepting evidence. Use a private server (--standalone) and an explicit
agent with access to the plugin tools so inherited settings do not obscure
the check.
For headless checks, require the chosen model in the invoking environment:
: "${LIVE_TEST_MODEL:?Set the model explicitly chosen for this run}"
opencode run --standalone --agent build --model "$LIVE_TEST_MODEL" --title "Handover check" --format json "What is 1234 + 5678? Reply exactly SUM-6912. Do not call any tools."
: "${SESSION_ID:?Set sessionID from the run's JSON events}"
opencode session export "$SESSION_ID" > session.json
Run these commands from the isolated project. Obtain SESSION_ID from the
run's JSON events; exported tool entries use .name, not .tool.
First verify ordinary completion without asking for a handover: no usage
anchor exists on the first request, so it should not nudge or hand over.
Then continue that session to cross the threshold. A context-hook observer
loaded after this plugin can independently record remaining tool names and
injected directives; a model's description of its tools alone is not proof.
-
New-session rotation (local directory config). Do: with
softlow, ask the agent to complete a small unit and then callcontext_handoverwith full text plus summary. Nudges are model-facing, not UI notifications. Expect: tool result names the successor ID; the source session refuses further work (tools stripped, locked notice); the successor opens with the handover seed plus backlink andread_handoverhint;read_handoverfor the source from any live session shows the verbatim text withcontinued in <successor>.TUI follow check: keep the source visible during rollover. Expect the TUI to switch to the successor without any prompt. Repeat, but navigate to another session before the source completes its handover; expect to stay there. Lock mode and child return must not switch sessions. Companion setup was verified in an isolated OpenCode 2.1.4 TUI without a model call; actual rollover navigation still needs this interactive check.
-
Lock mode. Do: set
onHandover: { "primary": "lock" }, cross soft, callcontext_handover. Expect: no successor is created; the result names the stored session ID and points atread_handover; the session locks and keeps no tools. Later work requests should refuse and direct the user to read that stored text from another session. -
Child return and parent read. Do: from the live parent session, launch an ordinary
workerwith a spawn prompt starting with[auto-context-handover-test soft=4096], adjusting soft for fixed prompt overhead. Use its configured model, without a model override or live configuration edits. Run typecheck first: the live host loads this working tree directly. Keep this to one or two spawns and require at least two model turns before completion, such as successive tool calls. The child must wait for the nudge, finish its bounded unit, and callcontext_handoverwith a short summary. Workers cannot spawn other agents. Expect: the child returns only the short summary plus its session ID and aread_handoverhint (never the full text); the child locks; the parent reads the full text viaread_handoverwith the child session ID. A live marker check confirmed stripping, the model-observed nudge, one handover call, the short child return and full parent readback. Reload, compaction, deletion, rejection and non-inheritance cases have unit coverage. -
Hard gate. Do: set
hardjust abovesoft, push usage past it, and ask the agent which tools remain before it hands over. Expect: every tool exceptcontext_handoveris removed for that request and a firm hard-limit directive is shown; callingcontext_handoverstill succeeds. -
Loop guard: root plus three short successions, then productive reset. Do: with
soft100000, do a short exchange and thencontext_handoverto rotate root -> s1 -> s2 -> s3, each successor adding only a sentence or two beyond its seed; then in a fresh chain, do enough work to add=25% of soft beyond the seed, followed by short handovers. The short exchange before each handover matters: without a measured pre-call snapshot the guard skips counting rather than guessing. Expect: s3 locks with "loop guard" and no new root; all tools are removed on later requests. Read its stored handover from another session. The productive chain resets the streak, then locks on the third short one. Counts are approximate, so do not expect exact manual outcomes. Keep transcripts short even with a larger soft threshold: fixed system/tool overhead counts as added tokens, so tiny thresholds can classify a sentence-only successor as productive. Explicitly request handovers below soft rather than filling the window just to get nudged. Separately, with
soft1000, a handover body over 2000 characters ensures the full seed exceeds 50% of soft; expect a warning, not a block. -
Forward/back links and read after compaction. Do: complete one rotation, then keep working in the successor until compaction runs, then call
read_handoverfor the source session. Expect: the source header showscontinued in <successor>and the verbatim text survives compaction. Both directions resolve only once the successor has stored its own handover; before that, the reverse link lives in the successor's seed prompt, andread_handoverfor the successor reports no handover stored. -
Deletion cleanup. Do: remove a handed-over session (
session.remove), then callread_handoverfor it. Expect: the read reports the session does not exist. KV cleanup (handover, lock, loop state, both links) is covered in the harness, not inspectable via the read tool. This includes child soft overrides.
Claude parent continuation retest
- Reload the plugin and start a fresh root session. Choose the child model explicitly and configure a low enough integer soft limit outside its task prompt. Ask for one sequential read-only subagent audit with no handover hints and enough work to require continuation.
- Check that the root introduction precedes its first prompt, the child
introduction/notices arrive, and the child stores a six-section handover
with pending user decisions in its summary. The parent must read it before
replying or deciding, then continue in a replacement with the same agent
type and model override without asking about context or subagent count.
Require at least two rollovers. Check every child calls
SubagentHandbackafter storage and every readback reports delivery status. Each parent spawn must contain only the reader-provided short prompt, plus optional redirection; its successor must read the predecessor before task work. Verify the original prompt and handover arrive verbatim, once each, without a memory-filled slot. The parent's default read must omit the full original/handover and include pending decisions, next steps and pointer; an explicitfull:trueread must recover both full texts. A successor's pointer-directed read must always be full, including before spawn metadata persistence. - Follow at least three generations: confirm generation increments, immediate predecessor/origin ids, inherited original prompt, title-based continuation when next steps are section 7, and no repeated completed work. Targeted re-reads may resolve carried open questions. Verify the parent notices stalled progress/growing remaining work without a fixed cap. Exercise a payload over 50% of soft and check compression advice; in fixtures verify malformed/missing pointers, spawn timing, reload and quota failures.
- Exercise a child ending without handback in an isolated fixture: the stop correction must request native handback once, not plain text. If no report arrives, the parent must read the child id first and continue any stored handover in a successor, never SendMessage to or resume the closed child. Check the absent-handback-tool fallback stops after the bounded correction.
- Inspect provenance and diagnostics against the spawn and latest usage; check first-measured context and last-batch delta independently of the task notification's token total, including honest unavailable values; confirm root measurement/nudging and native compaction are unchanged. Parent compliance and installed-host delivery still need live verification.
Limitations
- EXPERIMENTAL loop guard is approximate and in-memory parts (request identity, pre-call usage, duplicate guard, pending-successor map) reset on restart/reload; KV streaks survive. Missing measured pre-call snapshots skip counting (including immediate successor handovers). System/tool overhead and chars/4 bias productivity, especially at tiny thresholds.
- Non-text media is not char-counted.
- A process crash between successor creation and durable link writes can orphan the successor; the same-process pending map covers retries, but a retry after a crash may mint a second root. This is a known atomicity gap, not a transaction.
- The source lock persists: after an uncertain prompt admission the source
stays locked and refuses handover retries, so recovery is manual (the user
opens the successor and pastes via
read_handover). - TUI following uses live successful-rollover events only. Missed events (for example, while disconnected or before companion activation), failed successor lookups, and uncertain prompt admission do not redirect. Opening an old session later does not redirect either; use its forward link manually.
- Desktop and web do not auto-follow; there is no interactive review UI.
- No reset or interactive review action: unsupported values are explicit setup errors. Storage failures in the context hook still fail open for locks and measurement.
- Compaction can drop the visible estimate; the hook keeps a high-water mark so pre-compaction progress still counts at handover.
- Isolated OpenCode 2.1.4 live checks confirm lock mode, hard tool removal with a callable handover, linked rollover, default-variant preservation, the seed-size warning, and locking on the third unproductive successor. The restored loop-guard lock fallback has unit coverage but has not had a new isolated live run after removal of interactive review. Deletion cleanup is unit-tested, not live-tested. Nondefault variants, productive loop-streak reset, and readback after compaction were not exercised in these live runs.