Skip to content

Built-in context-usage warnings at configurable thresholds #27926

Closed as not planned

Description

@jirkacha

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.updated with time.completed set
  • 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 Plugin
  • status.items returns an array of declarations the TUI renders in the right column
  • render is 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:

  1. Plugin API: generic status.items surface (this benefits the whole plugin ecosystem)
  2. 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

  1. github-actions commented on May 16, 2026

    @github-actions
    Contributor

    This issue doesn't fully meet our contributing guidelines.

    What needs to be fixed:

    • The title should start with [FEATURE]: (e.g. [FEATURE]: Built-in context-usage warnings at configurable thresholds)
    • The feature request template requires a verification checkbox confirming the feature hasn't been suggested before ("I have verified this feature I'm about to request hasn't been suggested before")

    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.

  2. github-actions commented on May 16, 2026

    @github-actions
    Contributor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions