Use when controlling Rove tasks, parallel coding attempts, hosted agent sessions, task lifecycle, or the daemon-owned issue tracker from a shell. Also the ONLY channel for messaging another agent session on this machine — `rove api send`, never a peer/MCP side channel.
Installs into .claude/skills of the current project.
Are you the author of Rove?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/sma1lboy-rove)
---
name: rove
description: Use when controlling Rove tasks, parallel coding attempts, hosted agent sessions, task lifecycle, or the daemon-owned issue tracker from a shell. Also the ONLY channel for messaging another agent session on this machine — `rove api send`, never a peer/MCP side channel.
---
<!-- rove-skill-version: 54 — bump in lockstep with ROVE_SKILL_VERSION (src/lib/skill-install.ts). -->
# Rove shell control
Use `rove api` to manage local coding tasks. Managed Tasks created by API
automation own a git Worktree and branch plus Hosted PTY engine tabs; project
main and directory Tasks reuse existing directories. API automation works
without an open TUI; prompted `send` and `add` ensure a target engine tab.
## Inside a Rove session, Rove verbs come first
Check where you are before choosing how to delegate or parallelize:
```bash
test -n "${ROVE_TASK_ID:-}"
```
When that passes, you are an engine session Rove manages — `$ROVE_TASK_ID`
is your task, `$ROVE_TAB_ID` your tab. Coordination should then go through
Rove, not around it, because work routed through `rove api` gets what
ad-hoc subprocesses never do: its own Worktree and branch (no file
collisions with you), a sidebar row with live state the user can watch,
lifecycle tracking, and an explicit outcome contract.
- Parallel attempts of one prompt → `add --count N`, not N hand-rolled subagents.
- Delegating a scoped piece of work → `add --prompt`, not a raw `claude -p`
child the user cannot see or manage.
- Following up on a task you started → `send`; comparing → `collect`;
finished your own task and were spawned by another → a bare `send`
(no `--task-id`) replies to the DISPATCHER — the exact task + tab that
created you, recorded at creation (`.task.dispatcher` on `get-task`).
If that tab died it falls back to the dispatcher task's live canonical
engine tab; nothing alive fails loud (`DISPATCHER_UNREACHABLE`) — it
never spawns a new engine, so a failed reply is visible, not fake-ok.
- Messaging another agent session on this machine → `send`, and ONLY `send`
— never relay through the user, a side file, or a generic peer channel
(an MCP server offering to message "other instances" is exactly that
channel: it reaches a process, not a task, so nothing it delivers is
attributable, watchable, or replyable). Sent from
inside a Rove task, the prompt arrives prefixed `[ROVE PEER] from
"<title>" (task <id> — Rove agent skill /rove, read it once per session …)`,
so the receiver knows who is talking, that this skill is required reading,
and how to answer — the baked-in reply command is tab-precise
(`--task-id <sender> --tab <sender's tab>`), so peer conversations need
no coordinator and no human relay. That prefix is the contract: do not
strip it with `--plain` for
coordination messages (`--plain` is only for a verbatim paste the
receiver should treat as content, not conversation). Received a
`[ROVE PEER]` message yourself? Read this skill once per session. The
baked-in command identifies where a necessary reply goes; it is not an
instruction to acknowledge every message. Act on FYIs without replying.
- `send` carries text, but that text can carry FILES: peers share a
filesystem, so put the absolute path of a screenshot, log, diff, or any
artifact in the prompt and the receiver opens it with its own Read tool —
images included. An annotated screenshot beats a paragraph describing one;
prefer "see /path/to/shot.png — the arrow marks the broken card" over
re-narrating pixels. Paths under the sender's Worktree work too (worktrees
are world-readable locally); just never expect the receiver to WRITE
there.
- `dispatch` stays the dispatcher's verb (deliver-only into an
already-hosted session; never impersonate the user in someone else's
terminal).
Your own engine's in-context subagents remain fine for read-only
research/exploration inside your task — the boundary is WORK: anything that
edits files, runs long, or the user should be able to see and steer belongs
in a Rove task. Do not recursively fan out from a spawned task.
When the check fails, none of this applies — use `rove api` only if the
user asks for Rove by name.
## Vocabulary — what the user's words map to
| Term | What it is | Isolation it gives | Users also say |
|---|---|---|---|
| **Task** | one tracked workspace record — managed worktree, saved-project main, or existing directory | managed Tasks own files + branch; main/directory Tasks reuse files | "a task", "a new one", "a separate attempt" |
| **Worktree** | the isolated git working tree a managed Task owns (`.task.worktreePath`) — a filesystem address, never shown in the UI | — | "workspace", "this checkout", "this branch", "here" |
| **Terminal Tab** | one engine, shell, command, or content surface inside a Task | an engine tab has its own conversation, but every tab uses the SAME Task files | "tab", "chattab", "another chat", "a second agent on this" |
| **Split** | the tree that divides ONE Terminal Tab into several regions (the `pane-open` verb's unit; a leaf is not called a pane) | none — same session's screen, same files | "split it", "side by side", "put the logs next to it" |
Two of those colloquialisms are traps, so read them as INTENT, not as
product terms: in Rove's own vocabulary **Workspace** is the center Terminal
Tab region of the UI (CONTEXT.md), not a checkout, and **ChatTab** is
retired vocabulary for Terminal Tab. A user saying "in this workspace" means
the worktree they are looking at — answer the intent, keep writing the real
term.
They nest — **Task ⊃ Terminal Tab ⊃ Split** — and isolation drops at every
level down:
```text
new Task → own worktree + own branch (parallel work can't collide)
new Tab → own engine session, SAME files (a helper in the same checkout)
new Split → same session's screen, one tab divided (a monitor beside the work)
```
The distinction that decides every routing call: **a new tab shares the
worktree and branch; only a new task gets its own.** Two tabs in one task
edit the same files, so they can collide — that is a feature when the user
wants a helper in the same checkout, and a bug when they wanted parallel
attempts. A split isolates nothing at all: it is a layout, for watching
something (logs, `btop`, a test loop) next to the work — never the answer to
"do this work".
One boundary the isolation model does NOT cover: **never `git stash` in a
managed worktree.** The stash stack lives in the repo's common dir
(`.git/refs/stash`) and is shared by every linked worktree — two tasks that
stash in parallel can pop or drop each other's work. Commit instead; a
commit is per-branch and isolates exactly the way the model promises.
### Where does this work land?
Inside a Rove session (`$ROVE_TASK_ID` non-empty — check first: it is what
makes the tab/split rows addressable at all), match top to bottom and take
the first row that fits:
| The user says | Lands in | Command |
|---|---|---|
| "you do it", "just fix it", "change X to Y" | you, right here | no Rove verb — edit the files yourself |
| "try it N ways", "compare approaches" | N new tasks | `rove api add --repo "$PWD" --count N --prompt "…"` |
| "split", "side by side", "keep an eye on X while…" | a new region in the CURRENT tab | `rove api pane-open --command "…"` |
| names a tab: "tell the agent in tab 3" | that exact tab | `rove api send --task-id <id> --tab tab-3 --prompt "…"` |
| a LOCATION word: "in this workspace/worktree/checkout", "on this branch", "here", "same task" | THIS task, a NEW Terminal Tab | `rove api send --task-id "$ROVE_TASK_ID" --tab new --prompt "…"` |
| names an ENGINE for the same files: "let codex take over here", "try this one with claude instead" | THIS task, a new tab pinned to that engine | `rove api send --task-id "$ROVE_TASK_ID" --tab new --command codex --prompt "…"` |
| **anything else — no row above matched** | a NEW task — new worktree + branch | `rove api add --repo "$PWD" --prompt "…"` |
Order is the tiebreak: a count ("3 ways in this workspace") beats a location
word, and delegation language loses to "you do it" — the user asking YOU is
not asking for a fleet. Two more rules the table can't show:
- No location word at all ⇒ new task. It is the only routing whose isolation
cannot corrupt work-in-progress, so it is the safe default.
- "this repo" / "this project" are NOT location words — they name the repo,
not the checkout. Only worktree-scoped words route to a tab.
Outside a Rove session none of this applies: there is no "this task" to add
a tab to, so `add` (single or `--count`) is the only routing available.
### Know where you are before you route
```bash
echo "$ROVE_TASK_ID / $ROVE_TAB_ID" # who you are (empty = not a Rove session)
rove api get-task --task-id "$ROVE_TASK_ID" # .task.title, .task.branch, .task.id, .running, .tabs[]
# .task.worktreePath too — only if you are about to read/write its files
```
`get-task` is the per-task read that answers "what is my worktree, my
branch, and which sibling tabs exist" — `.tabs[]` carries each tab's `id`, `kind`,
`vendor`, `liveVendor`, `lastTitle` and `alive`, which is exactly the target list for
`send --tab`. A tab flagged `unregistered: true` is a live session the tab
snapshot lost; it is addressable like any other.
### Coordinating others? Start every turn with `context`
If you spawned tasks, begin each turn with one read instead of your memory —
between two of your turns a worker can finish, crash, stall on a permission
prompt or get its PR approved, and you witnessed none of it:
```bash
rove api context --repo "$PWD" --text
```
One line per task, **sorted so the first line is what needs a person next**.
The `group` on each row is DERIVED from that task's report, PR observation,
engine activity and tab liveness — it is NOT the `status` field, which a
crashed worker leaves reading `in_progress` forever:
| group | your move |
|---|---|
| `waiting-on-you` | answer it — permission prompt, quota wall, settled error, dead tab that delivered nothing |
| `landing` | merge it — PR open and approved |
| `ready-for-review` | read the diff — a report landed, nobody acted |
| `working` | nothing. An engine is producing output |
| `idle` | nothing. We looked; it is quiet |
| `unknown` | look (`get-task`, `read-output`). We could NOT look — this is not "idle" |
Never read `unknown` as `idle`, and never act on a `group` as if it were a
verification: `ready-for-review` means a worker CLAIMED something, so
`collect` and the actual diff still decide whether it lands. Drop `--text`
for structured rows (`group`, `rank`, `checkState`, `report`) when you are
going to branch on the fields rather than read them.
`context` also carries the unhandled attention inbox and the repo's newest
field notes — the same notes a fresh session here is handed, so brief your
workers from those and don't re-derive what is already written down.
### Refer to a task the way the user sees it
The sidebar row is the task's **title**; the line under it is its **branch**.
Nothing in Rove's UI ever renders `worktreePath`, so the directory name in it
(`~/.rove/worktrees/<repo>/marlin`) is a filesystem address the daemon picked
from an animal pool — the user has never read that word and cannot find it on
screen. **Never name a task by its directory** to a user or in a report.
Use the **title** when a human reads it, the **task id** (or its last six
characters) when you need it to be unique, and both when you need both:
- ❌ `marlin opened a PR` · `landed in zorilla` · `see mammoth's branch`
- ✅ `"Skill version guard…" (task …CWWA) opened PR #972`
- ✅ `succeeded: guard now fails the build (branch fix/skill-version-bump)`
`worktreePath` earns a mention only when the sentence is about files on disk —
`cd`, a path in a command, a file you edited.
## Found a defect in ANOTHER project? File a request, don't work around
The repos on this machine are deliberately interdependent — one project
using (and stumbling over) another is the normal case, not the edge case.
Each saved project can keep a **main task**: a long-lived resident agent on
that repo's own checkout. It is more than "task kind = existing directory" —
it is that project's standing inbox, addressable from any other repo's
session, and it dispatches what it receives.
So when work in repo A surfaces a defect in product B, the default is to
**file a request with B's main task — not to quietly patch around it in A**.
The test: would this bite someone else? A local workaround fixes only you;
a fix in B fixes everyone. Work around locally to unblock yourself if you
must, but file the report either way.
```bash
rove api list # find B's main task id
rove api get-task --task-id <their-main> # .tabs[] → the engine tab's id
rove api send --task-id <their-main> --tab tab-N --prompt "<report>"
```
Address the engine tab explicitly (`--tab tab-N` from `get-task`'s `.tabs[]`,
`kind == "engine"`): a long-lived main task's engine tab is often not
`tab-1`, and an un-addressed `send` may refuse with `NO_ENGINE_TAB` rather
than guess.
A report worth sending carries: **symptoms** (what happened, concretely, how
many times), **root cause** if you found it, **why it is worth fixing**
(who else it bites), and **suggestions without prescribing the
implementation** — the receiving project decides how to fix its own product.
**Send the report — never `add` a task into someone else's repo.** You hold
the problem-side context (symptoms, root cause, impact); only that project's
main agent holds the solution-side context (code structure, existing issues,
what's mid-flight, where this slots in the schedule). A task you compose
would bake YOUR guess at the fix into the brief, and that guess is usually
wrong — one field report here looked like "add a line to the docs" and
actually split into a docs fix plus a runtime-detection issue anchored in
files the reporter didn't know existed. **You raise the problem; their main
agent decomposes it into tasks.** That translation is the main task's whole
job, and it is why every project should keep one.
## Fresh worktrees start empty — install before you judge
A managed task's worktree is a **brand-new checkout**: no `node_modules`, no
build artifacts, nothing a lockfile promises. Two consequences:
- **A test failure in a fresh worktree may be fake.** Missing dependencies
masquerade as product bugs ("Could not resolve: react-dom/client" reads
like a regression, not a missing install). Before reporting any failure as
real, confirm the repo's install step ran — and when a failure looks
unrelated to your change, compare against the same command on the base
branch before believing it.
- **Repos with an install step should ship `.rove/init.sh`** — it runs once
per worktree, before the engine, in the worktree (per-user override:
`rove repo set --init-script`; inspect with `rove repo show`, which lists
`.rove/init.sh: absent` when unset). Working in a repo that lacks one and
you just paid the install tax? Suggest adding it.
## Discover before calling
Do not guess flags — but do not pay a round-trip for the ones you use every
turn either. These five carry almost all traffic:
```text
add --repo(REQ) --prompt --title --command --count --agents --activate
send --prompt|--prompt-file(REQ) --task-id --tab --command --plain
get-task --task-id(REQ) list (no flags)
collect --group <groupId> | --task-ids <csv> | --repo
```
Four names that have actually been guessed wrong here: `add --vendor` is
`--command`; `read-output --task` is `--task-id`; `dispatch --text` is
`--prompt` (`--text` belongs to `note`); `issue-list` has no `--state` at all
— filter its JSON yourself.
**[`references/api-flags.md`](references/api-flags.md) is every verb and flag.**
Find your section by what you were ASKED, not by Rove's noun for it:
| You are asked to … | Read section |
|---|---|
| schedule / recur / cron / "every morning" / "keep messaging one window" / 定时 / 每天自动 | routines |
| a GitHub issue, "file it upstream" / 提 issue | workitems |
| track work in Rove's own backlog, move a kanban card | issues |
| remember this for the repo, leave a field note / 记一笔 | drive |
| what did the agent do, per-turn cost, read its screen | read |
| make / rename / retarget / land / close a task | create / edit / lifecycle |
| materialize or adopt a checkout | worktree |
| which engines exist, send Rove product feedback | discover / feedback |
| an error code came back, `nextCommandArgs` | Error codes |
Reach for `schema` when the binary and that file disagree.
```bash
rove api schema --verb add # or --group create, --all
rove api <verb> --help
rove api engine-list # what you can launch, and with what command
```
Commands emit one JSON object; errors use
`{"error":{"message","code",...}}` on stderr. Common rejections also carry
`hint` (what to do) and `nextCommandArgs` (argv for the same `Rove`
executable — run `rove <args...>` verbatim to recover, e.g. `["api","list"]`
after `TASK_NOT_FOUND`). Add `--pretty` for readable output.
## Common operations
```bash
# Create one task and start its first engine turn.
rove api add --repo "$PWD" --title "focused title" --command claude \
--prompt "<complete scoped instruction>"
# Parallel attempts of the same prompt (hard cap 10; prefer 3-4).
rove api add --repo "$PWD" --count 3 --prompt "<prompt>"
rove api add --repo "$PWD" --agents claude:2,codex:1 --prompt "<prompt>"
# Follow up. Use an explicit id for unattended work; the active task can drift.
# From inside a Rove task this auto-prefixes [ROVE PEER] provenance
# (sender + reply command) — `add --prompt` wears it too, so a dispatched
# task's opening brief carries its reply address; --plain sends verbatim.
#
# A prompt with backticks, $vars, or quotes goes through --prompt-file, NEVER
# a double-quoted --prompt: in double quotes `rove api send …` is command
# substitution — the shell RUNS it and ships its output, and the words vanish.
# Single quotes block $ROVE_TASK_ID too, so there is no quoting that fits both.
rove api send --task-id <id> --prompt "<complete next turn>" # plain text only
rove api send --task-id <id> --prompt-file - <<'EOF' # anything else
<turn — backticks, $vars, quotes, multi-line, all verbatim>
EOF
# Reply home: no --task-id inside a dispatched task = the dispatcher's tab.
rove api send --prompt "succeeded: <one line> (branch <final branch>)"
# A task can hold several Terminal Tabs. `get-task` lists ONE task's tabs (the
# usual read before addressing one); `inspect` is the wider diagnostic — every
# task's snapshot plus daemon activity and live pty sessions.
rove api inspect --task-id <id>
rove api send --task-id <id> --tab tab-3 --prompt "<turn>" # exact alive tab
rove api send --task-id <id> --tab new --prompt "<turn>" # fresh engine tab
# Same worktree, DIFFERENT agent — the API twin of the TUI's ctrl+e pick. The
# engine is pinned to that tab (survives restarts, unaffected by a later
# set-command) and the task's own engine is left alone. --tab new only.
rove api send --task-id <id> --tab new --command codex --prompt "<turn>"
rove api get-task --task-id <id>
# One read for the whole round — the groupId `add --count` returned. Never
# hand-copy N task ids across turns: collect by group. Lost the groupId? Any
# sibling task's `.groupId` in `list` output recovers it.
rove api collect --group <groupId> --pretty
rove api list --pretty
```
`.running` is `true` / `false` / `null`. It means an ENGINE PROCESS is alive
in one of the task's engine tabs — a live shell, command, or content tab alone
does not count, and neither does an engine tab whose engine exited (the PTY
survives it as a login shell; each tab reports that as `engineAlive`). `null`
means the pty host could not be asked: "couldn't look", NOT "nothing is
running". **Never act on `null` as if it were `false`** — deleting a task on it
destroys a worktree that may hold live work. It is process truth, not
progress: a task whose work is merged and whose worker has signed off still
reads `true` until somebody deletes it (see "A task is finished when it is
GONE").
Omitting BOTH `--task-id` and `--tab` inside a task that has a dispatcher
targets that dispatcher's tab (see the reply rule above); otherwise the
target is the active task. Omitting only `--tab` targets a live engine tab
(`tab-1` first, then any surviving engine tab). **Trap: omitting only
`--task-id` while giving `--tab tab-N` inside a dispatched task delivers to
`tab-N` of the DISPATCHER's task** — the dispatcher's id fills in for
`--task-id`, but an explicit `--tab` is kept, so your `send --tab tab-3`
lands in the middle of another session's tab-3, not yours. Target your own
task's tab with `--task-id "$ROVE_TASK_ID" --tab tab-N`. Only when the task
has NO live session at all does `send`
auto-start the canonical engine in the task's worktree (`started: true` in
the result marks that fresh session). If live tabs exist but none resolves
as an engine, it refuses with `NO_ENGINE_TAB` — address one with `--tab
tab-N` or spawn one with `--tab new`; it never silently spawns a duplicate
engine. Its `hint` names `pty-list` — the live-PTY read (key, alive, pid,
command); use it when `.tabs[]` and reality disagree.
## Terminal panes
Split the workspace terminal the user is watching (tmux-style) or open a
separate command tab — the attached TUI performs it, so this is a no-op
headless:
```bash
# Split the focused tab; the pane runs the command through your login
# shell's `-ilc` (so it sees the same rc-exported PATH the engine tab does)
# and closes when it exits. Omit --command for an interactive shell.
rove api pane-open --command "btop"
rove api pane-open --direction down --command "watch -n1 git status -sb"
rove api pane-open --placement tab --title logs --command "tail -f app.log"
# Close panes you opened, by their --title (engine panes are never closed).
rove api pane-close --title logs
# Close one whole Terminal Tab by the id from `get-task .tabs[]`.
# This works with or without an attached TUI.
rove api tab-close --task-id <id> --tab tab-3
# Toast a one-liner in every attached Rove UI — surface "done / needs input /
# error" moments without touching any session (kinds get severity styling).
rove api notify --title "build green, artifacts in dist/" --kind done
```
Defaults: the caller's own task (`$ROVE_TASK_ID`, then the active task),
`--placement split`, `--direction right`. Alternate right/down to build a
grid; screen size bounds splitting — a split that would shrink any pane
below the minimum usable size (20×6 cells) falls back to a tab.
Panes land in the USER'S live workspace — open them when asked (monitors,
logs, dashboards), don't scatter panes for work `add` should own.
## Lifecycle
| Verb | Purpose |
|---|---|
| `rename --task-id ID --title T` | Rename a task |
| `set-branch --task-id ID --branch B` | Rename its branch |
| `set-command --task-id ID --command CMD` | Change the engine launch command for the next launch |
| `set-status --task-id ID --status S` | Set the lifecycle LABEL (`backlog`/`in_progress`/`in_review`/`done`/`canceled`/`error`). Cosmetic: the row, its Worktree, its branch and its engine all stay. `canceled` does NOT close or clean up anything |
| `pin --task-id ID [--pinned=false]` | Pin/unpin |
| `set-active --task-id ID` / `--none` | Change shared active task |
| `ensure-worktree --task-id ID` | Materialize without starting an engine |
| `land --task-id ID [--strategy merge\|squash] [--delete-branch] [--remove-worktree=false]` | Merge the task's branch into the base repo's current branch; the Worktree is removed by default (`--remove-worktree=false` keeps it). The branch always stays; dirty/self/base removals are refused, outcome in the result's `worktree` field |
| `delete --task-id ID [--force] [--delete-branch]` | Remove task + Worktree; the git branch stays unless `--delete-branch` (and `--force` never implies it) |
| `discover-adoptable --repo PATH` | Find untracked Worktrees |
| `adopt --repo PATH --worktree PATH` | Import a Worktree |
### "Close this task" means `delete`
`delete` is the ONLY verb that ends a task: it removes the row and its
Worktree, and the git branch survives as the durable record. `set-status
canceled` is a label — the row, Worktree, branch and engine all stay, so a
"close" done that way changes nothing the user can see. Reach for `delete`
whether or not the work merged; an unmerged branch is still on disk
afterwards, which is what makes this recoverable.
There is no "hide the row without deleting" verb (`archive` was removed).
**Deleting still needs the user to ask for it in that turn** — say what you
would remove and wait. `--delete-branch` (or `--force` on a dirty Worktree)
destroys the recoverable half and needs its own explicit authorization; the
two flags are never implied by one another.
## Issue tracker
Issues are daemon-owned, not repo files:
```bash
rove api issue-list --repo "$PWD" --pretty
rove api issue-create --repo "$PWD" --title "title" --body "context"
rove api issue-set-status --repo "$PWD" --id <n> --status done
rove api issue-update --repo "$PWD" --id <n> --title "new" --body "body"
rove api issue-update --repo "$PWD" --id <n> --task <taskId> # link; `--task none` unlinks
```
### Kanban semantics
The TUI and web render issues as a Backlog / In progress / Done board whose
columns derive from the issue's own lifecycle — do NOT move cards with
`issue-set-status doing`:
- **In progress** = the issue has a linked task; `issue-update --task <taskId>`
IS the move (typical flow: `issue-create` → `add` a task → link them).
- **Done** = `status done`; the daemon mirrors it automatically when the
linked task finishes.
- **Backlog** = everything else (`open`/`doing`/`hold`, unlinked).
## Choosing the engine (`--command`)
Rove picks an engine by COMMAND, not by a vendor name. `--command` on `add`
and `send --tab new` takes either an engine id from `rove api engine-list`
(`claude`, `codex`, …, plus any preset the user registered) or a full command
line Rove runs verbatim:
```bash
rove api engine-list --pretty # ids + the RAW command each runs
rove api add --repo "$PWD" --command "codex --search" --prompt "…"
```
**Nothing validates an engine's flags — that is your job.** Before dispatching
an unfamiliar engine or an unfamiliar flag, probe it yourself (`<cmd> --help`,
`<cmd> --version`) and only then compose the command. A bad command line
starts a session that dies or ignores you; Rove will not catch it for you.
**Model is a conscious either/or — default to WITHOUT.** Your own model
knowledge is training-data stale; the user's engine default is fresher than
your guess, and model ids you have never heard of are routinely valid.
- **Without a model (the default):** compose the command with no model flag —
the session runs on the user's own default for that engine. Choose this
whenever the user did not name a model this turn.
- **With a model (explicit request only):** the user named a model → pin it,
passing their string VERBATIM (`--help` first to confirm the flag exists;
never "correct" an unfamiliar model id to one you know).
```bash
rove api add --repo "$PWD" --command claude --prompt "…" # user's default model
rove api add --repo "$PWD" --command claude --model "<id as the user wrote it>" \
--prompt "…" # user named a model this turn
# `--model` is gated per engine (BAD_MODEL when the engine has no model flag);
# `engine-list` prints each engine's `models` as suggestions, never a closed set.
# `--tier swift|standard|deep` fills engine+model+effort from Settings → Auto
# effort instead — exclusive with --command/--model/--effort.
```
Omit `--command` to use the repo's default engine. `engine-list`'s `protocol`
field says how much Rove understands about an entry — `generic` means it
launches fine but Rove reads no history and pre-answers no trust dialog.
## Parallel-round rules
Spawn a parallel round (`add --count N` / `--agents`) only when the user
requests parallel approaches, comparison, or an explicit count.
Give each round a scoped prompt, report returned IDs, then use
`collect` to compare. Do not recursively fan out from spawned tasks. Do not
poll `send` in a tight loop or use it as casual chat; every call is a full
engine turn.
### Communicate at handoffs, not at every step
Default to one complete task brief and one final outcome. Send an interim
message only when it changes the recipient's next action: a blocking
dependency, a file-ownership or interface conflict, a scope correction, or
evidence that makes their current approach invalid. Resolve routine choices
within the assigned scope without asking the dispatcher.
Do not send receipt acknowledgements, skill-loaded notices, starting-work
announcements, routine progress, or acknowledgements of acknowledgements.
An incoming message does not by itself require a response. Answer explicit
questions once; bundle related findings and review corrections into one
message with artifact paths. Forward only the facts the recipient needs, not
the research trail or messages already delivered.
Before `send`, ask: does this unblock or change work now? If not, include it
in the final report. If a handoff contract has already been agreed, do not
reconfirm it. Avoid status pings; use a bounded read-only `get-task` or
`collect` when an actual coordination decision needs current state.
The dispatcher gives the user concise milestone updates; workers need not
relay those updates back to the dispatcher. Do not create another task
solely to coordinate or review a small, reversible change.
### Completion flows back through an engine tab (`send`)
Outcomes are explicit, never inferred — and they travel as a MESSAGE to the
spawning agent's engine tab, not as stored state nobody reads.
**Worker side** — a task created from inside another Rove task records its
dispatcher (the creating task + tab); when the work is finished, a bare
`rove api send --prompt "<succeeded|failed>: <one line> (branch <final
branch>)"` routes the outcome back to that exact tab. Include the final
branch name — the spawner needs it to `land`. Use the BARE form: an explicit
`--task-id <spawner>` skips dispatcher routing and lands on that task's
canonical engine tab, which can be a different agent's session.
**Name your branch before you report it.** A new task starts on an
auto-generated placeholder branch (`new-task`, `duck`, …); you are the only
party who knows what the work turned out to be. Once the shape is clear,
rename it to a short descriptive name in this repo's own convention:
```bash
rove api set-branch --task-id "$ROVE_TASK_ID" --branch <descriptive-slug>
```
Do this while you work, not at the end — the branch name is what the user
reads in the sidebar to tell your task from its siblings.
**"Succeeded" means COMMITTED.** Green tests in your working tree are not a
deliverable — the only thing `land` can merge is commits on your branch.
Before reporting success: `git status` clean, your work committed with a
real message (you wrote the code; you write its message). A worker that ran
everything, passed everything, and committed nothing has delivered nothing —
that exact mismatch has shipped empty merges before.
Rove checks this at the moment you claim it: a `succeeded:` report from a
managed task whose branch has **0 commits** is refused with
`EMPTY_SUCCESS_REPORT` and never reaches the coordinator. Commit, then send.
When the task genuinely produced no commits — an investigation, a review, a
question answered — say so explicitly with `--allow-empty`:
```bash
rove api send --allow-empty --prompt "succeeded: no bug — root cause is upstream, see notes"
```
**"CI is green" means `checkState: passing`.** A local test run is not CI: it
does not run the other jobs, the other platform, or the merge gates. Rove
polls your PR's checks and keeps the answer on the task —
```bash
rove api get-task --task-id "$ROVE_TASK_ID" # .task.prStatus.checkState
```
`none` (no PR yet) / `pending` (still running) / `passing` / `failing` /
`unknown`. Anything but `passing` is not green. Report what the field says,
not what your local run implied.
**Coordinator side** — do NOT block or poll. Keep working (or end your
turn); each worker's outcome arrives in your chat as a `[ROVE PEER]` message
with its task id. What arrives is the worker's claim, not Rove-verified —
verify the winner's actual diff before landing. Silence never proves a
worker died (it may be mid-turn or stuck on a permission prompt): peek with
`collect`/`get-task`, nudge with `send`, and never mark a silent task failed
or auto-retry it.
### A task is finished when it is GONE
The dispatcher closes tasks. A worker cannot delete itself, so when it reports
"done" the round is not over — its engine is still running, its Worktree still
holds a branch, and its row is still in the sidebar.
None of these mean a task is finished:
| Looks final | What it actually is |
|---|---|
| the worker said "done" | a message; it has no verb to remove itself |
| its PR is MERGED | the code landed; the task did not move |
| its issue is `status done` | a field in a different store |
| `set-status canceled` | a label — see Lifecycle above |
**The only evidence is that `rove api list` no longer shows it.** Check the
task, not the paperwork:
```bash
rove api get-task --task-id <id> # .running true = its engine is still alive
rove api delete --task-id <id> --wait
```
`--wait` is what turns "queued" into an answer; without it the call returns
before the Worktree is gone.
Before ending a round, sweep for both halves — they drift apart:
```bash
rove api list # tasks whose work is done but which are still here
git worktree list # directories no task claims (delete finds tasks by id,
# so a stray one is only reachable via git)
```
A worker that has taken over `main` in its Worktree will block the dispatcher's
own checkout — a release cannot even start until that task is closed.
### Closing a round
After comparing attempts, finish the round instead of leaving tasks behind:
```bash
# Land the winner: merge its branch into the base repo's CURRENT branch.
# Verify the base checkout is on the intended branch first.
rove api land --task-id <winner>
# Delete the losers: task + worktree go away, the git branch survives
# (recoverable). NOT --delete-branch — that destroys history and needs
# explicit user authorization.
rove api delete --task-id <loser1>
rove api delete --task-id <loser2>
```
`land` refuses a dirty base checkout; on merge conflict it aborts cleanly and
returns the conflicted files for manual resolution. `delete` removes a
loser's Worktree but keeps its branch (recoverable); `--delete-branch`
destroys the history and is never part of closing a round without explicit
user authorization.