Auto-compaction, auto-continue, auto-planning. Replaces OpenCode's default auto-compact/auto-continue lifecycle with one that creates a portable, persistent PLAN.md document. One persistent source of truth for your project that grows as the agent works autonomously. Continue using it in the current session or start a new session with it. Finally true "fire and forget".
This plugin was born out of frustration with OpenCode's limited auto-compact and summarize behavior. We didn't want to duplicate logic so we evaluated the v2 auto-compact and auto-continue and found them to still be lacking what we wanted.
Main complaints:
- Doesn't seem to honor
compaction.keeporcompaction.buffer - Both compaction token options are hard token counts, instead of percentages, so you have to constantly change them when using models with varying context sizes.
- Because it doesn't honor the limits, the 25% context tail is often lost when compacting.
- Also most likely related to running right up to the limit: the summaries are often missing info from the tail.
- You can't change the compaction message.
- The
continuemessage and previous prompt don't always seem to fire or present inconsistently. - You can't customize the compact or continue prompts.
- You can't customize the summary.
This plugin attempts to address all of those complaints:
- Instead of a compaction summary that isn't persisted anywhere but the session history, it will create an implementation plan/design doc (PLAN.md) in the working directory. Now you'll have a persistent, portable source of truth in your project that you can review and pass to other sessions.
- A token threshold that can be set as a percentage of the model's context size, rather than having to keep track of what hard numerical token limit you need for each model.
- The threshold is tracked live rather than after an assistant message is present. The live count catches reasoning and tool output tokens before they overshoot the limit. No more losing the tail or incomplete compaction summaries.
- True autonomy: when the threshold is reached the plugin will send a
planPromptinstructing the model to pause what it's doing, then it will compact with acompactPrompt, and resume working with aresumePrompt. There is also a last resortretryPromptthat will fire only once, if the PLAN.md was not written. - All prompts are full customizable, so if you need a detailed workflow or template for each phase of the auto-plan lifecycle you can define it.
There is also a useful side-effect that presented itself during testing and was not part of the intended purpose. As part of testing we set a low 60% threshold on a 64k context, which caused compacts to happen at 26k. One of our test runs was "Create a minecraft clone" with Qwen 3.8 27B , which seemed like it should just get stuck in an endless loop. It didn't. Instead it created a working clone in Vite after 9 auto-plan trips on just 26k context! So not only does this plugin fix OpenCode's auto-compact/auto-continue, it allows people with low VRAM to run dense models and complete long-form coding.
git clone https://github.com/krumpstead/opencode-neverending-story.git
bun install
bun allYou need to disable the auto-compact and pruning when installing the plugin.
The models.model.limit block is required:
- You must set the
contextandoutputfields to match the same limits set by the provider for your models.
The compaction block is required:
auto: false— OpenCode's built-in auto-compaction trips on an absolute token reserve (roughly the last 4–10% of the window) and runs the compaction turn with no tools, so it cannot writePLAN.md. If it stays enabled it will compact before or instead of the plugin's plan step.prune: false— history pruning would silently drop the context the plan turn needs.
The plugin never modifies your config. These blocks are something you need to add.
If you want to change the plugin defaults, set the JSON options in a nested pair: [plugin-name, options]:
{
"plugin": [
["opencode-neverending-story", { "threshold": 30, "planFile": "PLAN.md", "charsPerToken": 3, "summary": false }]
]
}Every prompt the plugin sends is overridable via the options below
(planPrompt, retryPrompt, resumePrompt, compactPrompt). The defaults
are shown in What the plugin sends.
| Option | Type | Default | Notes |
|---|---|---|---|
threshold |
number | 30 |
Percent of remaining context at which the plugin trips. A trip occurs when used reaches limit × (1 − threshold/100). The valid range is 0 < threshold < 100. There are two distinct trip paths: streaming — while a message is generating, the plugin estimates used as the last completed message's exact total plus the live output so far (streamed text/reasoning chars plus tool-call argument chars, divided by charsPerToken) and aborts the in-progress message (ESC×2 equivalent) when the estimate crosses; completion — the exact used of each finished assistant message (input + output + reasoning + cache.read + cache.write, the same figure the TUI displays) is checked as a backstop for input-side jumps the stream estimate can't see (a big tool output from the previous turn). |
charsPerToken |
number | 3.5 |
The characters-per-token ratio. The streaming estimate uses this to convert counted chars into tokens (estimate = baseline + chars / charsPerToken). Set it lower if you want the estimate to grow faster and trip earlier keeping t/s high. Set it higher if you want it to trip later, lengthening sessions. Tune it to your workload: 2–3 for code-heavy sessions (code/JSON packs more tokens per char than prose). 4–5 for prose-heavy sessions. 3.5 is a good middle ground. |
planFile |
string | "PLAN.md" |
The filename for the story. It's resolved against the session's working directory ($PWD). Substituted into the prompts when it's not PLAN.md. |
summary |
boolean | false |
Whether to keep OpenCode's default compaction summary. false (default) replaces the summarizer's prompt with a one-line pointer to the plan file (Compacted. See PLAN.md …), so the retained tail plus PLAN.md are the source of truth after a compact.true keeps the built-in summary and instead appends a note to it telling the summarizer to preserve PLAN.md's objective and outstanding next steps. Flip to true if you notice post-compact drift that the plan file alone can't cover. |
planPrompt |
string | shown below | Pre-compact prompt, sent when the threshold trips, telling the model to pause and persist its state to the plan file. A {planFile} placeholder is replaced with the configured planFile. Override to change the pause instructions. |
retryPrompt |
string | plan prompt + a "not updated" note | It's sent once if the plan file was not written after the plan turn. {planFile} placeholder. Defaults to the plan prompt plus the note shown below. |
resumePrompt |
string | "Resume ./PLAN.md" |
Post-compact prompt that restarts the work from the plan file. {planFile} placeholder. |
compactPrompt |
string | shown below | Replaces OpenCode's compaction summary prompt when summary is false. Must be an explicit single-line instruction (as shipped) — it's a bare statement that lets the summarizer emit the full built-in summary. {planFile} placeholder. |
The texts below are the defaults — each is overridable via the matching
option (planPrompt, retryPrompt, resumePrompt, compactPrompt), and they use
a {planFile} placeholder that is replaced with the configured
planFile option.
Pre-compact — the plan prompt (when the threshold is crossed):
You are running out of context, pause the current task for now and write everything you have learned into a PLAN.md in the
$PWD. DO NOT continue to analyze issues. Write out what you know and list what needs to be investigated. Document known next steps.
If PLAN.md was not written (the plugin checks the file's size/mtime), it
retries once with the same prompt plus:
Note: your previous response did not update PLAN.md in the
$PWD. Please write it now.
If it is still not written, the plugin compacts anyway and logs a warning.
Post-compact — the resume prompt (after compaction completes):
Resume
./PLAN.md
Compaction summary prompt (replaces the built-in summary when summary is
false; must stay a single-line instruction):
Respond with exactly the following single line and nothing else: Compacted. See PLAN.md in the working directory.
PLAN.md is a growing, living source of truth for the work, not a
one-shot handoff. The plugin creates nothing on disk — the model writes and
updates the file via its normal file tools, and it is expected to update it
in place (existing sections evolve; no new dated sections per cycle, which
would bloat the context it is supposed to shrink). After each compact + resume
the model reads the file back, so the objective, decisions, completed work,
and next steps survive the compaction. The plugin only checks the existennce of
the file, to detect whether the plan turn actually wrote it.
- Top-level sessions only. Subagent sessions (those with a
parentID) are ignored. Subagents share$PWDwith the parent, so a plan turn inside a subagent would clobber the parent'sPLAN.md. An over-full subagent fails loudly and the parent redispatches it. - The trip point is immediate. While a message is generating, the plugin estimates live context usage from streamed characters and, on crossing the threshold, aborts the in-progress message (the same call TUI ESC×2 makes) and sends the plan prompt on the resulting idle. This is so "pause the current task" lands mid-message instead of after the context is already full. The completion check remains as a backstop for input-side jumps (e.g. a huge tool output) that the stream estimate can't see.
- Compaction uses the session's current model The provider/model of the
last assistant message. The same call the TUI
/compactmakes. - The compaction summary is controlled by the plugin. Via the
experimental.session.compactinghook, whensummaryisfalse(default) the plugin replaces the summarizer's prompt with a one-line pointer toPLAN.md, so the post-compact context is the retained tail plusPLAN.md. Whensummaryistrueit instead appends a note to the built-in summary prompt telling the summarizer thatPLAN.mdis the cumulative plan and to preserve its objective and outstanding next steps. Note that stock OpenCode still runs the compaction turn (and unfortunately spends its input tokens). Either way — the plugin can reshape the prompt, and not skip the call. Skipping it would require a change to OpenCode itself. - This behavior mimics a real user so well in our tests, Qwen 3.8 27B doesn't realize it's a plugin creating the prompts.
- Logs go to
~/.local/share/opencode/log/opencode-neverending-story.log(info level: every trip, retry, compact, and resume; debug: per-message threshold checks). This uses the OpenCode log level.
-
Nothing happens. The threshold is only measurable for models whose context limit is known (from
provider.list). If the model has nolimitin its provider metadata, the plugin logs a one-time debug message and skips. Check the log file to confirm the plugin loaded and which sessions it is watching. -
The plugin and the built-in auto-compact both trip. You left
compaction.autoenabled. Set it tofalseas shown above. -
The session compacted but the work was lost. The plan turn did not update
PLAN.md. The plugin retries once and then compacts anyway with a warning in the log; make sure the model has write permission for$PWD(file-edit permissions allowlisted). -
A subagent keeps hitting the limit. Expected — subagents are intentionally out of scope to avoid collisions. The parent should evaluate failures and redispatch subagents with modified prompts on its own.
-
Context size is exceeded despite the plugins attempts to track it with a live count Even though the plugin will try to catch large tool outputs before they make it into context, it's still possible the model will read a large file all at once rather than chunks with no way for the plugin to stop it. You will get an error like the following:
request (72375 tokens) exceeds the available context size (65536 tokens), try increasing itYou have three options in this scenario:
-
Switch to a model with a context size larger than the request tokens size. Send it a
continueprompt and the workflow should finish. After the resume happens you can switch back to the model with the smaller context size, although the new model should continue using the existing threshold until your restart the client. -
Attempt to
/compactmanually. It will most likely fail but if the overshoot is small, sometimes it will work. This is not an ideal solution because your PLAN.md will be stale so you will want to instruct the model to write everything to PLAN.md before continuing. -
Start a new session and have the model work from the existing PLAN.md. Probably the least ideal choice because you will lose whatever work was performed before the last write to PLAN.md.
-
bun run build # Compile TypeScript to dist/
bun run bundle # Bundle with bun build for a single file bundle
bun clean # Remove build output, tarball and coverage reports
bun run deploy # Build, pack, and install to local OpenCode cache
bun all # Full cycle: clean → build → test → deploy → clean
bun start # Run plugin directly (for debugging)
bun test # Run tests
bun lint # Lint source files with biome
bun coverage # Run tests with coverage report. This command requires `genhtml` to be installed. Run `bun test --coverage` for CLI reports.MIT
{ "provider": { //... "models": { "Qwen3.8-27B-UD-IQ4_XS": { "name": "Qwen3.8-27B-UD-IQ4_XS", "limit": { "context": 65532, "output": 65532 } }, } //... }, "plugin": [ [ "opencode-neverending-story", { "threshold": 20 } ] ], "compaction": { "auto": false, "prune": false } }