Repository navigation
Releases: rbartoli/agent-usage-guard
Release list
v1.1.1
- A resumed subagent counts as one start. Since Claude Code 2.1.299, a
SendMessageresume also raisesagent.spawn, inside the same call. The guard judged it in both, so one resume counted as two starts and could ask a second time, as if a new agent were starting. The spawn event now passes a resume itsSendMessagecall already judged. A resume that arrives only throughagent.spawn, such as a teammate brought back from disk, is judged there as a resume, and every refusal of one says it was not resumed rather than not started. /agent-guard statusnames the version that runs. Its first line readsOn (1.1.1)., from the loaded copy's ownplugin.json, so afterclaude plugin updateand/reload-pluginsit shows which copy a session runs. Before, the status read the same in every version.
v1.1.0
- From 95%, each session winds down. Once a plan window on this machine reaches
AGENT_GUARD_LIMIT_DENY_PERCENT(95%), the point at which new agents are refused, the next prompt in each session carries one note for Claude: finish the step in progress, start nothing large, and end the turn with what is done and what is left. Lockouts since 1.0 came from many main sessions running in parallel, which no rule held; a lockout now finds them at a clean stop. A session is told again only after the window falls back below the threshold, such as after its reset. The prompt gate now reads every session's plan reading, so an idle session hears of a window another session filled. The journal records each note, and/agent-guard reportcounts them. - Idle compaction is documented, and pinned by a test. Claude Code 2.1.299 compacts a conversation of 200k tokens or more before its one-hour cache expires. The guard reads the compacted size, so the dormant question does not apply to that session; a test now covers it. The README says which sessions the dormant question still covers.
- CI publishes each release. When the tests pass on
mainand the version inplugin.jsonhas no tag yet, CI tags the commit withclaude plugin tag --pushand publishes its GitHub release, with that version's section of this changelog as the notes. A release is now a PR that bumps the version and dates its section. - The demo renders as an MP4 that stays readable in a feed.
demo/render.shalso writesdemo/out/agent-usage-guard.mp4: 1440×1440 at about 70 columns, a terminal window on a margin, recorded straight to H.264. The earlier post video was converted from the 95-column GIF, so a feed showing it 660 px wide put the text at about 11 px. The GIF comes from the same recordings at 960 px, anddemo/render.sh <scene>records only the named scenes.
v1.0.3
- The report counts each locked window once. It counted every request that failed on a usage limit, so retries and parallel sessions hitting the same window inflated the figure. A lockout row now keeps the reset time of the window that locked, and the report counts each window once. Rows from earlier versions have no reset time, so a run of them with no gap of an hour or more counts as one. Reports shorter than a week no longer give a weekly rate, which only repeated the count.
v1.0.2
- The status line clears when what it shows runs out. The guard redrew it only when the session did something, so a session that sat idle across a 5-hour reset kept "5-hour window 95%: new agents refused" while the new window stood at 3%, and an allow's "lifted until" stayed up after it ended. The guard now also redraws the line when a plan window resets, an allow ends, or the pause on agent spawns ends.
v1.0.1
- "Compact, then send" empties the input box when the put-back is late. The guard took the prompt out of the box only once, at the start of the compaction. If Claude Code put it back after that, it stayed in the box after being sent (seen on 2.1.295 with a plain "merge"). The guard now checks again right before it resends the prompt.
- An icon for the plugin directory listing: a shield with a pause sign.
- The listing's links point here. The directory filled Support and Documentation from the first matching README links, an Anthropic issue and Claude Code's mod docs.
plugin.jsonnow names this repository's issues, its README, and the README's Security and privacy section as the privacy policy. - Installs no longer copy the demo GIF. The README shows the 2.9 MB GIF from the v1.0.0 release instead of the repository, and
demo/render.shwrites it todemo/out/. - Security and privacy answers what the plugin directory's validation asks: what the mod decides, the one prompt it submits (yours, after "Compact, then send"), what its prompt and
SessionStarthooks change, and thatdemo/reads no credentials.
v1.0.0
Released 2026-10-09.
Rebuilt as a Claude Code mod (Claude Code v2.1.287 or later). The Python command hooks are gone: the guard now runs inside Claude Code, as a harness-agnostic core (core/) behind a Claude Code adapter (hooks/register.ts). A full review of 0.2.5 against Claude Code 2.1.292 found that hooks could only approximate what mods expose directly: live context, plan-window percentages, and a refusable event before every agent starts.
- Plan-aware agent gates. New agents ask from 80% of a plan window and are refused from 95%, and ask when the 5-hour window rises 20 points in ten minutes. This is the lever the 2026-08-08 incident lacked: 0.2.5 reacted only after a usage limit had already been hit.
- Every agent path is gated before it starts.
agent.spawncovers subagents, teammates and workflow agents, so Workflow and/deep-researchno longer need a blanket gate. Resuming a finished subagent withSendMessageis gated and counted./subtaskforks are counted when they start. - Questions instead of hard blocks. When a person can answer, a gate asks in Claude Code's own question dialog. An approval holds for a while (a plan window until it resets, a heavy context until it shrinks, agent counts for 10 minutes), which ends the approval fatigue of 0.2.5 (132 of 136 high-context asks approved, often one per tool call). Without a person (
claude -p, the SDK,dontAsk), gates refuse as before. - Subagents are judged on their own context. 0.2.5 read the main transcript for a subagent's tool calls, so subagents inherited the parent's context and tool budget and could have their report hand-back refused.
- Approvals are seen, not inferred. 0.2.5 read an approved dialog back as a refusal when the identical call came again, and escalated the denial ladder. The mod gets the answer from the dialog.
- Prompts are no longer held after a usage limit. Claude Code has waited at a limit and resumed at reset since v2.1.234. The 0.2.5 circuit breaker made 232 refusals that saved nothing, swallowed 31 background-task notifications, and kept blocking for 14 minutes after a limit lifted early. Lockouts are still journaled; they are the guard's outcome metric.
- Heavy prompts offer to compact first. At 500k context a prompt asks: compact then send (the prompt is resent as your own words), send anyway, or cancel (the prompt goes back in the input box). Dormant heavy sessions ask the same way.
/agent-guardreplaces the bracket markers. It runs without a model turn, so the model never reads an override. The old markers sent alone still work.- A Stop hook that reopens the turn changes the wording. All 37 identical retries after a denial in 0.2.5's journal followed
/goalreopening a turn the guard had asked to end; refusals now hand that decision to the user. - No per-call cost. The hooks run in-process; 0.2.5 started a Python process per tool call, at 110–160 ms each.
- Removed: the research gate and the broad-resume prompt patterns, which stood in for "a swarm is about to start" and are superseded by per-agent gating; the circuit breaker's prompt blocking and its reset-time parsing; the state file under
$XDG_STATE_HOME; the manual settings-hooks install. - Changed configuration: thresholds stay environment variables;
AGENT_GUARD_CONTEXT_MAXis nowAGENT_GUARD_AGENT_TOKENS_MAX,AGENT_GUARD_HIGH_CONTEXT_TOKENSisAGENT_GUARD_DORMANT_CONTEXT, and the block-fuse variables areAGENT_GUARD_FUSE_MAXandAGENT_GUARD_FUSE_SECONDS. New:AGENT_GUARD_LIMIT_ASK_PERCENT,AGENT_GUARD_LIMIT_DENY_PERCENT,AGENT_GUARD_BURN_PERCENT,AGENT_GUARD_DEPTH_MAX,AGENT_GUARD_PEER_TTL_SECONDS,AGENT_GUARD_JOURNAL,AGENT_GUARD_JOURNAL_DAYS. Gone with the state file and the circuit breaker:AGENT_GUARD_STATE, theAGENT_GUARD_EVENTS*variables,AGENT_GUARD_PENDING_SECONDS,AGENT_GUARD_LEASE_SECONDS,AGENT_GUARD_KNOWN_AGENT_SECONDS,AGENT_GUARD_RESEARCH_CONTEXT,AGENT_GUARD_RATE_COOLDOWN_SECONDS,AGENT_GUARD_SESSION_COOLDOWN_SECONDS,AGENT_GUARD_MAX_EFFORT_AGEandAGENT_GUARD_NOW. - Robust under parallel calls. Calls that trip while a question is open are held back at once instead of waiting on it, so no hook outlives its 10-second limit; an agent the guard could not judge in time is refused, not started. The retry fuse counts identical failures only when nothing else ran in between, so test, edit, re-run is never a loop.
/agent-guard resumeandallowlift the agent pause. The journal stays under 2 MB of the store, and a subagent's permission mode no longer decides whether the guard can ask. - Tests run in Claude Code's mod test kit (
claude plugin test), including a parallel batch of five agents against a limit of four, a race the first draft of the mod got wrong.
Changed between the first build (2026-10-07) and this release, after a pre-launch review, the README demo recordings and a quality pass:
- The README demo shows four scenes (a cold cache, parallel agents, a heavy-context loop and the subagent token budget), recorded from Claude Code against a scripted local API so it needs no account and spends no usage:
demo/render.sh. - An agent in a long tool call stays counted. The 15-minute expiry for agents whose end was never reported also caught a live agent blocked in one tool, such as a test suite, and nothing counted it again, so one more agent could start past the concurrency limit. An agent with a tool call running is never expired now, and any agent that makes a request counts as running again.
- Only a dismissed question counts as a no. Any other failure of the question dialog, such as the turn being interrupted while it was open, was read as the user declining: agents were refused for 10 minutes and the refusal fed the fuse. Such a failure now fails open.
- "Compact, then send" never sends at full size. When compaction failed, or a hook vetoed it, the prompt was resent anyway, past the 500k gate. It now goes back into the input box with the reason.
- Notifications leave the context warning for you. A background task's notification above 300k used up the one-time warning, so your own next prompt never carried it.
- Other sessions' subagent tokens age out with the window. Each session published one total that counted for as long as its record lived (15 minutes, against a 10-minute budget). Records now carry tokens per minute (peer record version 2); a session still running an older version is not counted until it reloads.
- A context reading below 400k clears the heavy-context budget. It revoked "Keep going" but kept the call history, so the next heavy call asked again.
- A
/subtaskfork is counted while a gated agent is starting. It was ignored whenever an agent spawn was pending. - The dormant gate works on resumed sessions. It timed a resumed session's idleness from the transcript's modification time, which Claude Code rewrites when it resumes a session and hourly while one is open, so
claude --resumeand--continuenever asked before re-writing an expired cache. Each session now stores when it was last active in the mod store for 30 days, and a resume reads it from there. Found while recording the README demo: a 485k-token session resumed after two hours sent its prompt without asking. - "Compact, then send" leaves the input box empty. Claude Code puts a dropped prompt back in the input box, so after the guard resent it the box still held the prompt, and one more Enter sent it twice. The guard now takes it out, unless you have changed it.
/agent-guard reportcounts only answers you gave. A question the session could not show, because it has no AskUserQuestion tool, counted as declined; it now counts as neither. A single event reads "1 event"./agent-guard statuswords a paused fuse as the status line does: "agent spawns paused until 14:32", not "agent fuse burning".- Tidier code, same behaviour. Shared helpers replace copies: one path asks and journals all three questions, one function counts across sessions, one refusal covers a stopped loop. A refusal carries its number, so the adapter no longer reads it back out of the text, and a test fails if the adapter stops reading one of the documented variables.
- README claims made exact. It no longer says most usage tools only monitor, or calls the mod small enough to audit; it gives the size instead (about 2,400 lines of TypeScript, no dependencies). Checked on 2.1.295: plan-window readings arrive in
claude -ptoo, after its first response.
v0.2.5
Released 2026-08-16.
- Drop the stray
Ufrom the prompt-denial banner. 0.2.4 rendered
🛡️U USAGE GUARD; it is🛡️USAGE GUARD.
v0.2.4
Released 2026-08-16.
- Label prompt-side denials
🛡️U USAGE GUARDand suppress Claude Code's "Original
prompt:" dump. A background-task<task-notification>under a session limit
was rendering as a generic hook error plus the XML envelope. Circuit-breaker
copy now says new work waits until reset (⏳), and names a finished
background task (📬) when that is what was blocked.
v0.2.3
Released 2026-08-15.
-
Throttle high-context warnings per session, not per request id. Successive
turns in the same heavy session were each a new request, so the 10-minute
notice window never hit and the journal filled with repeats of the same warn. -
Count the deny ladder by condition for session-level rules (high-context
turn, active/rolling/nested/agent-context), not by tool-input hash. A refused
5th agent then a different 5th is denial 2; a different Bash under a
high-context turn is too. Agent-budget silent-refusal inference matches a
stale pending reservation in the same session. Tool-error fuse stays
per-fingerprint. Ladder copy names "this condition" rather than "this exact
call" on those trips. -
Record
tool_nameand a coarsecontext_bucketon journal interventions, and
summarise both inreport. Still no prompt text or tool inputs.
v0.2.2
Released 2026-08-08.
-
Stay out of agent CLIs that are not Claude Code. Cursor Agent imports the
plugins enabled in~/.claude/settings.jsonand runs their hooks under its
own event names, so the guard was refusing Cursor prompts on the strength of
a Claude session's context and usage — advice that host cannot act on, with
/compactand the escape markers both meaningless there. Cursor exports
CURSOR_PLUGIN_ROOTbeside theCLAUDE_PLUGIN_ROOTcompatibility alias;
the guard now sees it, exits 0, and records nothing. Found by dogfooding. -
Run CI on the organisation's shared self-hosted runners, which is why the org
exists. The lane had been asking forubuntu-latestand could not start at
all, so nothing it checked was ever reported: Python 3.9 now comes fromuv
because the runners are Ubuntu 26.04, for whichactions/python-versions
publishes no 3.9 build, and three findings ruff 0.16.0 had been unable to
report are fixed — ISC004 on the report header, EXE001 onmutation-smoke.py,
and the formatter driftmainpredated.