Ask the human without stopping work. Use cactus instead of AskUserQuestion whenever a question can be posted now and collected later, whenever several decisions belong to one thread, whenever you are announcing what you will do anyway and only need a veto, whenever a command needs approval, or whenever a verify block or an ordered checklist should stay on the human's board across sessions. Triggers on "cactus", "ask the human", "park this question", "put it on the board", "I need a decision b...
Installs into .claude/skills of the current project.
Are you the author of Cactus?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/eighteyes-cactus-cactus)
---
name: cactus
description: Ask the human without stopping work. Use cactus instead of AskUserQuestion whenever a question can be posted now and collected later, whenever several decisions belong to one thread, whenever you are announcing what you will do anyway and only need a veto, whenever a command needs approval, or whenever a verify block or an ordered checklist should stay on the human's board across sessions. Triggers on "cactus", "ask the human", "park this question", "put it on the board", "I need a decision but keep going". Do NOT use for a question whose answer you need before the next keystroke and that has no sensible default; that is AskUserQuestion.
---
# cactus — the durable inbox between you and the human
## Choose your runtime instructions
This file is the shared Cactus workflow. Read the instructions for the host
running you:
- [Claude Code](CLAUDE.md) — Claude plugin hooks and Herdr identity.
- [Codex](CODEX.md) — Codex plugin hooks and the Codex session identity.
- [Grok](GROK.md) — webhook wake-up.
- [Claude Desktop and other MCP hosts](DESKTOP.md) — the verbs are
`cactus_*` tools, and answers are read at the fork.
The host-specific file changes only identity and wake-up mechanics. The row
semantics, authoring rules, and ownership rules below apply everywhere.
cactus is a SQLite inbox. You post a row, get a key back, and keep working. The
human answers in `cactus --tui` on their own schedule, from any project, and you
read the answer when you reach the fork. Rows survive the session, so the next
agent in this repository sees them too.
Full syntax lives in the tool, not here. Read it once per session before any
unfamiliar verb:
cactus --agent-help
## Preflight
command -v cactus >/dev/null && cactus where
`cactus where` prints the project the row will be filed under. Agent verbs see
only that project; if it is wrong, `cd` before asking.
## Workflow, required
1 ask post every decision the human makes here, not in chat;
--recommend LABEL --confidence L when you have a pick, -f
for every file the question is about, --agent on every row
2 wait a blocking ask (ask, run) waits for the human by default.
Post it as ONE backgrounded command (Bash run_in_background);
it waits, and its exit is your wake-up. No monitor.
3 work do everything the answer does not block while it waits
4 inspect on wake, `cactus get` the row (the background output holds
the answer); the next-turn frontier lists the rest
5 clear your own rows, by key, once acted on
`steer`, `notify`, `review`, `plan` and `data` never wait. `--no-wait` posts a
blocking row and returns at once; `--no-block` makes the row non-blocking.
A foreground `cactus ask`/`run` that would wait is refused by the plugin's
PreToolUse hook: rerun it with `run_in_background: true`.
## Blocked by a permission prompt
Do not stop and do not ask in chat. In Claude Code auto mode the plugin's
`PermissionDenied` hook has already posted the denied command as a `cactus run`
row in thread `denied`. Do not post it again: a second row is a second
approval, and the second run fails. Find the hook's row and wait on it with
one backgrounded call; its exit wakes you:
cactus list -s open -t denied --agent "$AGENT"
cactus get KEY --wait --agent "$AGENT" --json
Post the row yourself only when no `denied` row holds the command (a host
without the hook, or a prompt the hook did not see), also backgrounded:
cactus run "pnpm exec playwright install" --agent "$AGENT" --why "denied in auto mode"
`--why` becomes the row's context. The human approves or denies it from the
TUI; the wait exits with the answer, `approve` or `deny`. On approve, re-read
the row: `cactus get KEY --json | jq '.[0].result'`. A non-null `result`
means the TUI already ran it (exit code, a 50-line tail, a log path); act on
that. A null `result` after `approve` means the human approved from the CLI
and you run it yourself. Never run it before `approve`.
## Identity
`cactus ask` refuses a row without `--agent`. The session-start hook prints
the value to pass, resolved through herdr when the session runs in one. Outside
herdr, choose one stable value for the session and use it on every ask and
clear. A bare pane id is not an identity: it outlives the session and the next
occupant inherits your rows.
Clears are scoped by owner. `--thread`, `--here` and `--all` clear only rows
posted under the `--agent` you pass; another agent's key is refused; a row with
no owner clears by explicit key only.
## When cactus, when AskUserQuestion
AskUserQuestion the answer gates the next action and no default is safe
cactus ask the answer gates a later action; post now, work until then
cactus steer no gate: say what you will do, proceed, let a tap redirect
cactus run a command needs a yes before it runs
cactus review a verify block the human re-checks as the work changes
cactus plan an ordered checklist both sides tick
cactus notify an FYI the human dismisses; no answer expected
cactus data chunks the human copies; each copy is a verdict
There is no fork act. A fork in the conversation is an `ask` with two or three
`-c` options, one per direction, and it becomes a `steer` with `--chosen` the
moment one direction is the sensible default.
Parking the human on a question you could have answered yourself is the failure
mode. It is measured: `cursor.blocked` in `cactus feed` counts open blocking
rows per agent, for rows posted with `--agent`. Reach for `steer` first, `ask`
when proceeding under any assumption would waste the work, a waiting ask only
when the very next step depends on it; otherwise post it `--no-wait`.
## Batching
Post every question you can see now, under one thread, with `--no-wait`. Follow
up in the same thread with `-p KEY` when an answer opens a new question. A set
of taps is cheaper for the human than a drip of interrupts across an afternoon.
When work blocks on the batch, wait on all of it with one backgrounded call;
it exits once every key has settled (a blocking row leaves `open`; a
review/plan/data/steer/notify row gets a new verdict, tap, or clear), and that
exit is your wake-up:
cactus get q7 q8 q9 --wait --agent "$AGENT" --json
Otherwise leave the batch to the next-turn frontier.
## Authoring a row
Options are mile posts: two or three, mutually exclusive, each one a thing that
actually happens. Never "other". Split each on the first colon, label then
description.
Format the question itself for an 80-column terminal: it may use two or three
rendered lines, but no more. Cactus warns when an ask or edit wraps past three
lines; decompose a larger decision into follow-ups instead. Hard-wrap context
and choice descriptions at 80 columns, with blank lines between paragraphs.
cactus ask "Which auth backend?" \
-c "oidc: existing IdP" \
-c "local: bcrypt table" \
--context "Staging tenant is provisioned. Local means owning password reset. Default if unanswered: oidc." \
-f docs/auth-spec.md \
-t auth --agent "$AGENT" --recommend oidc --confidence high --why "tenant already exists"
A description may span lines. A line starting `+ ` is a pro, `- ` a con; the
rest is the summary. The card shows them as green and red marks under the choice.
-c $'oidc: existing IdP\n+ tenant already provisioned\n- couples us to IdP uptime'
`--context` carries what the human cannot see from the labels: what you tried
and what it cost, the numbers, what breaks under each option, what happens if
nobody answers. Never a restatement of the question, never reassurance.
`--recommend` (with `--confidence`) preselects the pick, so enter alone
submits it; the row still waits for the human. `--chosen` on a steer does not
wait: you proceed with it.
`--word SHORT` gives boards a stable label. Set it when a project has many rows
whose text starts the same way.
`-f PATH` attaches a file. Attach every file the question is about: the plan,
spec, diff, config, or draft the human would otherwise have to go find. They
open it from the card with `f` (view) or `F` (edit), or preview it inline
with `o` (a diff vs git HEAD when changed). Repeat for more;
`edit -f` replaces the list. A row about a file with no `-f` is a row the
human answers blind.
`--site URL` (http or https) gives the human a `w` key that opens the URL.
`edit --site ""` clears it.
## Steer: proceed, invite a veto
cactus ask "Using the staging tenant for the migration dry run" \
--act steer --chosen staging -t auth --agent "$AGENT" \
-c "staging: provisioned, disposable" \
-c "prod-shadow: real data, read only"
Then keep going. Before the irreversible step, re-read the row: a tap on the
other option redirects you.
## Block only when blocked
cactus ask "Safe to drop the legacy column?" --confirm --agent "$AGENT" --timeout 600
Run it with `run_in_background: true`. It prints the key at once, waits (default
timeout 3600s, `--timeout` overrides), and prints the answer on exit: that exit
is your wake-up. Exit 2 is the timeout: proceed on the default you stated in
`--context`, do not treat it as an error. A row the human
clears returns exit 0 with status `cleared`, which is a decline: check the
status, not just the exit code.
## Approve a command
cactus run "alembic upgrade head" --agent "$AGENT" -t ship --why "schema is one revision behind" --timeout 900 --json
Run it with `run_in_background: true`; it waits for the verdict and its exit
wakes you. `cactus run` posts the command as the row text, `--why` as its context. The
human sees the command and runs it from the TUI with `R` or `y`, which
records `result` on the row. The verdict labels are `approve` and `deny`.
Read `result` before doing anything: non-null means it already ran, null
after `approve` means run it yourself. `review --run` still attaches a
command to any other row when a verify block needs one.
## Persistent rows: review and plan
Both are born `live`, take a verdict every time the work is re-checked, and stay
on the board until cleared. Post them once at the start of a piece of work and
update them as it moves.
cactus ask "Does the build verify?" --act review -t ship --agent "$AGENT"
cactus review q8 --look-at "the diff" --run "pytest -q" --pass "0 failures" --fail "any failure" --then "tag the release"
cactus ask "Release steps" --act plan -t ship --agent "$AGENT"
cactus plan q9 --step build --step test --step tag
cactus plan q9 --done 1 # 1-based at the CLI
The latest verdict is `answer`; the full log is `answers`. Re-read rather than
cache: a human can undo a verdict and the row reads `open` again.
Read a review or plan row with `cactus get KEY --agent "$AGENT"`: that read is
what tells the human you heard the verdict (the card shows `sent`, then
`heard ✓`). After acting on a verdict, respond with `plan`, `review`, or `edit`
and `--agent "$AGENT"` (the card returns to normal), and `clear` the row when
the work is done.
## Data: hand over chunks
Also persistent. Each `-c` is one chunk — SQL, a command, a snippet — label
then body, split on the first colon; the body may be multi-line.
cactus ask "Backfill queries" --act data -t db --agent "$AGENT" \
-c "count: SELECT count(*) FROM orders WHERE backfilled IS NULL" \
-c "run: UPDATE orders SET backfilled = now() WHERE backfilled IS NULL"
In the TUI, a digit copies that chunk to the clipboard; each copy appends a
verdict naming the chunk's label, readable with `cactus get`. `d` retires the
row.
## Collect on the next turn
A backgrounded waiting ask wakes you when it exits, even on an idle session.
Rows posted `--no-wait`, and review/plan rows, come back through the
next-turn frontier, which shows answered, elaborated, and open rows. Use
`cactus get KEY --agent "$AGENT"` before acting on a review or plan verdict.
## Elaborate or decompose: the human wants the question changed
`e` on a row moves it to status `elaborate`. The next-turn frontier and
`cactus get` show its `hint` (what the human typed, or null) and
`instruction` (the hint, else: rewrite plainly, no jargon, add what you
tried, the numbers, what each option costs, what happens if nobody
answers). Rewrite the row in place; the key stays:
cactus edit q7 --agent "$AGENT" --context "..." [--text "..."] [-c "label: desc"]...
The row returns to `open`; your own `--agent` stream does not echo the
`edited` event back at you (q334). `edit` also works on any open or live row
you own without a request, so fix a typo or add a fact the moment you notice
it. `-c` replaces the choices and drops a `--recommend` that no longer names
one. A human `u` on an elaborate request reads as `withdrawn`: re-read the
row before rewriting.
`D` in the TUI asks for decomposition through the same `elaborate` event.
When its instruction says to decompose, do not edit the original row. Post
each smaller, independently answerable question as a follow-up (`-p q7
--no-wait`) with the same `--agent`; the follow-ups inherit its thread. Once
they are posted, clear the original row with `cactus clear q7 --agent
"$AGENT"`, then wait on the follow-ups as a batch (see Batching).
## Read back
cactus get q7 --json | jq '.[0]' get returns a list, one row per key
cactus list -s answered -t auth --json a thread's verdicts
cactus feed --json --here the whole actionable inbox, one document
The answer lives under `.answer`, not at the top level: `.answer.selected[]`,
`.answer.text`, `.answer.skipped`, `.answer.created_at`. The top-level `text`
is the question. `skipped` means the human saw it and chose not to decide;
act on your stated default.
Keys are `qN` within the project. Across projects (`--all`) use the row's
`ref`, `LABEL:qN`, which every verb accepts as a key.
## Decision records
Every answer, undo, verdict and clear rewrites one file per row in the
project, so the decision travels with the code. Commit it with the change it
governed.
.ai/cactus/qN-ID-SLUG.md decision record, rewritten on each answer, undo, verdict, clear; commit it
Set `CACTUS_RECORDS=0` in a test harness that runs from a real repository, or
the scratch rows write records into it.
## Retire
cactus clear q7 retire, keep the transcript
cactus clear --thread auth --agent "$AGENT" retire your rows in a thread
cactus clear --purge q7 delete
Clear what you have acted on. A stale open row is a question the human answers
for nothing.
## Exit codes
0 ok 1 error 2 --wait timed out 3 nothing matched
`cactus ask --wait` on a `--no-block` row, `steer`, `notify`, `review`,
`plan`, or `data` is exit 1. `cactus get KEY --wait` on one returns on the next
change: a new verdict, a tap, a clear.
## Webhook owners
Agents that use external webhook delivery need a per-agent webhook wake-up.
Read [GROK.md](GROK.md) for the external-agent recipe and
[WEBHOOK_SETUP.md](WEBHOOK_SETUP.md) for the map, smoke test, and
transport details. Do not set global `CACTUS_POKE` in a normal shell profile:
it overrides Herdr delivery for every agent.