- TypeScript 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
A session could reach the hard context limit instead of handing over when the agent did not write HANDOVER.md on the first instruction: the instruction forbade *all* tool calls while writing the file needs one, so the model emitted the document as chat text, and the plugin then waited silently while the host's own prompt (a goal continuation) restarted the work. The reserve was spent; the fallback compaction only fired at ~99.9%. - the stop instruction allows exactly the file-writing tool - while the document is missing the instruction is re-sent each step (limit: 3 instructions per cycle) - after that, or when the document no longer fits, one regular compaction is queued instead of dispatching doomed steps - tracker counts the instructions per cycle; README documents it Tests: 65/65, tsc --noEmit clean. |
||
| src | ||
| test | ||
| .gitignore | ||
| index.ts | ||
| LICENSE | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
oc-ctx-handover
An OpenCode plugin that replaces automatic context compaction with a deliberate handover: when a session approaches its context limit, the agent is stopped and made to write a handover document (HANDOVER.md) describing what it did, what is unfinished, and what to do next. The session context is then cropped and a fresh agent continues from the document.
Handover vs. regular compaction
Regular compaction waits until the context is nearly full, then asks the model for an automatic summary of the transcript and continues from that summary. It replaces the session's history with a lossy, machine-generated digest of everything — including all the dead ends — and you have no say in what it keeps.
This plugin moves the decision earlier and hands the instructions on how to continue to the agent:
| Regular compaction | oc-ctx-handover | |
|---|---|---|
| When | near the context limit | at 80% of the limit (configurable), or on demand via /handover |
| Who writes the summary | the model, as an extra summarization call | the agent itself, as a document you can read and edit |
| What survives | whatever the summarizer judged important | the current step, decisions made, next steps — and anything you name in /handover <note> |
| Cost | an extra model call to produce the summary | no extra context read at handover time: the model already knows what it needs to write |
| Afterwards | the session continues from a summary you never see | the same session continues from HANDOVER.md, which you can read, edit, and commit |
The session id never changes: the compaction happens in place, so the TUI keeps its focus and no empty session is left behind.
Install
No build step and no dependencies — OpenCode loads the TypeScript directly through the package-root index.ts.
-
Put the plugin somewhere stable:
git clone <this-repo> <local path>/oc-ctx-handover -
Add the directory to
pluginsin your global OpenCode config (~/.config/opencode/opencode.jsonc):{ "$schema": "https://opencode.ai/config.json", "plugins": [ "<local path>/oc-ctx-handover" ] }Point at the directory containing
index.ts, not at a file. Relative paths (./plugins/oc-ctx-handover) resolve against the config file. To enable it for a single project only, drop the directory in<project>/.opencode/plugins/oc-ctx-handover/instead — plugins there load automatically. -
Restart the background service so the plugin is loaded:
opencode service restart -
Verify it registered by checking its log:
tail -n 20 ~/.local/share/opencode/log/oc-ctx-handover.logOn a working install the log ends with lines like:
[oc-ctx-handover] ... session api: compact=ok prompt=ok context=ok [oc-ctx-handover] ... compaction hook registered [oc-ctx-handover] ... handover command registered [oc-ctx-handover] ... context hook registeredThe file is created by the plugin itself, so it will not exist until the plugin has run at least once.
-
That's it. Sessions now hand over at 80% of the model's context limit.
HANDOVER.mdis written to the session's project directory; add it to your.gitignoreunless you want it committed.
The plugin needs OpenCode V2 (ctx.session.hook, ctx.session.compact, ctx.command.transform). On a build without them it logs why and stays out of the way.
Usage
Automatic: nothing to do. When a step would start within 4,096 tokens of the 80% ceiling, or usage reaches the ceiling, the agent is told to stop and write the document.
The document step is capped at a document budget — 5% of the model's context limit, bounded to 4,096–32,768 tokens — and the instruction names that budget, so the model can size the document instead of being cut off mid-write. The only tool the instruction allows is the one that writes the file. While the document is missing the instruction is re-sent on each step (the host may add its own prompt in between, e.g. a goal continuation, and hook-appended messages are not stored in the session history), up to three instructions. If the document still is not written, or if less than 2,048 tokens remain before the hard limit, no document can fit: the plugin stops the step and queues a regular compaction (once per session) instead of dispatching a doomed write.
On demand: type the command at any context level.
/handover
/handover capture the migration plan and the two failing tests
The optional note becomes a requirement for the document. Either way the cycle is the same: the agent stops, writes HANDOVER.md, the session is compacted in place, and the next message tells the fresh agent to read the document and continue.
Configuration
All settings are environment variables, read from the environment of the OpenCode service at plugin load. Export them where the service starts, then opencode service restart.
| Variable | Default | Meaning |
|---|---|---|
OC_CTX_HANDOVER_THRESHOLD |
0.8 |
Fraction of the model's context limit at which the handover fires (0–1). |
OC_CTX_HANDOVER_HANDOFF_FILE |
HANDOVER.md |
Where the document goes. A relative path resolves against the session's project directory; an absolute path is used as-is. |
OC_CTX_HANDOVER_LOG_FILE |
~/.local/share/opencode/log/oc-ctx-handover.log |
Diagnostics log. |
OC_CTX_HANDOVER_DISABLED |
unset | 1, true or yes disables the plugin. |
An unset or unusable value falls back to the default rather than failing; the plugin never throws into a session.
Troubleshooting
Every step logs one usage line, so the log is the first place to look:
usage 132000/200000 tokens (66.0%) [measured] anchor=131000, threshold 80%, session ses_..., model provider/model
[measured]vs[estimated]—measuredmeans the provider-reported token count was available and only the growth since it was estimated.estimatedmeans the whole payload was estimated from characters; the plugin still works, just less precisely.- Nothing in the log — the plugin is not loaded: check that
pluginscontains the directory and not a file, then restart the service and look in~/.local/share/opencode/log/opencode.logfor load errors. disabled: set OC_CTX_HANDOVER_DISABLED=0— the kill switch is set to a truthy value; unset it.no ctx.session.hook on this build— the OpenCode build is too old (pre-V2); the plugin registers nothing.no ctx.command.transform on this build; /handover is not registered— the threshold handover still works, only the command is missing.
Development
node --test # test suite (node:test)
npx -p typescript tsc --noEmit # typecheck (fetches TypeScript; none is vendored)
index.ts is the OpenCode entrypoint and only re-exports src/index.ts, so the directory OpenCode loads is the directory you edit — a running service picks up changes on reload.