Docs navigation
Command Reference

brainbase task

Dispatch work to an agent and read what happened. create starts a task and its first run; list, get and logs are read-only. Every subcommand takes --json and works without a TTY, so the whole group is usable from a script or a CI step.

brainbase task create

Create a task on an agent and start its first run. Without --agent the agent claimed in brainbase.agent.yaml is used, so a linked folder needs only a message.

bash
brainbase task create --message "triage the open PRs"
brainbase task create --agent <agent-id> --message "triage the open PRs"
brainbase task create --message "run the smoke suite" --wait
brainbase task create --message "replay yesterday's ticket" --version v11
brainbase task create --message "run the smoke suite" --metadata env=staging --metadata owner=ci
brainbase task create --message "run the smoke suite" --wait --timeout 900 --json
text
Task ID: 09d61d87-311a-414e-b6e2-76376b5eaf21
Agent ID: 2ff01836-b183-478b-89d5-cd2f77118c25
Status: initializing
First run accepted.
FlagDescription
--message <text>Required. The first user message.
--agent <id>Override the agent claimed in brainbase.agent.yaml.
--title <text>Optional task title.
--model <id>Model override for this task only.
--version <version>Run a saved version of the agent: 12, v12 or a version id. Overrides a pin for this task. With --json, the output adds agent_version_id and version_number.
--interface <id>Open the task as a build conversation for this interface. Without --agent, the interface's agent is used.
--metadata key=valueLabel the task. Repeatable. Splits on the first =; each key once. Find labelled tasks with task list --metadata.
--waitBlock until the task reaches a terminal status, then exit accordingly.
--timeout <secs>Give up waiting after <secs> and exit 1. Requires --wait.
--jsonEmit one JSON object and nothing else.

Without --wait the command prints the task id and exits immediately — the run continues on the platform. With --wait it polls the task until it settles, printing each status change, and its exit code reports the outcome.

--version looks the version up before creating anything, so an unknown version creates no task, and the output names the version the task was asked to run. Like a pin, it freezes the version's instructions, entrypoint, installed skills, tools, playbooks, harness and default model, while secrets, credentials, machine settings, permission rules, connections and skill contents stay current. Only your own login or token can choose a version.

Exit codes with --wait

CodeMeaning
0The task finished as success.
1The task finished as fail or need_more_info, or --timeout elapsed.
130You interrupted the wait with Ctrl-C. The task keeps running.
need_more_info is not a pass
A task ends as need_more_info when the agent stopped mid-work and needs something from you — it also covers a run that hit its token or turn budget. --wait exits 1 on it, so a CI gate does not go green on work that never finished. The same applies to --timeout: giving up waiting is not the same as observing a pass.

Under --json --wait the command prints exactly one line, after the task settles, carrying the final status alongside the exit code it used.

json
{
  "task_id": "09d61d87-311a-414e-b6e2-76376b5eaf21",
  "agent_id": "2ff01836-b183-478b-89d5-cd2f77118c25",
  "status": "success",
  "outcome": "terminal",
  "exit_code": 0
}

outcome distinguishes why the wait ended: terminal (the task settled), timeout (--timeout elapsed) or interrupted (Ctrl-C).

brainbase task list

Recent tasks you can reach, newest first. Metadata only — pass an id to task get for the detail.

bash
brainbase task list
brainbase task list --agent <agent-id> --limit 20
brainbase task list --json
brainbase task ls --interface <interface-id>
brainbase task list --metadata env=staging --metadata owner=ci
text
success         nightly smoke suite
  09d61d87-311a-414e-b6e2-76376b5eaf21
  agent 2ff01836-b183-478b-89d5-cd2f77118c25 · created 2026-08-31T21:39:53.205552

running         triage the open PRs
  b2d629ae-abbe-4d50-b88d-2f35acb77323
  agent 2ff01836-b183-478b-89d5-cd2f77118c25 · created 2026-08-31T22:04:11.882301
FlagDescription
--agent <id>Only this agent's tasks. Omitted, every task you can reach.
--interface <id>Only this interface's build conversations. A plain list hides them.
--metadata key=valueOnly tasks carrying this label. Repeatable; a task must carry every label given. A key containing : cannot be filtered on.
--limit <n>Cap the page. 1–200; the server default is 50.
--jsonEmit the task array and nothing else.

