Agent Deadline — soft budget for subagents

sonnet install / configure / remove project | global opt-in

What 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

SituationSuggested 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

  1. Status first, always

    Before any mode runs, the skill checks both project and global scope for hook files, settings.json wiring and existing config, and prints a state table. Nothing is installed or removed blind.

  2. 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 triggers AskUserQuestion instead of a guess.

  3. Plan stated before any question

    The skill writes out, in plain text, exactly what will change: which files, which paths, which settings.json entries — before asking anything.

  4. Only missing answers are asked

    Scope (Project / Global / Both) and budget (default 20 min) are asked via AskUserQuestion only if not already given in the arguments. Choosing Global surfaces the per-call cost in the question itself, not buried in a final report.

  5. File work delegated to hook-creator

    A single brewcode:hook-creator spawn per mode-and-scope combination copies the 2 hook files, merges or strips settings.json entries, and writes or edits agent-deadline.json. Both scopes at once means two spawns in one message.

  6. 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

ModeEffectHook filessettings.jsonConfigState
status (default, no args)report only
installwire + configurecopiedentries mergedwritten
disablepause enforcementkeptkeptenabled: falsekept
enableresume enforcementkeptkeptenabled: truekept
uninstallunwiredeletedentries strippedkeptkept
purgefull wipedeletedentries strippeddeleteddeleted

The two hooks

HookEventBehavior
agent-deadline-guard.mjsPreToolUse 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.mjsSubagentStopDeletes 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

ScopeHooks dirsettings.jsonConfig
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
}
KeyMeaning
enabledmust be exactly true; anything else turns enforcement off
defaultMinutesbudget applied to every agent type; default 20
byAgentTypeper-type overrides, e.g. {"Explore": 10}; empty object = one uniform limit
hardStopRatiooptional, 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 Bash call is not observed until its next call — cap long commands separately with BASH_MAX_TIMEOUT_MS.
  • agent_type for plugin agents is unverified. Whether the payload reports brewcode:developer or developer for a plugin agent has not been confirmed against a live payload. A byAgentType key that doesn’t match silently falls back to defaultMinutes — 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, TaskOutput pass 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

Use /brewtools:plugin-update to check and update the brewcode plugin suite in one command. See the FAQ for details.