starchild-ai-agent/official-skills11 files

Agent Hooks

Manage shell hooks — user scripts that run at agent lifecycle points to block, rewrite, or warn on actions, via the /hooks command.

Specification
Skill ID
starchild-ai-agent/official-skills/agent-hooks
Publisher
starchild-ai-agent
Repository
official-skills
Installs
0
Files
11
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

starchild-ai-agent/official-skills/agent-hooksInstalls these files
  • SKILL.md
  • templates/runtime_footer.py
  • templates/runtime_footer_selftest.py
  • templates/security_guard.py
  • templates/security_guard_selftest.py
  • templates/verify_code_changes.py
  • templates/verify_code_changes_selftest.py
  • templates/verify_commitments.py
  • templates/verify_commitments_selftest.py
  • templates/verify_publish_claims.py
  • templates/verify_publish_claims_selftest.py

What this skill tells the agent

Agent Hooks

Shell hooks let a user run their own script at fixed points in the agent's lifecycle — to block a dangerous action, rewrite an input or an outbound message, inject context into the model, or warn the user. The script can be written in any language; it talks to the agent over a simple JSON-on-stdin, JSON-on-stdout protocol.

Tools: read_file, write_file, bash

When to use

Reach for hooks when the user wants the agent to automatically enforce a rule or react to an event without being asked each time. Examples:

  • "Stop me from running rm -rf / destructive bash" → pre_tool_call block
  • "Never let a private key get pushed to Telegram" → on_outbound_message block
  • "Log every tool call for audit" → post_tool_call observe
  • "Remind the agent of X at the start of every model call" → pre_llm_call context
  • "Don't let the agent claim it published when it didn't" → on_completion_claim (in /goal) or on_stop (in normal chat)
  • "If the answer fails my quality check, make the agent redo it" → on_stop block

If the user just wants a one-off check, that's not a hook — hooks are for recurring, automatic lifecycle enforcement.

How configuration works (the agent does it end-to-end)

The agent installs and activates a hook with zero user copy-paste. Write the script, write the config entry, then call the loopback self-approve API — it flips the master switch on, approves the script for every event it's wired to, and hot-mounts it live (no restart). The user just tests it afterward.

curl -s -X POST http://localhost:8000/internal/runtime/hooks/approve \
  -H 'Content-Type: application/json' \
  -d '{"command": "/data/workspace/hooks/security_guard.py"}'
# -> {"ok": true, "events": [...], "mounted": N, "master_enabled": true}
⚠️ ALWAYS use an absolute path under `/data/workspace` in both the yaml command: and this call. Never a relative path like skills/agent-hooks/templates/security_guard.py: the bridge spawns the script with the server cwd (/app), so a relative path resolves to /app/skills/… — an empty dir — and every spawn fails. Because the bridge fails OPEN (a script it can't run = "continue"), the guard then silently protects nothing while /hooks list still shows it "mounted". To avoid this, the standard install copies the template into `/data/workspace/hooks/` and points the yaml there (see the workflow below). /app/skills is NOT the skills dir — the real one is /data/workspace/skills/ (a.k.a. /app/workspace/skills/ via symlink).
  • command MUST be the exact command: string from the shell_hooks.yaml entry (the absolute script path). The hook MUST already be declared in the yaml — approval flips a declared hook to live, it can't conjure one out of thin air.
  • The endpoint is loopback-only (same-uid-in-container trust boundary, same as .env reads). It auto-enables the master switch (enable_master defaults true), so the hook fires immediately.
  • Approval records the script's mtime, so a later edit surfaces as drift in /hooks list / /hooks doctor — a swap-the-script change stays visible.

This is the whole activation story for the user — there isn't a second step. After the call returns {"ok": true}, tell them it's live and to test it. Do not mention /hooks approve, /hooks on, or "two gates" — those are internal.

Fallback (older builds only): if the curl returns 404, this runtime predates the self-approve API — only then fall back to asking the user to paste /hooks approve <command> then /hooks on.

The two gates (both handled by the self-approve API — you don't surface them)

Internally a hook fires only when BOTH hold; the self-approve call above flips both in one shot, so the user never sees or types either:

  1. Master switch ON — shell_hooks.enabled: true in workspace/config/agent.yaml. The API auto-enables it (enable_master defaults true).
  2. Per-hook approval — the (event, command) pair is recorded in the allowlist with the script's mtime (so a later edit shows as "changed since approval" drift — a swapped script stays visible). The API approves every event the command is wired to.

These exist as a security boundary, not as a user step. Do NOT mention "approve" or "two gates" when explaining hooks to a user — just say you'll set it up and they can test it. (Manual /hooks on + /hooks approve exist only as the 404 fallback for older runtimes.)

The /hooks command

Plain text on web / Telegram / WeChat (no LLM, no cost):

CommandWhat it does
/hooks or /hooks listmaster switch state, config path, every hook + approval/health
/hooks on \/hooks offflip the master switch (hot mount/unmount, no restart)
/hooks doctorrun each approved hook against a synthetic payload, check JSON
/hooks approve <event> <command>approve + activate live (no restart)
/hooks revoke <command>revoke + detach live (no restart)
/hooks helpusage

Events (12) and what each can do

EventFiresCapabilitystdin gives the script
on_user_messagea user message arrives, before the model sees itblock / rewrite textmessage, channel
pre_tool_callbefore a tool runsblock / rewrite inputtool_name, tool_input
post_tool_callafter a tool runsobserve (log/metrics)tool_name, tool_result
transform_tool_resultresult before agent sees itappend a notetool_name, tool_result
pre_llm_callbefore a model callinject contextsystem, last_user_message, model
post_llm_callafter a model replyobserve / swapmodel
on_response_endfinal reply assembled, once per turnrewrite replyresponse, model, tokens, tool_names
on_stopturn boundary, after on_response_endblock → force a redoresponse, tool_names, stop_hook_active
on_outbound_messagebefore a TG/WeChat pushblock / rewrite outboundnotification, type
on_completion_claimagent claims a /goal doneblock → force a redogoal, summary, response, tool_names
on_session_startsession beginsobservestatus
on_session_endsession endsobserve / cleanupstatus

Every payload also includes event, session_id, agent_id, cwd.

The `event` field is the dispatch key. It names which lifecycle moment is firing (pre_tool_call, on_user_message, …). A multi-event script (like security_guard.py, one file wired to five events) reads event to decide which branch to run — no event in the payload means no branch matches, so the script falls through to "continue" (empty output = allow). The runtime always sets it; you only have to remember it when hand-crafting a test payload (see the dry-run step below). It is NOT something you put in shell_hooks.yaml — there the event: key tells the bus when to call you; the event field in the payload is the bus telling the script which moment it is.

The three "make the agent fix it" levers (don't mix them up)

These three fire near the end of a turn but have very different power — pick by what you need to happen when something's wrong:

EventPowerUse when
on_response_endrewrite only — edit the stored/forwarded reply (footer, redaction, mask). Cannot make the agent redo. Zero loop risk.You only need to change the text (mask a leaked key, add a cost footer).

Use this skill in a project

Open any RiverX project, search the Skills panel for starchild-ai-agent/official-skills/agent-hooks, and install it in one click.

Use this skill