brainbase task send

Send a follow-up message to a task, including while the agent is still running. This is how you answer an agent that stopped to ask a question, for example a build conversation started by brainbase interface build.

bash
brainbase task send <task-id> "Use the ticket owner's display name"
FlagDescription
--input-id <uuid>Recover a send whose answer was lost, using its original id and the identical message. Omit it to send a new follow-up.
--generation <n>When retrying, pass the generation the first attempt printed. Omitted, a fresh one is read for a new send.
--jsonEmit the receipt, including expected_generation, and nothing else.

The command exits 0 when the message was queued, dispatched, submitted or completed, and 1 when it is unconfirmed, cancelled, failed, or the request errored.

brainbase task stop

Stop a task's running execution and cancel any follow-ups still waiting to run. A cancelled follow-up keeps its text, but a later message does not replay it.

bash
brainbase task stop <task-id>
brainbase task stop <task-id> --generation 3

Before stopping, the command prints the task's generation and the exact command to retry with on stderr. A stop covers the task as it stood at that generation, so a follow-up sent after it is not cancelled. If a stop is interrupted or not confirmed, run the printed command, with the same --generation, rather than a fresh stop that could cancel newer work. A turn that is still starting or finalizing cannot be stopped yet: wait a second and retry the printed command. Status is the task's status once the stop has landed.

text
Task ID: 4aac86b3-7c1e-4f5a-9d2b-3e8f6a1c0b57
Status: success
Stop generation: 3
Cancelled follow-ups: 0
Follow-ups covered by this Stop are cancelled. Their text is retained; a new instruction will not replay them.
FlagDescription
--generation <n>The generation to stop at. Pass the one a previous attempt printed when retrying; omitted, the current one is read first.
--jsonEmit the stopped task record with expected_generation and cancelled_inputs, and nothing else.

The command exits 0 when the stop was confirmed, and 1 when it was refused, failed or could not be confirmed, with the reason and the retry command on stderr. Ctrl-C on task create --wait or task logs --follow only detaches; task stop is how you cancel remote work.

brainbase task get

One task: status, agent, model, the machine it ran on, any caller metadata, and the eval verdicts recorded against it. model is the model set on the task, or agent default when it follows its agent's default. With --json, the task record is nested under task, next to eval_runs.

bash
brainbase task get <task-id>
brainbase task get <task-id> --json
text
success         nightly smoke suite

  task     09d61d87-311a-414e-b6e2-76376b5eaf21
  agent    Support Bot (2ff01836-b183-478b-89d5-cd2f77118c25)
  model    agent default
  created  2026-08-31T21:39:53.205552
  machine  143975a9-9677-4562-9d55-b37e365510c6 (size s)
  sandbox  3d86f338-305c-432c-94b6-12fc6c91d446

  EVAL VERDICTS
  pass  answers-the-question

When a task failed, the reason is printed under failure above the metadata. If Brainbase stops a task because there are no credits left or a billing limit was reached, failure says so and includes the detail returned by the service. Add credits or raise the applicable billing limit, then run the task again. Other failures still show their recorded reason.

brainbase task update

Rename a task, change its metadata labels, switch its model or return it to its agent's default model. It works on a running task too, and it never prompts. At least one change flag is required.

bash
brainbase task update <task-id> --title "Nightly smoke run" --metadata env=staging
brainbase task update 4aac86b3-7c1e-4f5a-9d2b-3e8f6a1c0b57 --title "Nightly smoke run" --metadata env=staging --json
brainbase task update <task-id> --model claude-sonnet-5-5
brainbase task update <task-id> --clear-model

Without --json, it prints a summary of the updated task. With --json, it prints the full task record that brainbase task get --json returns under task, and nothing else.

text
updated  Nightly smoke run

  task     4aac86b3-7c1e-4f5a-9d2b-3e8f6a1c0b57
  model    agent default

  METADATA
  source  slack
  env  staging

  read it with brainbase task get 4aac86b3-7c1e-4f5a-9d2b-3e8f6a1c0b57
