| name | helix-spec-tasks |
| description | Start, watch, steer and stop Helix spec tasks — launch a coding agent in its own sandbox, chat with it, send prompts, read its conversation history, exec commands and copy files into its container, take screenshots, stream its desktop, and drive standalone sandboxes. Use when the user wants to run a Helix coding agent, dispatch work to a spec task, talk to a running task, debug a stuck agent, or work with Helix sandboxes. |
Running and steering Helix spec tasks
A spec task is a unit of work that gets its own isolated sandbox — a full Ubuntu container, with
a GNOME desktop by default, running a coding agent (Claude Code, Codex, Gemini CLI, Qwen Code,
Goose, or Zed's agent) against the project's repositories.
Read helix-cli for auth and helix-board for
creating and moving the cards themselves. This skill is about the running task.
Version note. Addressing a task by its spt_… id in send/interact (instead of looking
up its session id) landed in helixml/helix#3033 — merged to main, shipping in the first release after 2.12.3. On older binaries resolve it first:
SES=$(helix api /spec-tasks/spt_01xxx | jq -r .planning_session_id).
Identifiers
| Prefix | Thing |
|---|
prj_ | project |
spt_ | spec task (a card on the board) |
ses_ | session — the conversation + container behind a task |
app_ | agent (an "app" in the API) |
sbx_ | standalone sandbox (Sandboxes API, not tied to a task) |
A task has at most one session at a time (planning_session_id), and it is reused for the whole
lifecycle — planning and implementation. Switching agents mid-task keeps the same session, so
context carries over.
Pick an agent
helix spectask list-agents -o acme
helix spectask list-agents -o acme --zed-external-only
agent_type: zed_external is necessary but not sufficient, and list-agents does not print
the field that decides it. Three orthogonal attributes are in play:
| Attribute | Values | Spec tasks need |
|---|
agent_kind | helix_agent (default) · coding_agent · org_agent | coding_agent |
agent_type | zed_external, … | zed_external |
code_agent_runtime | claude_code, codex_cli, zed_agent, … | any |
list-agents shows the type and happily prints a copy-paste spectask start line for an
agent that start will then refuse:
Error: failed to create spec task: API returned 400:
spec tasks requires agent kind "coding_agent", got "org_agent"
That happens with helix-org bots (a chief-of-staff, say): they are zed_external, so they look
launchable, but the helix-org runtime reclassifies them to org_agent after apply. Check kind
directly before trusting the list:
helix api /apps | jq -r '.[] | "\(.id) kind=\(.agent_kind) name=\(.config.helix.name)"'
Agents created from a project YAML agent: block are classified coding_agent, so those are
always launchable whatever their runtime: — see helix-board.
Start a task
helix spectask start --project prj_01xxx --agent app_01yyy \
-n "Add dark mode" --prompt "Users want a dark theme across the app"
helix spectask start spt_01xxx
helix spectask start --project prj_01xxx --agent app_01yyy -n "Investigate flaky login" \
--prompt "Work through the brief below end to end." \
--prompt-file ./brief.md \
--attach ./failing-run.log --attach ./trace.txt
helix spectask start --project prj_01xxx --runtime headless-ubuntu -n "Bump deps" --prompt "…"
Behaviour worth knowing:
- It returns immediately. The sandbox provisions in the background; the printed task URL
shows it booting.
--wait blocks (up to 3 minutes) and then prints session-level connect info.
- A
--wait timeout is not a failure — the task exists and is still provisioning. The CLI
exits 0 and prints the task id.
--prompt-file is appended after --prompt when both are given. Use it for design docs and
briefs — nothing needs to be committed.
--attach uploads files as task attachments; the agent reads them inside the sandbox at
~/work/helix-specs/design/tasks/<NNNNNN>_<slug>/attachments/. Put logs and large context
there rather than in the prompt, and give each file a distinct name — they land in one flat
directory, so seven files called SKILL.md collide.
--runtime is fixed for the life of the task. ubuntu-desktop (default) gives a streamable
GNOME desktop with screenshots; headless-ubuntu is agent-only.
-q prints only the task id (or the session id with --wait) — use it in scripts.
Chat with a running task
helix spectask send spt_01xxx "List the files you've changed so far"
helix spectask send spt_01xxx "Run the test suite and fix what fails" --wait --max-wait 600
helix spectask send spt_01xxx "What is the current branch?" --wait --json
--wait polls (default every 2s, up to --max-wait seconds, default 300) until the agent stops
working. Without it you've only queued the message.
Read and follow the conversation:
helix spectask interact spt_01xxx
helix spectask interact spt_01xxx --history --count 20
helix spectask interact spt_01xxx --send "status?"
helix spectask interact spt_01xxx --watch --interval 5
Run with no flags, interact drops into an interactive chat loop — avoid that in a
non-interactive context; use --send/--history instead.
For structured access to the conversation, the session MCP tools are exposed directly:
helix spectask mcp session ses_01xxx current_session
helix spectask mcp session ses_01xxx session_toc
helix spectask mcp session ses_01xxx get_turn --turn 3
helix spectask mcp session ses_01xxx search_session --query "database migration"
helix spectask mcp list ses_01xxx
These take a session id. Get it with helix spectask get spt_01xxx --json | jq -r .planning_session_id.
Look at what the agent is doing
helix spectask list
helix spectask screenshot ses_01xxx
helix spectask live ses_01xxx
helix spectask health
spectask health does not check the sandbox hosts. If tasks aren't getting sandboxes at all,
that's helix api /sandboxes — see helix-deploy.
spectask screenshot writes screenshot-<timestamp>.jpg into the current directory and prints
the filename — it does not write to stdout, so don't redirect it. (helix sandbox screenshot
does the opposite: stdout by default, -o for a file.)
Desktop control (desktop runtimes only):
helix spectask mcp desktop ses_01xxx take_screenshot
helix spectask mcp desktop ses_01xxx list_windows
helix spectask mcp desktop ses_01xxx type_text --text "hello"
helix spectask mcp desktop ses_01xxx mouse_click --x 640 --y 480
Work inside the task's container
helix spectask exec is not a general-purpose shell. The desktop container enforces a
server-side allowlist and rejects anything else with 403 command not allowed: <cmd>:
vkcube glxgears weston-simple-egl # benchmark/graphics
ls cat echo test # inspection
pkill killall # process control
npm claude # agent CLI upkeep
helix-claude-auth-wrapper helix-codex-auth-wrapper
git # only `git config --global user.name|user.email <v>`
No bash, no sh, no python3, no go. There is no shell, so pipes, redirection, && and
globs are not available either — each call is one execve of an allowlisted binary.
helix spectask exec ses_01xxx ls /home/retro/work
helix spectask exec ses_01xxx cat /home/retro/work/README.md
helix spectask exec ses_01xxx -- ls -la /home/retro/work
helix spectask exec ses_01xxx --timeout 300 vkcube
helix spectask exec ses_01xxx --background vkcube
Use -- before any argument starting with -, or cobra parses it as a flag of exec itself
(unknown shorthand flag: 'c' in -c is what a forgotten -- looks like).
To actually run arbitrary commands, pick one of:
- Ask the agent —
helix spectask send spt_01xxx "run the tests and paste the failures" --wait.
It has a real shell; you are talking to something that can use it.
- Use a standalone sandbox —
helix sandbox exec is general-purpose (see below).
Copying files in is unrestricted:
helix spectask copy ses_01xxx ./patch.diff
helix spectask copy ses_01xxx ./config.json --dest /home/retro/work/config.json
helix spectask copy ses_01xxx ./data.txt --no-file-manager
Note the argument order: session first, then the local file.
--timeout on exec defaults to 30 seconds.
Stop and resume
helix spectask stop ses_01xxx
helix spectask resume ses_01xxx
Do not run helix spectask stop --all on a shared instance — it stops every external-agent
session, including other people's work.
To free resources while keeping the card, prefer helix spectask archive spt_01xxx
(see helix-board) — it stops the agent and takes the card off the
board.
Standalone sandboxes
helix sandbox is a separate, lighter-weight API: a container with no board card and no coding
agent. Good for one-off compute, reproducing a build, or scripted environments.
helix sandbox runtimes
helix sandbox create --runtime headless-ubuntu --size medium --ttl 1800 --name scratch
helix sandbox list --org acme
helix sandbox wait sbx_01xxx --timeout 3m
helix sandbox exec sbx_01xxx -- bash -lc "uname -a"
helix sandbox exec sbx_01xxx --detached -- ./long-job.sh
helix sandbox logs sbx_01xxx cmd_01xxx --follow
helix sandbox commands sbx_01xxx
helix sandbox kill sbx_01xxx cmd_01xxx --signal TERM
helix sandbox ls sbx_01xxx --path /root
helix sandbox read sbx_01xxx /root/out.txt
echo "hello" | helix sandbox write sbx_01xxx /root/in.txt --mode 644
helix sandbox screenshot sbx_01xxx -o shot.jpg
helix sandbox terminal sbx_01xxx
helix sandbox delete sbx_01xxx
Sandboxes expire at their TTL. Set one long enough for the job, and delete explicitly when done
rather than relying on expiry.
--org is per-command, not sticky. If you create a sandbox in one org and then omit --org
on exec/delete, the CLI resolves your first org and the API answers 404 sandbox not found
— which reads like the sandbox died, but is really "wrong org". Export HELIX_ORG, or pass
--org to every sandbox subcommand in a script.
The runtime list is deployment config (HELIX_SANDBOX_RUNTIMES on the API), not a fixed set —
helix sandbox runtimes is the only reliable answer for a given deployment, and it lists the
desktop runtime alongside the headless ones (ubuntu-desktop, headless-ubuntu, node22,
python313 on a default install).
A runtime appearing there does not guarantee the node can start it: the catalogue is
control-plane config, while the images a sandbox node has pulled are independent of it. See the
"node online but create fails" case in helix-deploy. --image needs
HELIX_SANDBOX_ALLOW_CUSTOM_IMAGE=true on the server and is rejected otherwise.
Each sandbox runs under its own Docker daemon inside the Hydra runner, so containers you start
inside one are invisible to every other session. See
helix-deploy for what that means operationally.
Testing and diagnostics
helix spectask test --session ses_01xxx --all --json
helix spectask e2e --project prj_01xxx --agent app_01yyy --prompt "List files" --cleanup
helix spectask benchmark ses_01xxx --duration 30
helix spectask latency ses_01xxx
helix spectask stream ses_01xxx --duration 30 -v
spectask e2e is the fastest end-to-end confidence check: it creates a task, boots the sandbox,
cancels a live turn, sends a follow-up, screenshots, and exercises the session MCP tools.
When benchmarking video, never measure on a static desktop — it idles at ~10 FPS by design.
Expected: static ≈10, terminal activity 15–35, vkcube 55–60.
Troubleshooting
| Symptom | What it means |
|---|
start seems to hang or times out | Normal — the task was created; the sandbox is still provisioning. Check helix spectask get spt_01xxx. |
task … has no session yet | The task was created but never started. helix spectask start spt_01xxx. |
| Screenshot 503s | The container is up but RevDial hasn't connected yet. Wait and retry before assuming it's broken. |
Task sits in spec_review forever | It's waiting on a human. helix spectask approve spt_01xxx. |
Task sits queued with ⏳ | A WIP limit or a dependency. Raise the limit in the project YAML or finish the blocker. |
| Agent stops responding | helix spectask interact spt_01xxx --history to see the last turn, then helix spectask exec … ls/cat to inspect the container. |
403 command not allowed: bash | spectask exec is allowlisted — no shell. Ask the agent, or use a standalone sandbox. |
unknown shorthand flag: 'c' in -c | Missing -- before flag-like arguments to exec. |
| Screenshot/stream fails on a headless task | Expected — headless-ubuntu has no compositor. |