Agent Deadline — soft budget for subagents
sonnet install / configure / remove project | global opt-inWhat it does
Claude Code has no wall-clock timeout for subagents, and maxTurns — when it fires — kills the agent outright and discards its final report. /brewtools:agent-deadline installs two hooks that impose a soft budget instead: at 80% of the allotted minutes the agent gets one non-blocking “wrap up” directive; past 100% every tool outside a finalization set is denied, so the agent is forced to write its report rather than lose it.
Run the skill with no arguments and it reports status first — installed scopes, wired hooks, current config — before doing anything else. Every other mode (install, disable, enable, uninstall, purge) states its plan, asks only what is still unknown, then delegates the actual file and settings work to the brewcode:hook-creator agent.
The feature is opt-in. Installing the brewtools plugin does not turn this on — these hooks are not registered in brewtools/hooks/hooks.json. Nothing runs until you invoke this skill and confirm install.
When to use
| Situation | Suggested invocation |
|---|---|
| Long autonomous subagent runs where a lost final report is costly | /brewtools:agent-deadline install |
| Want the same budget everywhere on this machine | /brewtools:agent-deadline install global 30 |
| Check what is currently installed without changing anything | /brewtools:agent-deadline |
| Temporarily stop enforcing without removing files | /brewtools:agent-deadline disable |
| Fully remove hooks, settings entries and config | /brewtools:agent-deadline purge |
Example
/brewtools:agent-deadline install project 20
Expected flow: the skill prints the current status table, states the plan (“copy 2 hook files into .claude/hooks/, write .claude/agent-deadline.json with defaultMinutes: 20, merge 2 settings.json entries”), asks only for confirmation since scope and minutes are already given, then delegates to brewcode:hook-creator and re-prints the status table showing guard=yes cleanup=yes settings_refs=2 enabled=true.
Workflow
- Status first, always
Before any mode runs, the skill checks both project and global scope for hook files,
settings.jsonwiring and existing config, and prints a state table. Nothing is installed or removed blind. - Mode decided from free text
No arguments defaults to
status. Otherwise the skill reads intent —install,disable,enable,uninstall,purge— including RU triggers like “поставь” or “убери совсем”. Ambiguity between install and a removal verb triggersAskUserQuestioninstead of a guess. - Plan stated before any question
The skill writes out, in plain text, exactly what will change: which files, which paths, which
settings.jsonentries — before asking anything. - Only missing answers are asked
Scope (Project / Global / Both) and budget (default 20 min) are asked via
AskUserQuestiononly if not already given in the arguments. Choosing Global surfaces the per-call cost in the question itself, not buried in a final report. - File work delegated to hook-creator
A single
brewcode:hook-creatorspawn per mode-and-scope combination copies the 2 hook files, merges or stripssettings.jsonentries, and writes or editsagent-deadline.json. Both scopes at once means two spawns in one message. - Refreshed status printed
The status block re-runs and the skill states whether a new session is needed — hook wiring changes (install/uninstall/purge) need one; config value changes (enabled, minutes, overrides, hard-stop ratio) are read live, no restart.
Технические детали
Modes
| Mode | Effect | Hook files | settings.json | Config | State |
|---|---|---|---|---|---|
status (default, no args) | report only | — | — | — | — |
install | wire + configure | copied | entries merged | written | — |
disable | pause enforcement | kept | kept | enabled: false | kept |
enable | resume enforcement | kept | kept | enabled: true | kept |
uninstall | unwire | deleted | entries stripped | kept | kept |
purge | full wipe | deleted | entries stripped | deleted | deleted |
The two hooks
| Hook | Event | Behavior |
|---|---|---|
agent-deadline-guard.mjs | PreToolUse on .* | Tracks elapsed time per agent_id. At 80% of budget, adds a non-blocking additionalContext warning. Past 100%, denies every tool outside the advertised finalization set: Read, Write, Edit, MultiEdit, NotebookEdit, TodoWrite, TaskUpdate. Past hardStopRatio x budget, the allowance shrinks further to Write, Edit only, with deny reason AGENT DEADLINE HARD STOP. |
agent-deadline-cleanup.mjs | SubagentStop | Deletes the finished agent’s state file. |
Actually allowed past 100% is 3 tools wider than what’s advertised to the agent — TaskCreate, BashOutput, TaskOutput are permitted but not named in the directive. This is deliberate: naming BashOutput invites a poll loop, while an agent that genuinely needs to harvest an already-running job still gets through. AskUserQuestion is denied outright past the deadline — a subagent parked on a human answer is exactly the unbounded wall-clock time this guard exists to stop.
Installation targets
| Scope | Hooks dir | settings.json | Config |
|---|---|---|---|
| Project | <repo>/.claude/hooks/ | <repo>/.claude/settings.json | <repo>/.claude/agent-deadline.json |
| Global | ~/.claude/hooks/ | ~/.claude/settings.json | ~/.claude/agent-deadline.json |
Project config wins over global; a malformed project config is skipped and global is used instead. Global writes go through Bash only — ~/.claude/* is a protected path, blocked for Write/Edit in every permission mode.
Config shape
{
"enabled": true,
"defaultMinutes": 20,
"byAgentType": {},
"hardStopRatio": 2
}
| Key | Meaning |
|---|---|
enabled | must be exactly true; anything else turns enforcement off |
defaultMinutes | budget applied to every agent type; default 20 |
byAgentType | per-type overrides, e.g. {"Explore": 10}; empty object = one uniform limit |
hardStopRatio | optional, default 2, must be > 1 — multiple of the budget past which the allowance drops to Write, Edit only |
Budget resolves as byAgentType[agent_type] ?? defaultMinutes, read fresh on every hook call.
Warning
Honest limits — read before relying on this.
- Not a timeout. Elapsed time is sampled only at tool-call boundaries. An agent stuck inside one 25-minute
Bashcall is not observed until its next call — cap long commands separately withBASH_MAX_TIMEOUT_MS. agent_typefor plugin agents is unverified. Whether the payload reportsbrewcode:developerordeveloperfor a plugin agent has not been confirmed against a live payload. AbyAgentTypekey that doesn’t match silently falls back todefaultMinutes— no error, no warning.- Fail-open. Any error inside the hook lets the call through; the session never breaks because of it.
- The matcher is
.*— every tool call pays the cost, not only subagent ones. Measured on Apple M-series, Node v24.1.0, 30 runs: median 58.3 ms, p90 62.5 ms per call, including the main-session no-op path. A global install therefore taxes every tool call in every repo and every session, whether or not that session ever spawns a subagent. Numbers are from one machine and one Node version — re-measure before trusting them as a budget line item. - The declared finalization set is narrower than what’s actually allowed.
TaskCreate,BashOutput,TaskOutputpass through past 100% but are intentionally left out of the directive text shown to the agent — by design, not an oversight.
Связанное
Brewtools overview
All brewtools skills and agents in one place.
Manager
Installs a HARD wall that blocks main-session edits and forces subagent delegation — pairs well with a per-agent deadline.
GitHub source
SKILL.md, the install runbook, and both hook scripts.
Updating plugins
/brewtools:plugin-update to check and update the brewcode plugin suite in one command.
See the FAQ for details.