An OpenCode plugin that guards sub-agent capacity, enforcing a lightweight, configurable, per-session capacity lifecycle. It tracks cumulative tool invocations and ingested context tokens per sub-agent session, surfaces live remaining-capacity feedback, and smoothly transitions exhausted sub-agents from an active execution stage to a controlled finalization stage where non-whitelisted tool usage is blocked so agents conclude their tasks cleanly.
- Two-stage lifecycle: sessions move one-way from
executiontofinalizationwhen any limit is breached. - Dual-metric tracking: cumulative tool invocations and estimated ingested context tokens, isolated per
sessionID. - Configured-agent guard: only agents with an explicit, enabled capacity profile are restricted; unconfigured and unnamed agents are exempt by default, and per-agent overrides can assign custom budgets.
- Finalization whitelist: permit specific tools (and optionally specific file paths) once a budget is exhausted.
- Transparent feedback: interception errors explain which limit was hit, and live budget status is injected into the system prompt.
- Hierarchical config: global defaults → per-agent overrides → environment variables.
- BPE token estimation: accurate per-session token budgets with bounded-cost counting for large tool outputs.
Reference the package folder directly in ~/.config/opencode/opencode.jsonc:
{
"plugin": [
"github:your-username/opencode-context-guard#v1.0.0"
]
}{
"plugin": [
"opencode-context-guard"
]
}The plugin accepts an optional options tuple in opencode.jsonc:
{
"plugin": [
["opencode-context-guard", {
"enabled": true,
"defaults": {
"maxTools": 15,
"maxTokens": 40000,
"finalization": {
"allowedTools": [],
"allowedPaths": []
}
},
"agents": {
"code-reviewer": {
"maxTools": 10,
"maxTokens": 30000,
"whitelistedTools": ["lsp"]
},
"writer": {
"maxTools": 10,
"maxTokens": 25000,
"whitelistedTools": ["context7*"],
"finalizationRemaining": 2,
"finalization": {
"allowedTools": ["write", "edit"],
"allowedPaths": ["./reports/**"]
}
}
}
}]
]
}Only agents listed in agents are guarded: agent names absent from agents are exempt, and profiles with enabled: false are exempt.
| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
true |
Global toggle for capacity guarding. |
defaults.maxTools |
number |
15 |
Default maximum cumulative tool invocations. |
defaults.maxTokens |
number |
40000 |
Default maximum ingested context tokens. |
defaults.finalization |
object |
{ "allowedTools": [], "allowedPaths": [] } |
Tools/paths permitted once in finalization. |
agents |
object |
{} |
Per-agent overrides: enabled, maxTools, maxTokens, finalization, whitelistedTools, finalizationRemaining. |
agents.<name>.whitelistedTools |
string[] |
unset | Tool-name patterns that do not count toward maxTools. Each entry matches a tool exactly, or as a prefix when it ends with * (e.g. context7*). Tool names starting with todo and the exact skill tool are always non-counting. |
agents.<name>.finalizationRemaining |
number |
unset | Positive integer of paid finalization invocations. When unset (or zero), the legacy behavior applies: the transition is at maxTools and finalization calls are unlimited. |
When no options are provided, the plugin enforces the following per-agent budgets (mirroring the reference AGENT_RESTRICTION_TABLE):
| Agent | maxTools |
maxTokens |
finalization.allowedTools |
whitelistedTools |
finalizationRemaining |
|---|---|---|---|---|---|
web-researcher |
9 | 24000 | — | — | — |
file-explorer |
15 | 50000 | — | lsp |
— |
code-reviewer |
12 | 18000 | — | lsp |
— |
planner |
15 | 18000 | write, edit, apply_patch |
lsp |
2 |
doc-writer |
10 | 10000 | write, edit, apply_patch |
lsp |
2 |
coder |
24 | 48000 | write, edit, apply_patch |
context7*, task, lsp |
3 |
Explicit tuple options override the baked budgets per field: e.g. agents.coder { "maxTools": 5 } lowers maxTools to 5 while maxTokens stays 48000. Agents not in the table are exempt from guarding entirely.
| Variable | Effect |
|---|---|
OPENCODE_CONTEXT_GUARD_ENABLED |
Overrides enabled (true/false). |
OPENCODE_CONTEXT_GUARD_DEFAULT_MAX_TOOLS |
Overrides defaults.maxTools (accepts Infinity for unlimited). |
OPENCODE_CONTEXT_GUARD_DEFAULT_MAX_TOKENS |
Overrides defaults.maxTokens (accepts Infinity for unlimited). |
opencode.jsoncoptions (highest)- Environment variables
- Built-in defaults (lowest)
- On
chat.params, the plugin records the agent for the incoming session. - On
tool.execute.before, it records the attempt and — if the session has enteredfinalization— blocks any tool not permitted byfinalization.allowedTools/allowedPathswith a structuredCapacityLimitError. - On
tool.execute.after, it accumulates the tool count and BPE-based token estimate (input args + output text), transitioning the session tofinalizationwhen either limit is reached. - Live budget status is injected into the system prompt and appended to successful tool results.
- Tool names starting with
todoand the exactskilltool never count toward the budget, regardless of configuration. - Each configured entry matches a tool name exactly, or as a prefix when the entry ends with
*(e.g.context7*matchescontext7_resolve-library-id). - A successful whitelisted call still consumes tokens and is recorded as a success (advice, status, and token totals include it) — only its
toolCountcontribution is zero. - The whitelist affects counting only; it does not grant any permission and is unrelated to
finalization.allowedTools.
- Profiles without a positive
finalizationRemainingretain the legacy behavior: the transition happens when cumulativetoolCountreachesmaxTools(or on token exhaustion), and finalization-stage calls permitted byfinalization.allowedTools/allowedPathsare unlimited. - With
finalizationRemainingset toR, the transition happens once the non-finalization budget is exhausted:toolCount >= maxTools - Ror token exhaustion, whichever comes first. - After the transition, only
finalization.allowedTools/allowedPathscalls pass; each successful finalization call consumes one unit ofR, regardless of whether its tool is in the counting whitelist. WhenRis spent, the session is locked. - One-time handover exception: during finalization, a single successful
write/edit/apply_patchcall targeting the session's ownHandovers/SCRATCH_<agent>_<sessionID>.mdfile (exact path or any path ending in/+ that filename) bypasses the finalization restrictions exactly once. Ordinary accounting still applies to it (tokens and success counting), it does not consumefinalizationToolsUsed, and block messages and status advice advertise the remaining handover until it is consumed. Handovers are per-session state only — no templating, persistence across restarts, or generation of files.
npm test
npm run typecheckMIT
{ "plugin": [ "file:/path/to/packages/opencode-context-guard" ] }