Gates check the work. Hooks and walls are about the run: hooks are told what is happening, walls decide what may happen.
Hooks
A hook is a file droost runs when something happens in a run: post to chat, update a dashboard, write a log.
hooks:
run_start: droost/hooks/notify.php
phase_begin: droost/hooks/notify.php
phase_end: droost/hooks/notify.php
run_complete: droost/hooks/notify.phpIt gets JSON on standard input with phase, run_id and work_item (the
ticket, if there is one), and DROOST_PROJECT_ROOT in the environment.
<?php
// droost/hooks/notify.php: one line per event, to a log.
$event = json_decode(stream_get_contents(STDIN), true);
file_put_contents(getenv('DROOST_PROJECT_ROOT') . '/droost/runs.log',
date('c') . " {$event['run_id']} {$event['phase']}\n", FILE_APPEND);
echo "logged\n";The first line it prints is shown to the agent. A hook can never stop or fail a run.
Walls
A wall is asked before the agent uses a tool whose name matches a pattern. It lets the call through or refuses it, with a reason the agent sees.
walls:
tracker_writes: { tools: "^mcp__atlassian__(create|edit|transition)", run: droost/walls/tracker-writes.php }It gets JSON on standard input with tool_name, input (the arguments) and
project_root. Exit 0 allows the call; exit 2 refuses it, and what you
print on standard error is the reason. A wall that crashes, is missing or
takes longer than 10 seconds refuses: it never says yes by accident.
<?php
// droost/walls/tracker-writes.php: the agent may read the tracker, not write to it.
$call = json_decode(stream_get_contents(STDIN), true);
fwrite(STDERR, "Ask a person to make this change: {$call['tool_name']}\n");
exit(2);A wall is asked about file edits, shell commands, MCP tools (mcp__…) and
the agent's questions to you: the calls the guard sees. It is not asked about
other tools, such as reading a file or fetching a web page. Walls work where
the guard does, which today means Claude Code.
Scripts only a person may run
List a script under operator_only: and the guard refuses it from the agent,
and tells the agent to hand it to you:
operator_only:
- droost/bin/publish-releasePanel tabs
Add a tab of your own to the run panel: a script that prints JSON.
panel:
tabs:
release: { title: Release, run: droost/panel/release.php }It gets project_root, tab and run (the open run, or null) on standard
input, and prints one object:
{ "badge": "2/3", "sections": [
{ "kind": "checklist", "title": "Before release", "items": [
{ "text": "Changelog written", "done": true },
{ "text": "Config exported", "done": true },
{ "text": "Translations updated", "done": false, "note": "fr missing" } ] } ] }A section's kind is text, markdown, fields (items: [{label, value}]),
checklist (items: [{text, done, note?}]) or list
(items: [{text, detail?, tag?, tone?}]). A .ts or .js file runs under
Node. droost workflow:panel --json shows the whole panel, your tabs
included.