Repository navigation
Description
Problem
When a long session approaches the model's context window, there's currently no proactive signal. The user only notices once compaction kicks in or responses start degrading. For agentic workflows it would help to know early — e.g. at 75 % — so the user can wrap up the task or start a fresh session before things get tight.
Proposal
Add an opt-in config that emits OS-level notifications (or at minimum bus events) when context usage crosses configurable thresholds.
{
"context_warnings": {
"enabled": true,
"thresholds": [0.75, 0.9, 0.97],
"notify": "os" // "os" | "event" | "both" | "off"
}
}Behaviour
- Triggered after each assistant
message.updatedwithtime.completedset - Usage computed as
(tokens.input + tokens.cache.read) / model.limit.context - Each threshold fires at most once per session (deduped)
- Severity scales with threshold (🟡 / 🟠 / 🔴)
Prior art
I already run this as a local plugin using client.config.providers() to look up model.limit.context and node-notifier for the OS toast — works well in practice. Reference implementation (~80 LOC):
```ts
import type { Plugin } from "@opencode-ai/plugin"
import notifier from "node-notifier"
const THRESHOLDS = [0.75, 0.9, 0.97] as const
const notified = new Map<string, number>()
const fmt = (n: number) => `${(n / 1000).toFixed(1)}k`
const notify = (title: string, message: string) =>
notifier.notify({ title, message, timeout: false, sound: true })
export const ContextMonitor: Plugin = async ({ client }) => {
let modelLimits: Map<string, number> | null = null
let limitsPromise: Promise<Map<string, number>> | null = null
const loadLimits = async (): Promise<Map<string, number>> => {
const map = new Map<string, number>()
const res = await client.config.providers()
const providers = (res.data as any)?.providers ?? []
for (const p of providers) {
const models = (p.models ?? {}) as Record<string, any>
for (const [modelId, m] of Object.entries(models)) {
if (m.limit?.context) map.set(`${p.id}/${modelId}`, m.limit.context)
}
}
return map
}
const getLimits = async () => {
if (modelLimits) return modelLimits
if (!limitsPromise) limitsPromise = loadLimits()
modelLimits = await limitsPromise
return modelLimits
}
return {
event: async ({ event }) => {
if (event.type !== "message.updated") return
const info: any = (event as any).properties?.info
if (!info || info.role !== "assistant" || !info.time?.completed) return
const modelKey = \`\${info.providerID}/\${info.modelID}\`
const limit = (await getLimits()).get(modelKey)
if (!limit) return
const t = info.tokens
const used = (t?.input ?? 0) + (t?.cache?.read ?? 0)
const pct = used / limit
if (pct < THRESHOLDS[0]) return
const sessionID: string = info.sessionID
const lastPct = notified.get(sessionID) ?? 0
const crossed = THRESHOLDS.find((th) => pct >= th && lastPct < th)
if (crossed === undefined) return
notified.set(sessionID, crossed)
const title = crossed >= 0.97
? "🔴 Context almost full"
: crossed >= 0.9
? "🟠 Context 90%"
: "🟡 Context 75%"
let sessionTitle = sessionID
try {
const s = await client.session.get({ path: { id: sessionID } })
if (s.data?.title) sessionTitle = s.data.title
} catch {}
notify(title, \`\${sessionTitle}\n\${fmt(used)} / \${fmt(limit)} (\${(pct * 100).toFixed(0)}%) · \${modelKey}\`)
},
}
}
```
Why built-in?
- Discoverability — most users won't write a custom plugin
- Cross-platform OS notifications — currently `node-notifier` adds a 5+ MB dependency
- TUI integration potential — colored token counter in the status line, fading from green → yellow → red
Happy to open a PR if there's interest in this direction.
Bonus: TUI status-line integration
OS notifications cover the crossing-the-threshold event well, but a persistent indicator inside the TUI — next to the existing LSP / MCP entries in the right column — would arguably be more useful for at-a-glance awareness during long sessions.
Imagine:
┌──────────────────────────────────────────┐
│ …chat… │
├──────────────────────────────────────────┤
│ build · sonnet-4-6 · ctx 42% ░░░░░░░░░ │ ← green
│ build · sonnet-4-6 · ctx 78% ▓▓▓▓▓▓░░░ │ ← yellow
│ build · sonnet-4-6 · ctx 95% ▓▓▓▓▓▓▓▓▓ │ ← red
└──────────────────────────────────────────┘
This benefits any plugin, not just context monitoring — token cost meters, build status, custom CI badges, etc. could all hook into the same surface.
Proposed plugin API
export default (async ({ client }) => {
return {
"status.items": () => [
{
id: "context-monitor",
priority: 10,
render: ({ session, model }) => ({
text: `ctx ${pct}%`,
color: pct > 0.9 ? "red" : pct > 0.75 ? "yellow" : "green",
tooltip: `${used} / ${limit} tokens`,
}),
},
],
}
}) satisfies Pluginstatus.itemsreturns an array of declarations the TUI renders in the right columnrenderis called per redraw with current session/model context- Optional
color,tooltip,onClick(future) fields
Scope split
If a single feature is too broad, splitting into two issues makes sense:
- Plugin API: generic
status.itemssurface (this benefits the whole plugin ecosystem) - Built-in: ship a default context-usage status item using that API + the OS-notification behaviour described above
I'd be happy to prototype either side if there's directional agreement.
Activity
github-actions commentedon May 16, 2026
This issue doesn't fully meet our contributing guidelines.
What needs to be fixed:
[FEATURE]:(e.g.[FEATURE]: Built-in context-usage warnings at configurable thresholds)Please edit this issue to address the above within 2 hours, or it will be automatically closed.
If you believe this was flagged incorrectly, please let a maintainer know.
github-actions commentedon May 16, 2026
This issue has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window.
Feel free to open a new issue that follows our issue templates.