FlagDescription
--title <text>The new title, trimmed. A blank value is refused; use --clear-title. At most 200 characters.
--clear-titleRemove the title. Automatic titling may name the task again on a later turn.
--metadata key=valueSet one label and keep the others. Repeatable. Splits on the first =.
--unset-metadata <key>Remove one label. Repeatable. A label the task does not have is ignored.
--clear-metadataRemove every label. With --metadata, the labels become exactly the ones given.
--model <id>Switch the model the task's next turn runs on. A turn already running keeps its model. Trimmed; a blank or malformed id is refused before any request.
--clear-modelReturn the task to its agent's default model from its next turn. Cannot be combined with --model.
--jsonPrint the updated task record and nothing else.
Concurrent label edits all survive
--metadata and --unset-metadata send only the labels you name, and the server applies them in one step, so several agents labelling the same task at once keep each other's labels. Against a control plane that cannot merge labels yet, the command reads the current labels and sends the whole map instead, where two writers at the same moment can overwrite each other. --clear-metadata --metadata key=value writes exactly the given labels.

Some keys belong to Brainbase: _parent_link_verified, the brainbase_benchmark_* keys and the interface keys brainbase_interface_id and brainbase_interface_viewer. Setting or removing one is refused with the key named, and nothing is changed.

The server decides whether a model fits the task. An unknown model id, auto on a harness other than Kafka or OpenCode, or any model on a Cursor, Factory or Qoder task (those harnesses pick their own) is refused with the server's reason. The summary shows the task's model, or agent default when none is set on the task, which is what --clear-model returns it to.

Updating a task needs edit access to its agent; with a PAT, that means the publish scope. The command exits 0 when the task was updated, and 1 on any usage error, refusal or request error, with the reason on stderr.

brainbase task delete

Permanently delete a task and tear down its sandbox. This cannot be undone. A task that is still running is not deleted: stop it with brainbase task stop <task-id> first. The task is read before anything is asked, so a running task is reported before the prompt.

bash
brainbase task delete <task-id>
brainbase task delete <task-id> --yes --json
FlagDescription
--yes, -ySkip the confirmation prompt. Required without a terminal.
--jsonEmit { id, kind, name, deleted } and nothing else. Never prompts, so it needs --yes.

brainbase task logs

The task's transcript, one line per event: messages, the agent's reasoning, every tool call and its result, and the closing verdict. Output is line-oriented and each event is collapsed onto a single line, so it stays greppable — a tool call that printed twenty lines of output still matches grep tool_call.end.

bash
brainbase task logs <task-id>
brainbase task logs <task-id> --limit 50
brainbase task logs <task-id> --json | jq '.items[] | select(.type == "tool_call.end")'
brainbase task logs <task-id> --follow
brainbase task logs <task-id> --follow --json | jq -r 'select(.type == "idle")'
text
2026-08-31T21:40:15.460110Z  user.message  run the smoke suite
2026-08-31T21:40:31.126370Z  assistant.thinking  Running the suite, then reporting the result.
2026-08-31T21:40:31.126407Z  tool_call.start  run_tests
2026-08-31T21:40:31.126407Z  tool_call.end  run_tests → success
2026-08-31T21:40:37.037864Z  idle  success — all 128 tests passed
2026-08-31T21:40:37.037864Z  assistant.message  All 128 tests passed.
FlagDescription
--limit <n>Stop after <n> events instead of reading the whole transcript. With --follow, the size of the first page the server sends before it starts tailing.
--follow, -fStay attached and print events as they land, until the task finishes, including any follow-ups sent with task send, or until interrupted with Ctrl-C.
--jsonEmit { "items": [...] } with every raw event and nothing else. With --follow, one raw event per line instead.
The human view is a summary; --json is complete
Plain output drops assistant.message.chunk deltas only where the same turn and subagent already carries the finalized assistant.message they add up to — printing both would show that reply twice. Where no finalized row is present, the chunks are the only copy of the reply and they print: mid-turn while the agent is still writing, after an interrupt, or when --limit cuts the transcript short. --json returns every row unchanged.

Following a running task

Without --follow the command reads the transcript and exits, which is the dump you want after task create --wait or in a CI step. With --follow it stays attached and prints events as they land, until the task finishes — so it is something a CI step can block on rather than something that has to be killed.

Finished includes follow-ups. When task send queues a message on a task that has already finished, --follow stays attached until that follow-up has run and its turn has ended, then exits. Following a task whose follow-ups already ran prints those turns too before it exits.

