On this page
- List and search tasks
- Task vs Session
- Create A Task That Starts Work
- Start a Task from a Playbook
- Choose A Harness, Model, And Effort
- Create Additional Sessions
- Inspect Session Changes
- Read a Session's Code Change Counts
- Session States
- Waiting for Capacity
- Retrying After a Machine Failure
- Describing A Task In A List
- Send Follow-Up Messages
- Read Newest Messages First
- Messages Recorded on Calls
- Attach Context
- Provide Input Material
- Stop Or Close Work
- Pause or resume the task
- Finish the task
- Ownership In Automation
Tasks and Sessions
Published
Updated
Tasks are the managed coding API. A task is the work item. A session is one agent run under that task. Messages are the conversation with the agent. Attachments provide extra context.
Use this workflow when you want Wallfacer to boot the VM, run the agent, manage idle teardown, and preserve the conversation. To find an existing task and read its session from the CLI, see Find Tasks and Read Sessions from the CLI.
List and search tasks
GET /v1/tasks returns one activity-ordered list across every active account you belong to. It defaults to scope=mine, which selects tasks whose owner_user_id is you. Set scope=visible to include every task you can see in those accounts, or account_id to narrow either scope to one account. Each row includes the task's account ID and name. Pending accounts are excluded.
Use q to search titles, slugs, and prompts across the filtered list. Search uses full-text and case-insensitive substring matching, including short prefixes, and keeps recent-activity order. Set archived=true to list archived tasks instead of active ones. Queries can be up to 255 characters.
curl -s "$WALLFACER_API/tasks?scope=visible&q=checkout&per_page=20" \
-H "Authorization: Bearer $WALLFACER_TOKEN"
wallfacer tasks list --scope visible --q "checkout" --per-page 20For one account's task list, use GET /v1/accounts/{account_id}/tasks or wallfacer tasks account-list with that account configured in the CLI. The account list supports status, owner, creator, and inbox filters. Its q search uses the same matching but sorts by relevance, then ID.
wallfacer tasks account-list --q "checkout" --status activeBoth lists use cursor pagination with 25 results by default and up to 100 per page. Pass meta.next_cursor as cursor with the same filters for the next page. Start without a cursor after changing filters. A task updated while you page can move ahead of the cursor; restart from the first page to see its new position. See Search when you need matches across tasks and other record types.
Task vs Session
| Concept | Responsibility |
|---|---|
| Task | The work item, owner, default environment, original prompt, attachments, and completion state. |
| Session | One attempt against the task: environment, VM, branch name, base branch, messages, and idle timeout. |
| Message | A user instruction, assistant response, or internal transcript event. |
| Attachment | Context carried with the task in MCP resource shape. |
A task can have multiple sessions. That is useful when you want separate attempts, separate branches, or sibling sessions in different environments.
Create A Task That Starts Work
If you create a task with a prompt, Wallfacer creates a session, sends the first message, and begins booting a VM.
curl -s -X POST "$WALLFACER_API/accounts/$ACCOUNT_ID/tasks" \
-H "Authorization: Bearer $WALLFACER_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: task-checkout-flake-001" \
-d '{
"title": "Fix checkout test flake",
"prompt": "Find and fix the flaky checkout test. Keep the change focused and add a regression test.",
"environment_id": "'$ENVIRONMENT_ID'",
"idle_timeout_seconds": 600,
"attachments": [
{
"name": "CI failure",
"uri": "https://github.com/acme/shop/actions/runs/123",
"mimeType": "text/markdown",
"text": "The checkout test fails intermittently in CI with a timeout waiting for payment confirmation."
}
]
}' | jq ".data"environment_id is optional. When you omit it, the task runs on its agent's computer: it defaults to the environment configured on the task's agent (resolved through created_by, see Accounts and Members). An explicit environment_id always wins. Omit it and leave the agent without an environment only when the task spans multiple environments, then set environment_id per session instead.
The task response gives you the task ID. List sessions for that task to find the generated session.
Start a Task from a Playbook
To run a playbook manually, create a task with its pipeline_id instead of a prompt. A disabled playbook can still start a manual task; disabling only stops its automatic triggers. An archived or unpublished playbook cannot start one. inputs are supported only for freeform tasks started with a prompt; do not include them when creating a playbook task.
Choose A Harness, Model, And Effort
Set harness, model, and effort to choose which coding agent runs the work, which model it uses, and how much reasoning effort it spends. All three are optional, and all three apply to the session that task create starts when you also pass a prompt. A create without a prompt starts no session and drops them.
They are not stored on the task. Creating a session under an existing task accepts none of these fields, and that session resolves its harness, model, and effort from the acting agent's current settings.
harness accepts claude or codex and defaults to claude. model is a model id scoped to that harness, such as claude-opus-4-8 for Claude or openai/gpt-5.6-sol for Codex. Pass the id exactly as GET /v1/models reports it. effort is in the chosen harness's own vocabulary: low, medium, or high for Claude, and minimal, low, medium, high, or xhigh for Codex. When you omit effort, the effort pinned on the task's agent applies only if harness resolves to that agent's own harness. Send a different harness and the agent's effort is ignored, because each harness has its own vocabulary, and the selected harness's default applies instead.
An unknown harness, model, or effort falls back to a supported default rather than failing the request, so read session.vendor, session.harness_model, and session.harness_effort to confirm what the session runs. See Models for the available options.
curl -s -X POST "$WALLFACER_API/accounts/$ACCOUNT_ID/tasks" \
-H "Authorization: Bearer $WALLFACER_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: task-codex-sol-001" \
-d '{
"title": "Fix checkout test flake",
"prompt": "Find and fix the flaky checkout test.",
"environment_id": "'$ENVIRONMENT_ID'",
"harness": "codex",
"model": "openai/gpt-5.6-sol",
"effort": "high"
}' | jq ".data"curl -s "$WALLFACER_API/accounts/$ACCOUNT_ID/tasks/$TASK_ID/sessions" \
-H "Authorization: Bearer $WALLFACER_TOKEN" \
| jq ".data[0]"Create Additional Sessions
Create another session when you want a separate attempt or branch.
curl -s -X POST "$WALLFACER_API/accounts/$ACCOUNT_ID/tasks/$TASK_ID/sessions" \
-H "Authorization: Bearer $WALLFACER_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: session-checkout-flake-alt-001" \
-d '{
"title": "Alternative checkout fix",
"prompt": "Try a minimal fix in the polling helper only.",
"environment_id": "'$ENVIRONMENT_ID'",
"branch_name": "fix/checkout-flake-alt",
"base_branch": "main",
"auto_commit": true
}' | jq ".data"Sessions inherit the task environment when environment_id is omitted. Pass a session-level environment when one task spans multiple repos or stacks. When branch_name is omitted, Wallfacer generates {handle}/{hash} from the agent's handle (or the person's handle) and the last eight hex characters of the session ID. Each session has its own branch. In a multi-source environment, sources[].head_branch may differ for a source pinned to a pull request, and sources[].base_branch shows that source's base.
Inspect Session Changes
Session responses include a sources array with one entry per repository. Each entry reports commit_count, file_count, lines_added, and lines_removed for its working branch, plus its head and base branches. has_changes reports uncommitted workspace changes. diff_summary sums the measured file and line counts across sources, so you can show the size of a session's diff without listing files. Use the session diffs endpoint for the file-level diff.
A null sources[].file_count means that source has not been measured. A null diff_summary means the session has not been measured yet, not that it has no changes. Measurement runs shortly after VM startup and after each turn. The last measured counts remain available after the VM is destroyed.
Read a Session's Code Change Counts
GET /v1/accounts/{account}/tasks/{task}/sessions/{session} returns diff_summary with file_count, lines_added, and lines_removed across the session's sources. Each sources[] entry has its own file_count and line totals. Use the summary for a session-level change indicator and the per-source counts when a session spans repositories. The session diff endpoint supplies the changed files themselves.
The counts are cached after the VM starts and refreshed as work progresses. diff_summary: null means the aggregate is still unknown; it does not mean there are no changes. A positive summary can be partial when another source's file_count is null. Zero is returned only after every source has been measured and found unchanged. The last measured counts remain available after the VM is destroyed. See the API Reference for the complete session schema.
Session States
Sessions move through lifecycle states:
| Status | Meaning |
|---|---|
waiting | An interactive session is parked between attempts because no VM was free or its VM died during a turn. waiting_since reports when the wait began. Queued messages deliver once it starts. |
waiting_for_snapshot | The environment base snapshot is generating for the first time. |
starting | A VM is provisioning. |
active | The VM is up and the agent can receive messages. |
idle | The idle timeout elapsed and the VM was destroyed; sending a message can reactivate it. |
closed | The session was closed or failed terminally. |
Subtract waiting_since from the current time to show how long a queued session has been waiting. It is non-null only while status is waiting, and it clears as soon as the session starts, closes, or goes back to idle. A session that entered the wait before the field existed reports null while still waiting, so read null as an unknown duration rather than as evidence the session is not waiting. status is the authority on that.
Use close_reason to explain why a session stopped. It distinguishes user-initiated close, successful completion, VM failure, setup failure, snapshot failure, turn failure, resource exhaustion, and related states.
Read close_reason together with status, not instead of it. Two reasons are automatic recoveries and leave the session idle rather than closed, so a non-null close_reason does not by itself mean the session is finished:
| Reason | What happened | Resulting status |
|---|---|---|
provisioning_failed | The session never finished starting up and was recovered automatically. | idle. Send another message to retry. |
harness_lost | The agent stopped without reporting how the turn ended, and the session was recovered automatically. | idle. Send another message to retry. |
resource_exhausted | No machine ever came free. The session waited and retried for over an hour before giving up. | closed |
waiting_timeout | The session sat queued for longer than any retry schedule allows, so it was closed rather than left waiting indefinitely. | closed |
vm_failed | The VM failed during boot or runtime. A playbook may still retry the step in a new session. Read error_message for the cause. | closed |
Both recovery reasons set error_message with what to do next, and both preserve the transcript.
A machine failure closes the affected session with the fixed close_reason: vm_failed value. Read error_message for the underlying cause, such as a kernel out-of-memory kill or firecracker_exited. A closed playbook session can coexist with a step that is still retrying.
Waiting for Capacity
A session that cannot get a machine reports the wait, so a client can render it instead of showing a slow boot:
statusiswaitingandwaiting_sinceis the ISO 8601 timestamp the wait began. Subtract it from the current time for elapsed wait. It is null outside a wait, and a wait that started before the timestamp was recorded reports null, so read null as an unknown duration rather than as evidence the session is not waiting.- The parent task's
binding_constraintreportskind: "retrying", withnext_retry_seconds(the wait before the next attempt) andretries(attempts so far). It carries the same shape as the constraint a capacity-parked pipeline step emits, so one rendering path covers both. It is also the shape a machine failure reports, so readsummaryfor which of the two a task is parked on. error_messageon a capacity failure is written for a person to read and says what to do next. It no longer carries the upstream provider payload.
The platform retries for over an hour and then closes the session with close_reason: resource_exhausted. A wait that outlives any retry schedule is swept and closed with close_reason: waiting_timeout. Both are resumable: posting a message starts a fresh attempt.
Retrying After a Machine Failure
A VM can fail during boot or runtime. The task reports binding_constraint.kind: "retrying" while a retry is parked. Read its summary to distinguish a machine failure from a capacity wait. The retries count belongs to the cause named there; capacity and machine failures have separate retry budgets.
An interactive chat turn can retry twice on fresh VMs, about 30 seconds apart. The budget resets for each completed turn. Its existing session returns to waiting while the turn retries, then closes with close_reason: vm_failed if the retry budget is spent. An active session between turns is also closed after its VM dies; a session already in idle is not changed by this recovery.
A playbook action follows a different session lifecycle. Its failed session closes immediately with close_reason: vm_failed and an error_message naming the cause. If retries remain, Wallfacer parks the step as pending and relaunches it in a new session. A closed vm_failed session therefore does not mean the step has exhausted its retries. If the third VM fails, the action fails and the playbook follows its configured failure route.
A VM death while a step is awaiting a person's answer force-closes that action as a failure. The playbook follows the configured failure route instead of staying parked for the answer.
Describing A Task In A List
last_message_preview on the task is a one-line, markdown-stripped summary of the last thing the agent said on it, across all of its sessions. Read it when a list row should describe the work rather than restate the lifecycle status: status: "active" reports only that the task has not reached a terminal state, which a plain conversation never does.
- Only turns carrying visible text count, so a turn that only called tools or only thought leaves the previous summary standing.
- It is
nullwhen the agent has not spoken on the task yet. Some older tasks with legacy statuses can also returnnullafter the agent has spoken because their message previews have not been filled in. Render either case without a second line rather than printing a placeholder. - The summary is stored on the task, so listing tasks costs no request per row.
Send Follow-Up Messages
curl -s -X POST "$WALLFACER_API/accounts/$ACCOUNT_ID/tasks/$TASK_ID/sessions/$SESSION_ID/messages" \
-H "Authorization: Bearer $WALLFACER_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: msg-add-test-001" \
-d '{"message": "Also add a unit test around the timeout branch."}' \
| jq ".data"Sending a message automatically resumes an idle or closed session when the platform can reactivate it.
Messages are ordered by sequence when delivered. Queued messages can have sequence: null, so clients should render by sequence and then creation time.
Conversation UIs usually render user and assistant messages and collapse internal types such as queue-operation, attachment, and ai-title.
Message lists are cursor-paginated. Use meta.next_cursor when present to load the next page.
Read Newest Messages First
A message list comes back oldest first. Pass direction=desc to start at the newest message instead, which is what a client that opens a conversation on its last screen needs.
curl -s "$WALLFACER_API/accounts/$ACCOUNT_ID/tasks/$TASK_ID/sessions/$SESSION_ID/messages?direction=desc" \
-H "Authorization: Bearer $WALLFACER_TOKEN" \
| jq ".data"The cursor walks in whichever direction the page was fetched in, so following meta.next_cursor from a desc page pages backward into older messages.
asc is the default, so a request that sends no direction gets the order it always got. Any other value is a 422 with a direction field error.
Messages Recorded on Calls
A message's channel is voice when it came from a call. On recorded call turns, call_channel distinguishes voice (spoken aloud) from chat (typed in the meeting chat); it is null on other messages. A call can start work, but the call ends before the work is answered, so the resulting task conversation is answered in the app. See Find Voice Call Records to filter call events by agent.
Attach Context
Attachments are MCP-aligned resources. For agent context, include inline text or base64 blob. Use uri as a reference to the source of the content; do not assume the agent will fetch arbitrary external URIs for you.
curl -s -X POST "$WALLFACER_API/accounts/$ACCOUNT_ID/tasks/$TASK_ID/attachments" \
-H "Authorization: Bearer $WALLFACER_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: attachment-ticket-42" \
-d '{
"name": "PROJ-42",
"uri": "https://tracker.example.com/PROJ-42",
"mimeType": "text/markdown",
"text": "# PROJ-42\nCheckout sometimes times out while waiting for payment confirmation."
}'Attachments report status: "ready" when content is available and status: "failed" when content could not be made available. Read attachment contents through the contents route when you need MCP ResourceContents shape.
Provide Input Material
Alongside the prompt, task, session, and message create accept an inputs array. Each input is a named piece of content the agent reads as a file in the sandbox, carried inline as text or content_base64, or by reference as an upload_id for material too large to send inline. See Uploads for the upload flow and limits.
Stop Or Close Work
Abort a running session when you want to interrupt the agent without permanently closing the session:
curl -s -X POST "$WALLFACER_API/accounts/$ACCOUNT_ID/tasks/$TASK_ID/sessions/$SESSION_ID/abort" \
-H "Authorization: Bearer $WALLFACER_TOKEN"Close a session when the attempt is done:
curl -s -X PATCH "$WALLFACER_API/accounts/$ACCOUNT_ID/tasks/$TASK_ID/sessions/$SESSION_ID" \
-H "Authorization: Bearer $WALLFACER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status": "closed"}'The session VM is released when the session idles or closes.
Pause or resume the task
Send PATCH /v1/accounts/{account_id}/tasks/{task_id} with {"status": "paused"} to stop playbook steps from advancing. Abort an in-flight turn first if you need it to stop immediately. Pausing releases the task's sandbox VM unless a running session still uses it. After that VM is released, resuming the task with status: "active" boots a fresh VM and re-runs the interrupted step.
Finish the task
When the goal was reached outside Wallfacer, or the work is no longer needed, finish the task itself with PATCH /v1/accounts/{account_id}/tasks/{task_id}:
curl -s -X PATCH "$WALLFACER_API/accounts/$ACCOUNT_ID/tasks/$TASK_ID" \
-H "Authorization: Bearer $WALLFACER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status": "done"}'Set status to done for a completion or cancelled to close work that is no longer needed. Either outcome cancels the current playbook step and ends any running session. Only the task owner or an account admin can finish an active or paused task. A task already finished returns HTTP 409.
Ownership In Automation
A task runs as an agent. created_by is that execution identity: it must be an enabled agent of the account, and it defaults to the account's connected agent when you omit it. Every commit, pull request, and credential the task uses resolves to that agent, not to a person.
The responsible human is recorded separately as the task's owner. When a person's token creates a task, that person becomes the owner; a task an agent opens on its own stays unowned and surfaces in a shared "needs someone" queue. Task access is account-scoped, so any member can list and read the account's tasks regardless of who created them.