OpenCode plugin for model-directed context compression. Run it explicitly with /compress manage,
or let the plugin initiate the same workflow before any session fills its context window.
- No separate summarizer or hidden compaction model: the active agent writes one truthful summary and a short block title; ordinary compression span selection is deterministic inside the plugin.
- Manual compression runs when you trigger
/compress manage. - Automatic compression runs once when completed assistant usage reaches the earlier of 90% of the model-reported context window or 330,000 tokens by default.
- When plugin-owned automatic compression is enabled, native OpenCode auto-compaction is disabled through the plugin config hook so the two mechanisms cannot race.
- Public tools are
compress({ summary, topic })for deterministic uncompressed-history folding andsquash({ from, to, summary, topic })for explicit existing-block maintenance.squashis fail-closed at runtime to an active/compress squashturn.compressis not: runtime only requires a visible user owner of the executing call; the ban on autonomous normal-turn use is a prompt-level contract agents must honor, not plugin enforcement. - Manual management, automatic management, and authorized normal-turn use all share the same
deterministic selection: every eligible uncompressed message after the newest existing
[bN]block, excluding the newest configured execution steps (protectedTurns, default3). - Existing compressed blocks are immutable under ordinary compression. Only an explicit
/compress squashturn may replace one contiguous range of at least two blocks. - The
compresspermission controls both tools. If it is denied, neither management workflow opens an unusable model turn. - A successful
compresscall is the finish line: the fold takes effect immediately for the next model turn, with no need to wait for a further user message. - After a successful compression, automatic and model-initiated compression pause for the next
three completed assistant responses in that exact session. An explicit
/compress managemay override this cooldown. - After that management turn completes, its trigger, tool calls, tool outputs, and assistant chatter are hidden from future model prompts.
- Automatic turns require a high-detail current-task handoff and tell the agent to resume the interrupted task immediately when work was genuinely still active.
- The plugin never opens a session turn on its own for anything but compression: an automatic
threshold trigger, or a
/compresscommand you ran. - Compression turns for the
orchestratoragent additionally require the session's handoff report file to be brought up to date before thecompresscall, while the evidence is still visible. Every other agent's compression turn carries no report wording at all. - The first model request after a successful compression carries a transient notice telling the agent its context was compressed, to re-read the relevant documentation, and to continue the original task. It is appended by the prompt transform, never written to the session, and stops appearing as soon as the agent produces work after the compression.
/compressor/compress help: show command help./compress manage [instruction]: send a self-contained context-management reminder that requires onecompresscall withsummaryandtopic; optional trailing text is passed to the agent as the user's specific compression instruction./compress squash [instruction]: explicitly ask the agent to replace one contiguous range of at least two existing[bN]blocks with a smaller truthful summary; optional trailing text is passed separately as the user's squash guidance./compress context: show token usage breakdown for the current session./compress stats: show session and all-time compression totals./compress autoor/compress auto status: show this session's effective automatic-compression settings and cooldown./compress auto on|off: enable or disable automatic compression for this session. Already-matching effective state is a no-op that reports the source without writing a redundant override./compress auto threshold N: override this session's absolute token threshold./compress auto ratio N: override this session's context-window threshold with an integer percentage from 1 to 99./compress auto reset: clear this session's threshold and ratio overrides without changing its on/off setting or cooldown.
/compress manage and /compress squash intentionally create model-visible turns.
All /compress auto feedback is user-only. Session off disables every automatic trigger for
that session—both the absolute threshold and the context-window ratio—until it is turned on again.
The process-level autoCompression.enabled: false setting remains authoritative and cannot be
overridden from a session.
During normal work agents must call compress only when the current user message explicitly
authorizes compression — a prompt contract (lib/prompts/compress.md), not a runtime text check.
/compress manage and an automatic threshold trigger open a model-visible management turn with a
self-contained reminder. In every authorized path the agent:
- Review the current conversation and reconcile chronology (later evidence supersedes stale plans).
- Call
compressonce with:summary: the durable replacement for all eligible uncompressed history after the newest existing blocktopic: a short title for the new compressed block
- Treat a success receipt as durable and already active. Do not call
compressagain that turn.
The plugin selects the span deterministically inside that one call:
- Resolve the compression boundary (active management trigger, or the visible user turn that owns the executing tool call).
- Apply existing compression transforms so historical blocks occupy their canonical positions.
- Take every uncompressed message after the newest existing block (or all uncompressed messages when no block exists).
- Exclude the newest
protectedTurnsexecution steps so they remain verbatim after the fold. - Never select a block or any message ID belonging to an existing block.
- Atomically persist the new block, IDs, stats, management completion marker when applicable, and cooldown anchor before returning success.
If nothing eligible remains, the tool returns a truthful empty result and leaves state unchanged. Normal-turn ownership is tied to the executing tool call; a later queued user cannot fold the wrong turn. Ambiguous ownership fails closed without changing state.
Automatic triggering is event-driven, per session, and deduplicated. It observes completed provider usage; it does not open a management turn on every response. Primary and subagent sessions use the same thresholds, transforms, protected tail, cooldown, commands, tools, continuation, and feature-detected Goal overflow recovery. Their summaries, overrides, cooldowns, and persisted files remain isolated by exact session ID.
New task child sessions require a host that does not synthesize blanket child denies for compress
and squash. Existing persisted child-session permission rules are not migrated; a child created by
an older host may need to be recreated before compression tools are available.
Normal-turn compression still respects the three-response post-compression cooldown. During
cooldown, compress refuses model-initiated use and asks the agent to wait; an explicit user
/compress manage remains the override.
/compress squash is a separate, user-only maintenance workflow. The agent sees current positional
[b0], [b1], ... labels, chooses one contiguous inclusive range containing at least two existing
blocks, and calls squash once. The replacement occupies the selected range's first position;
out-of-range blocks and all uncompressed conversation remain unchanged, and later blocks are
relabeled by position without being reordered. Labels are derived from transcript anchor order and
are never persisted identities.
Squash is deliberately more lossy than ordinary compression because it summarizes saved summaries; the hidden original messages are not restored or reread. It never starts automatically, does not participate in Goal overflow recovery, and does not arm or replace the post-compression cooldown. Its savings statistic counts only a positive reduction in model-visible summary tokens.
While a management turn is still open, the agent can see the reminder and tool activity. The
instant its owning compress or squash call succeeds, the management prompt and status
notifications are hidden from the very next model turn — no further user message is needed. The
completed tool call itself stays briefly because providers require the tool-call/result pair; its
submitted input is left literal so the agent cannot mistake a synthetic marker for what it
submitted. On later turns, the
model-visible context contains only compressed [bN] blocks, normal conversation between
compression runs, the preserved newest execution steps, and model-visible Goal continuation text.
The cleanup leaves no marker or placeholder behind.
After a successful compress, the prompt transform appends one plain user message at the tail of
the model request telling the agent that its context was compressed, to re-read the relevant task,
spec, report, and project documentation, and to continue the original task.
The notice is derived, not tracked. There is no pending flag to clear, so a request that fails or
is rebuilt simply recomputes the same answer and the notice is not lost. It is recomputed from the
raw transcript on every transform: due while the assistant message carrying the successful
compress call has produced no work after that call, and gone once it has. Ignored plugin status
notifications, Goal continuation messages, and ordinary user messages do not count as work.
Because it lives only in the transformed request, it is never persisted; a session with many
compressions accumulates no injected-notice residue. It is shown to every agent, including
subagents, and is not shown for squash.
npm install @skybluejacket/opencode-context-compressThen add it to your config:
| Platform | Global | Project-level |
|---|---|---|
| OpenCode | ~/.config/opencode/opencode.jsonc |
.opencode/opencode.jsonc |
Clone the repo, build, and reference the compiled entry file directly:
git clone https://github.com/AidenGeunGeun/opencode-context-compress.git
cd opencode-context-compress
npm install
npm run build{
"plugin": ["file:///absolute/path/to/opencode-context-compress/dist/index.js"]
}Config files are loaded and merged in this order:
~/.config/opencode/compress.jsonc(orcompress.json)$OPENCODE_CONFIG_DIR/compress.jsonc(orcompress.json)<project>/.opencode/compress.jsonc(orcompress.json)
If no global config exists, the plugin creates ~/.config/opencode/compress.jsonc with:
{
"$schema": "compress.schema.json"
}Default runtime config:
{
"enabled": true,
"debug": false,
// "dailyLog": follows "debug" when unset
"notification": "detailed",
"notificationType": "chat",
"protectedTurns": 3,
"commands": {
"enabled": true
},
"autoCompression": {
"enabled": true,
"contextWindowRatio": 0.9,
"tokenThreshold": 330000
},
"tools": {
"compress": {
"permission": "allow",
"showCompression": false
}
}
}protectedTurns is general compression policy (manual, automatic, and authorized normal paths).
Default is 3. The legacy nested key autoCompression.protectedTurns is still accepted as a
fallback when the top-level key is absent; an explicitly configured top-level value wins.
Logging keys (under ~/.config/opencode/logs/compress/, or $XDG_CONFIG_HOME/opencode/logs/compress/):
debug(defaultfalse): on every successful prompt transform, write a full minimized context snapshot tocontext/<sessionId>/<timestamp>.json. Not limited to compression turns. Each file holds the whole conversation so far, so disk use grows quickly with turn count — leave off unless debugging.dailyLog(optional): one-line activity log atdaily/<YYYY-MM-DD>.log. When unset, followsdebug, so existing configs that only setdebugkeep both outputs. SetdailyLog: truewithdebug: falseto keep the activity log without snapshots.
Session state is stored at:
~/.local/share/opencode/storage/plugin/compress/<sessionId>.json(or under$XDG_DATA_HOME)
Stored fields include:
- compressed tool IDs
- compressed message IDs
- compression summaries (
[bN]blocks: anchor, message IDs, summary, topic) - manual, automatic, and explicit squash management-turn cleanup markers
- per-session compression stats
- session automatic-compression overrides and the post-compression cooldown anchor
- optional one-shot Goal overflow recovery owner (
goalOverflowRecovery: overflow message id +{ goalID, timeUpdated }) when the host blocked a Goal onContextOverflowError
Durable mutations for one session run through SessionStateManager.runExclusive(sessionId, ...).
A candidate state is saved atomically before it is committed to memory.
The raw conversation history still exists in OpenCode storage, but completed management machinery is suppressed from future model prompts. Restarting the session reloads the saved cleanup markers, so old management turns do not reappear in the model-visible stream.
- Old completed session state continues to load and render. Existing
[bN]anchors and ordering are unchanged. - Historical management residue that still contains retired tool parts is cleaned up so old completed management machinery stays hidden; that cleanup path is not part of the current agent-facing workflow.
- Stale persisted
compressionMapSnapshotdata is ignored and cleared through the normal state lifecycle. It is never executed and is not replaced by another durable snapshot system. - The legacy
"dcp"storage directory string remains for migration from older installs.
This plugin coexists with OpenCode’s native Session Goal (/goal) when the host exposes it. Goal
lifecycle stays host-owned; the plugin only recognizes Goal continuation text at management
boundaries and may resume a blocked Goal after one bounded overflow recovery.
Host Goal responses may include objective and timestamps; for overflow recovery the plugin only
requires Goal id, status, and time.updated (lifecycle owner/version CAS — not an elapsed
metric). There is no Goal token/elapsed accounting on the host API, and this plugin does not track
Goal metrics.
Goal continuations are synthetic user text that remains model-visible (not ignored). Stable
recognition requires all of:
- User text part with
synthetic: true - Exact prefix:
Continue pursuing the active session goal. - A line
Goal reference: goa_* <timestamp>(Goal id + ownertime.updated)
The plugin ignores that combination only as a management-boundary exception so ordinary Goal steering does not close an open management turn. The marker is not stripped from model context. Recognition is fail-open: missing prefix or reference → treat as a normal user boundary.
Do not pause/resume the Goal around ordinary automatic management turns. Host prompt admission may self-heal when a newer durable user (including a management turn) was admitted while a joined run exits; that recheck is generic OpenCode behavior, not plugin-specific.
When automatic compression is enabled and the host reports assistant ContextOverflowError for a
session whose Goal is blocked:
- Store the exact owner
{ goalID, timeUpdated }with the overflow message id in per-session state. - Open one recovery management turn that uses the same single
compressworkflow. - After successful
compress, feature-detectsession.goal/session.goalUpdateand resume only if the same Goal is still blocked with the same owner token (optional CAS on the host resume API). - Never resume after edit, replacement, manual pause, completion, or a different blocked Goal.
- Failed compression leaves the Goal blocked and does not loop.
- Hosts without Goal APIs: recovery is disabled only; ordinary automatic compression still works (absent Goal methods remain graceful — feature detection returns no Goal data and skips resume).
Implementation: lib/goal.ts, SDK adapters in lib/sdk/client.ts (SessionGoalInfo: id,
sessionID, objective, status, time.{created,updated}), state field goalOverflowRecovery,
auto handler in lib/auto-compression.ts, post-success path in lib/tools/compress.ts, marker
exceptions in transform/policy/hooks.
- Plugin source tests:
tests/goal-compatibility.test.ts, overflow cases intests/auto-compression.test.ts(marker boundary, one-shot recovery, owner payload, stale/manual/ no-API/resume failure). - Joint host tests live in the OpenCode fork
(
packages/opencode/test/session/prompt.test.ts): real builtdist/index.jsloaded against scripted local-provider OpenCode for common Goal+compression, overflow success once, failed compression no loop, and stale edit/manual pause no resume. npm testrebuilds committeddist/and must pass before handoff.- No install/restart/config edit is required for docs-only or already-referenced
file://loads; restart OpenCode only after Aiden chooses to install/use a newly built plugin or host binary.
Future merge/removal: if OpenCode drops Goals or changes continuation text, delete marker exceptions
and overflow recovery here and rebuild dist/. Do not teach broad OpenCode core about this plugin.
npm install
npm run generate:prompts
npm run typecheck
npm test
npm run buildPrompt utility docs are in scripts/README.md.
The project-scoped agent onboarding skill is in .agents/skills/work-on-context-compress/.
MIT

{ "plugin": ["@skybluejacket/opencode-context-compress"] }