text
$ brainbase task logs 67e4806e-fd61-4124-89ec-9f5db12ca3be --follow
Following task 67e4806e-fd61-4124-89ec-9f5db12ca3be — Ctrl-C to stop.
2026-09-01T21:06:46.229288Z  assistant.message  first line, landed while following
2026-09-01T21:06:50.383054Z  tool_call.end  → ok
2026-09-01T21:06:54.525705Z  idle  success

The server sends a page of recent history first and then keeps the connection open. --limit sizes that first page only — the feed then catches up from where it left off and tails live, so --limit is not a cap on how much history you see. Leave it off to take the server's own default.

A dropped connection reconnects on its own and resumes from the last event it received rather than replaying, so a long follow over a flaky link does not print the same events twice. Resuming needs a control plane new enough to stamp each frame with its cursor; against an older one a reconnect re-sends the opening page, so you may see a few events a second time. Reconnection notices go to stderr, which keeps a --json pipeline parseable. Stop it with Ctrl-C.

Exit codes with --follow

CodeMeaning
0The task reached a terminal state with no follow-up still to run. The follow printed the closing event and ended.
130You stopped the follow with Ctrl-C. The task keeps running.
143Something sent the process SIGTERM — a CI step's timeout, a container shutting down.
1The server refused the stream — an expired login, an unknown task id — or it stayed unreachable for 60 seconds.
0 means the follow finished, not that the task passed
A follow ends when the task settles, whatever it settled on: a task that ends in failure still exits 0 here, because what failed is the work and not the tail. Use task create --wait, whose exit code reports the task's own status, when you need the job to go red on a failed task.

Reconnecting is not failing. A follow rides out dropped connections for as long as they keep coming back, and only gives up once the stream has been continuously unreachable for 60 seconds — long enough to cover a network change or a control-plane restart, short enough that a broken link answers you rather than hanging. Any successful reconnect resets that clock, so a flaky hour of one-off drops never ends the follow.

That 60-second budget cannot cost you a task that finished while the link was down. The clock only runs while the stream is disconnected, and a reconnect resets it before any event is delivered — so a task that settles during an outage still exits 0 once the stream comes back and the closing event lands. The 1 is reachable only when the stream never returns inside the budget, which is the one case where the command genuinely does not know how the task ended.

Two things read differently while following
A live tail cannot see the future, so it cannot do what the finished read does. Streaming assistant.message.chunk deltas print as they arrive instead of being folded into the finalized message that later replaces them — waiting for that message would mean showing nothing at all while the agent is mid-answer. --json emits one record per line rather than a single { "items": [...] } document, because an open stream has no last element to close one on. And rows come out in the order they arrive rather than sorted by timestamp, which the finished read can do because it holds the whole transcript — so following a task and then re-reading it can show the same events in a different order.

Dispatch and assert in CI

The read side plus --wait is what makes a task a build step. Dispatch, block on the result, and let the exit code fail the job — then print the transcript so a failure is diagnosable from the build log alone.

bash
#!/usr/bin/env bash
set -euo pipefail

# A user-scoped PAT must be exported, not just stored by `token create`.
export BRAINBASE_TOKEN="$BRAINBASE_PAT"

status=0
result=$(brainbase task create \
  --agent "$AGENT_ID" \
  --message "run the release checklist for $GIT_SHA" \
  --wait --timeout 1800 --json) || status=$?

[ "$status" -eq 0 ] && exit 0

# `create` can fail before a task exists at all — an expired PAT, an
# unreachable control plane — so there may be no id to read. Print
# whatever came back instead of running `logs` on an empty id, and keep
# the transcript from replacing the exit code that says what went wrong.
task_id=$(jq -r '.task_id // empty' <<<"$result" 2>/dev/null || true)

if [ -n "$task_id" ]; then
  echo "Task $task_id did not pass:"
  brainbase task logs "$task_id" || true
else
  echo "Could not start the task: $result"
fi

exit "$status"
Credentials in CI
Task commands, including eval verdicts on task get, use the saved PAT when the login session cannot be used. Set BRAINBASE_TOKEN in CI to explicitly select a PAT ahead of either saved credential. See Authentication.

JSON errors

Task commands with --json report failures as {"error": {"code": "...", "reason": "...", "message": "...", "details": {}}} and exit non-zero. A missing confirmation is code: invalid_input, reason: confirmation_required; a running task is code: conflict, reason: task_running.