Agent Orchestration
Hook
Hooks
What agentsmith runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Install
$ npx -y skills add PromptPartner/agentsmith --agent claude-codeShips with agentsmith. Installing the plugin gets these hooks.
Where it lives
- hooks/context-budget-nudge.shGitHub
Read the script
#!/usr/bin/env bash # Stop hook — BEST-EFFORT context-budget nudge (opt-in, experimental). # # HONEST CAVEAT: no Claude Code hook receives the live context-% (only the statusline does), so # this reads the % that config/statusline.py persisted to a temp file. That makes it # inherently fragile — the file can be stale (statusline hasn't rendered since the last turn) or # missing (statusline not installed / crashed). The RELIABLE handoff path is the "handoff" # keyword (hooks/handoff-on-keyword.sh) and the human-watched ctx:NN% gauge. Treat this as a # backstop, not a guarantee. See hooks/README.md. # # Behaviour: when an operator-configured context USED threshold is reached, it nudges ONCE per # session (a marker file prevents a block-loop), asking the agent to safe-state + write a recall # prompt before stopping. # # No percentage is a reliable universal quality boundary: behavior varies by model, task, prompt # position, and conversation history. The hook is therefore disabled until HANDOFF_PCT_THRESHOLD # is set to an integer from 1–100. Treat that value as a personal workflow cue, calibrated on your # own tasks. Signals older than 300 seconds fail open; tune that bounded freshness window with # HANDOFF_SIGNAL_MAX_AGE_SECONDS (1–3600). # # Wire it (global ~/.claude/settings.json): # "hooks": { "Stop": [ { "hooks": [ # { "type": "command", "command": "bash ~/.claude/hooks/context-budget-nudge.sh" } ] } ] } set -euo pipefail command -v jq >/dev/null 2>&1 || exit 0 THRESHOLD="${HANDOFF_PCT_THRESHOLD:-}" MAX_SIGNAL_AGE="${HANDOFF_SIGNAL_MAX_AGE_SECONDS:-300}" input=$(cat) sid=$(printf '%s' "$input" | jq -r '.session_id // empty' 2>/dev/null) || exit 0 # Invalid configuration and unscoped events must stay silent. Falling back to a shared # "default" file can leak a percentage between sessions and produce a false handoff cue. [ -n "$THRESHOLD" ] || exit 0 [[ "$THRESHOLD" =~ ^[0-9]+$ ]] || exit 0 [ "$THRESHOLD" -ge 1 ] 2>/dev/null && [ "$THRESHOLD" -le 100 ] 2>/dev/null || exit 0 [[ "$MAX_SIGNAL_AGE" =~ ^[0-9]+$ ]] || exit 0 [ "$MAX_SIGNAL_AGE" -ge 1 ] 2>/dev/null && [ "$MAX_SIGNAL_AGE" -le 3600 ] 2>/dev/null || exit 0 [[ "$sid" =~ ^[A-Za-z0-9._-]{1,128}$ ]] || exit 0 pf="${TMPDIR:-/tmp}/claude-ctx-${sid}.pct" marker="${TMPDIR:-/tmp}/claude-ctx-${sid}.nudged" [ -f "$pf" ] || exit 0 # no signal yet → do nothing [ -f "$marker" ] && exit 0 # already nudged this session → don't loop # A statusline may stop rendering or a runtime may reuse a session identifier after recovery. # Never act on a side-channel value older than five minutes (configurable, capped at one hour). if mtime=$(stat -c %Y "$pf" 2>/dev/null); then # GNU/Linux : elif mtime=$(stat -f %m "$pf" 2>/dev/null); then # BSD/macOS : else exit 0 fi now=$(date +%s 2>/dev/null) || exit 0 [[ "$mtime" =~ ^[0-9]+$ ]] && [[ "$now" =~ ^[0-9]+$ ]] || exit 0 age=$((now - mtime)) [ "$age" -ge 0 ] 2>/dev/null && [ "$age" -le "$MAX_SIGNAL_AGE" ] 2>/dev/null || exit 0 pct=$(tr -d '[:space:]' < "$pf" 2>/dev/null) || exit 0 [[ "$pct" =~ ^[0-9]+([.][0-9]+)?$ ]] || exit 0 pint=${pct%%.*} [ "$pint" -le 100 ] 2>/dev/null || exit 0 if [ "$pint" -ge "$THRESHOLD" ]; then output=$(jq -n --arg p "$pint" '{decision:"block", reason:("Context is at " + $p + "% used — at your configured handoff cue. Context reliability varies by model, task, and conversation history; this percentage is a personal workflow heuristic, not a quality boundary. Before you stop: bring the working tree to a safe state, write a handoff note, and output a ready-to-paste recall prompt with the item, branch, completed work, exact next step, and gotchas. Then stop.")}') || exit 0 : > "$marker" printf '%s\n' "$output" fi exit 0 - hooks/handoff-on-keyword.shGitHub
Read the script
#!/usr/bin/env bash # UserPromptSubmit hook — RELIABLE handoff trigger for Claude Code and Codex. # When the user says "handoff" or "wrap up", inject the handoff protocol so the agent # safe-states and emits a recall prompt before context runs out. This path needs NO # context-% visibility — it keys off the prompt text, which the hook always receives. # See core/50-git-and-handoff and hooks/README.md. # # Wire it (global ~/.claude/settings.json): # "hooks": { "UserPromptSubmit": [ { "hooks": [ # { "type": "command", "command": "bash ~/.claude/hooks/handoff-on-keyword.sh" } ] } ] } set -euo pipefail command -v jq >/dev/null 2>&1 || exit 0 # no jq → silently no-op, never block the prompt input=$(cat) prompt=$(printf '%s' "$input" | jq -r '.prompt_text // .prompt // empty' 2>/dev/null) || exit 0 shopt -s nocasematch 2>/dev/null || true if [[ "$prompt" =~ handoff || "$prompt" =~ wrap[[:space:]]+up ]]; then context='HANDOFF REQUESTED. Before anything else: (1) bring the working tree to a SAFE STATE — commit or stash so nothing half-edited is lost; (2) write a durable handoff note (memory and/or the project tracker); (3) output a ready-to-paste fresh-chat recall prompt that carries the issue/branch IDs, what is done (commit SHAs / PR links / merge state), the EXACT next step, and the gotchas/decisions a fresh session would otherwise re-derive. Keep it tight; do this before any new work.' # Current Claude Code and Codex both require event-specific context beneath hookSpecificOutput. # Preserve the pre-current-Claude prompt_text/top-level shape for existing installations that # still send it. Unknown/malformed input stays fail-open: it never blocks the user's prompt. legacy_claude=$(printf '%s' "$input" | jq -r 'has("prompt_text") and (has("prompt") | not)' 2>/dev/null) || exit 0 if [ "$legacy_claude" = true ]; then jq -n --arg c "$context" '{additionalContext:$c}' else jq -n --arg c "$context" '{hookSpecificOutput:{hookEventName:"UserPromptSubmit", additionalContext:$c}}' fi fi exit 0 - hooks/ui-design-reminder.shGitHub
Read the script
#!/usr/bin/env bash # PreToolUse nudge — remind the agent to consult DESIGN.md when it edits UI (opt-in, fail-safe). # # Fires at most ONCE per session, and only when BOTH hold: # • a Claude Edit/Write/MultiEdit or Codex apply_patch targets a UI file — *.tsx *.jsx *.vue # *.svelte *.css *.scss *.less *.astro, or a path under a components/ or ui/ directory; AND # • a DESIGN.md exists at the project root (so backend/CLI/library projects never see it). # It NEVER blocks the edit and NEVER auto-approves it: it only injects a one-line reminder via the # PreToolUse `additionalContext` field, then lets the normal permission flow proceed. Every surprise # — no jq, malformed input, non-UI path, no DESIGN.md, already nudged — is a silent no-op. # # Model: hooks/context-budget-nudge.sh (once-per-session marker, jq-guarded, fail-open). # # Wire it (global ~/.claude/settings.json): # "hooks": { "PreToolUse": [ { "matcher": "Edit|Write|MultiEdit", "hooks": [ # { "type": "command", "command": "bash ~/.claude/hooks/ui-design-reminder.sh" } ] } ] } # # Hook stdin/output schemas can drift between runtimes; this stays fail-open (a bad read or an # unknown field → no-op, never a blocked edit). Claude file tools provide tool_input.file_path; # Codex apply_patch provides patch text in tool_input.command. Both accept # hookSpecificOutput.additionalContext with exit 0 for normal flow. set -euo pipefail command -v jq >/dev/null 2>&1 || exit 0 input=$(cat) tool=$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null) || exit 0 case "$tool" in Edit|Write|MultiEdit) fp=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty' 2>/dev/null) || exit 0 ;; apply_patch) # Codex sends the whole patch as a string. Extract every affected path, then choose the first # UI path; a backend file appearing first in a multi-file patch must not hide a later UI edit. paths=$(printf '%s' "$input" | jq -r ' .tool_input.command // empty | split("\n")[] | select(test("^\\*\\*\\* (Add|Update|Delete) File: |^\\*\\*\\* Move to: ")) | sub("^\\*\\*\\* (Add|Update|Delete) File: "; "") | sub("^\\*\\*\\* Move to: "; "") | sub("\\r$"; "") ' 2>/dev/null) || exit 0 fp="" while IFS= read -r candidate; do candidate=${candidate//\\//} case "$candidate" in *.tsx|*.jsx|*.vue|*.svelte|*.css|*.scss|*.less|*.astro|*/components/*|*/ui/*) fp="$candidate"; break;; esac done <<< "$paths" ;; *) exit 0 ;; esac [ -n "$fp" ] || exit 0 fp=${fp//\\//} # normalise Windows backslashes so dir globs match case "$fp" in *.tsx|*.jsx|*.vue|*.svelte|*.css|*.scss|*.less|*.astro|*/components/*|*/ui/*) ;; *) exit 0 ;; # not a UI file → silent esac # The self-gate: a DESIGN.md at the project root. No design system declared → stay silent, so backend # projects never see this. Check the session cwd first, then the git root (covers a subdir cwd). cwd=$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null) || exit 0 [ -n "$cwd" ] || cwd=. root="$cwd" if [ ! -f "$root/DESIGN.md" ]; then gr=$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null) || gr="" [ -n "$gr" ] && root="$gr" fi [ -f "$root/DESIGN.md" ] || exit 0 # Once per session (marker keyed by session_id): a screen's worth of edits nudges once, not N times. sid=$(printf '%s' "$input" | jq -r '.session_id // empty' 2>/dev/null) || exit 0 marker="${TMPDIR:-/tmp}/agentsmith-uidesign-${sid:-default}.nudged" [ -f "$marker" ] && exit 0 : > "$marker" 2>/dev/null || true jq -n --arg f "$fp" '{ hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: ("Editing UI (" + $f + "). This project declares a design system in DESIGN.md — read it and match it (colors, type, spacing, components, states) before changing UI. If DESIGN.md still reads \"[TODO]\", establish the design system first. Update DESIGN.md in the same change when you add or restyle a component.") } }' exit 0
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
Ships withagentsmith
Proof before done. Move from vibe coding to agentic engineering. Give Claude Code or Codex your project rules, a definition of done, and checks that produce evidence.
Get the whole plugin

