Agent Deadline Setup — soft budget for subagents

sonnet status / install / upgrade / enable / disable / uninstall / purge project | global opt-in user-invoked

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-setup 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 in the canonical set (install, upgrade, enable, disable, 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 extras [project|global] and [minutes] come after the mode word.

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-setup install
Want the same budget everywhere on this machine/brewtools:agent-deadline-setup install global 30
Pick up a new hook version after a brewtools update, same budget/brewtools:agent-deadline-setup upgrade
Check what is currently installed without changing anything/brewtools:agent-deadline-setup
Temporarily stop enforcing without removing files/brewtools:agent-deadline-setup disable
Fully remove hooks, settings entries and config/brewtools:agent-deadline-setup purge

Example

/brewtools:agent-deadline-setup 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, upgrade, enable, disable, 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/upgrade/uninstall/purge) need one; config value changes (enabled, minutes, overrides, hard-stop ratio) are read live, no restart.

Technical details

Modes

ModeEffectHook filessettings.jsonConfigState
status (default, no args)report only
installwire + configurecopiedentries mergedwritten
upgradere-emit from the current plugin version, budget preservedre-copiedentries re-mergedvalues preservedkept
enableresume enforcementkeptkeptenabled: truekept
disablepause enforcementkeptkeptenabled: falsekept
uninstallunwiredeletedentries strippedkeptkept
purgefull wipedeletedentries strippeddeleteddeleted

Canonical order, shared by every -setup skill: status | install | upgrade | enable | disable | uninstall | purge. upgrade asks nothing — it reads defaultMinutes, byAgentType and hardStopRatio back out of the existing config and replays the install for that scope, so a plugin update finally reaches the project with the same budget. A disabled setup stays disabled; a scope with nothing installed is routed to install.

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,
  "version": "X.Y.Z",
  "generated_by": "brewtools:agent-deadline-setup",
  "last_updated": "YYYY-MM-DD"
}
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
version / generated_by / last_updatedstamped by install/upgrade; /brewcode:setup-status reads them to flag a stale install

Budget resolves as byAgentType[agent_type] ?? defaultMinutes, read fresh on every hook call.

Note

enabled here is opt-in polarity: absent or anything but true means OFF. That is the opposite of agent-router-setup (only an explicit false disables) and brewdoc:docsync-setup (an absent key means enabled) — the three do not behave alike.

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:skill-creator or skill-creator 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.

Version and ownership

install/upgrade write agent-deadline.json’s version, generated_by: "brewtools:agent-deadline-setup" and last_updated at top level, alongside the behavior keys. agent-deadline-guard.mjs and agent-deadline-cleanup.mjs each carry a matching // brewcode-meta: version=X.Y.Z generated_by=brewtools:agent-deadline-setup comment on line 2. upgrade re-stamps all three, status surfaces a stale install by comparing them against the installed plugin version.

📄

Brewtools overview

All brewtools skills and agents in one place.

⚙️

Manager Setup

Installs a HARD wall that blocks main-session edits and forces subagent delegation — pairs well with a per-agent deadline.

📋

Setup Status

Read-only dashboard across every -setup skill — installed, stale or missing, with the hand-run command for each.

🔗

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.