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.
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
Task ID: 09d61d87-311a-414e-b6e2-76376b5eaf21
Agent ID: 2ff01836-b183-478b-89d5-cd2f77118c25
Status: initializing
First run accepted.
| Flag | Description |
|---|---|
--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=value | Label the task. Repeatable. Splits on the first =; each key once. Find labelled tasks with task list --metadata. |
--wait | Block until the task reaches a terminal status, then exit accordingly. |
--timeout <secs> | Give up waiting after <secs> and exit 1. Requires --wait. |
--json | Emit 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
| Code | Meaning |
|---|---|
0 | The task finished as success. |
1 | The task finished as fail or need_more_info, or --timeout elapsed. |
130 | You interrupted the wait with Ctrl-C. The task keeps running. |
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.
{
"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.
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
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
| Flag | Description |
|---|---|
--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=value | Only 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. |
--json | Emit 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.
brainbase task send <task-id> "Use the ticket owner's display name"
| Flag | Description |
|---|---|
--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. |
--json | Emit 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.
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.
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.
| Flag | Description |
|---|---|
--generation <n> | The generation to stop at. Pass the one a previous attempt printed when retrying; omitted, the current one is read first. |
--json | Emit 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.
brainbase task get <task-id>
brainbase task get <task-id> --json
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.
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.
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
| Flag | Description |
|---|---|
--title <text> | The new title, trimmed. A blank value is refused; use --clear-title. At most 200 characters. |
--clear-title | Remove the title. Automatic titling may name the task again on a later turn. |
--metadata key=value | Set 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-metadata | Remove 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-model | Return the task to its agent's default model from its next turn. Cannot be combined with --model. |
--json | Print the updated task record and nothing else. |
--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.
brainbase task delete <task-id>
brainbase task delete <task-id> --yes --json
| Flag | Description |
|---|---|
--yes, -y | Skip the confirmation prompt. Required without a terminal. |
--json | Emit { 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.
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")'
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.
| Flag | Description |
|---|---|
--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, -f | Stay 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. |
--json | Emit { "items": [...] } with every raw event and nothing else. With --follow, one raw event per line instead. |
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.
$ 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
| Code | Meaning |
|---|---|
0 | The task reached a terminal state with no follow-up still to run. The follow printed the closing event and ended. |
130 | You stopped the follow with Ctrl-C. The task keeps running. |
143 | Something sent the process SIGTERM — a CI step's timeout, a container shutting down. |
1 | The server refused the stream — an expired login, an unknown task id — or it stayed unreachable for 60 seconds. |
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.
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.
#!/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"
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.