Replaces Opencode v2 context compaction with a file based handover scheme instead
  • TypeScript 100%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Troed Sangberg 5e8c31110c handover: re-send the instruction and bound the document step
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.
2026-10-23 13:44:58 +00:00
src handover: re-send the instruction and bound the document step 2026-10-23 13:44:58 +00:00
test handover: re-send the instruction and bound the document step 2026-10-23 13:44:58 +00:00
.gitignore oc-ctx-handover: OpenCode context-handover plugin 2026-10-19 19:29:53 +00:00
index.ts oc-ctx-handover: OpenCode context-handover plugin 2026-10-19 19:29:53 +00:00
LICENSE Adding README and LICENSE 2026-10-22 23:49:12 +02:00
package.json oc-ctx-handover: OpenCode context-handover plugin 2026-10-19 19:29:53 +00:00
README.md handover: re-send the instruction and bound the document step 2026-10-23 13:44:58 +00:00
tsconfig.json oc-ctx-handover: OpenCode context-handover plugin 2026-10-19 19:29:53 +00:00

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.

  1. Put the plugin somewhere stable:

    git clone <this-repo> <local path>/oc-ctx-handover
    
  2. Add the directory to plugins in 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.

  3. Restart the background service so the plugin is loaded:

    opencode service restart
    
  4. Verify it registered by checking its log:

    tail -n 20 ~/.local/share/opencode/log/oc-ctx-handover.log
    

    On 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 registered
    

    The file is created by the plugin itself, so it will not exist until the plugin has run at least once.

  5. That's it. Sessions now hand over at 80% of the model's context limit. HANDOVER.md is written to the session's project directory; add it to your .gitignore unless 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] — measured means the provider-reported token count was available and only the growth since it was estimated. estimated means 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 plugins contains the directory and not a file, then restart the service and look in ~/.local/share/opencode/log/opencode.log for 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.