Background work

Background work comes in two vocabularies. A background session is a whole session that keeps working after you leave the terminal: /bg hands the running conversation to a detached worker process and returns you to the shell. Background jobs are work inside a live session: task agents dispatched with the task tool, processes started with bash background:true, and waits started by scheduler. Jobs are tracked per session and share one set of TUI surfaces. Agent and process results are delivered into the conversation automatically; a schedule resumes its waiting model turn directly.

Background sessions

/bg (alias /background) sends the current session to the background while it is working: the running turn pauses at a safe boundary (the in-flight model response and tool batch finish first, and blocking waits such as wait_agent, scheduler, and job_output return immediately instead of running out their timeout), a detached worker process takes the session over, and the TUI exits to the shell with session <id> continues in background; xal bg attach <id>. /bg needs work in progress and refuses while a permission request or question is waiting for an answer. Anything still queued in the composer when the handoff happens is printed as not sent: <text> so nothing disappears silently.

In-flight background jobs cannot move between processes: they are stopped at detach and the stop is recorded in the transcript, so the model knows on resume. This also works when the main turn is idle and only task agents or background processes remain. The worker continues from the handoff notice, survives the terminal closing, and keeps state in ~/.xal/bg/<session-id>/: lease.json with exclusive worker ownership, state.json with current status and activity, control.json for authenticated handoff and stop requests, and worker.log with the event stream. Workers are never respawned automatically; a session goes to the background only when you send it there.

Manage background sessions from the shell:

CommandPurpose
xal bg / xal bg listList background sessions with status, title, and activity.
xal bg attach <id>Take a background session back into the TUI.
xal bg stop <id>Ask a worker to stop gracefully and report if it does not acknowledge within 15s.
xal bg clear [id]Remove finished entries (or one entry) from the list.

Ids accept unique prefixes. Statuses:

StatusMeaning
runningThe worker is executing turns; the activity column shows what it is doing.
doneThe work finished; the worker exited.
needs inputThe agent hit a permission request or question and stopped; attach to answer it.
stoppedStopped with xal bg stop (a pending request is denied as part of the stop).
failedThe turn or the worker failed; the detail column has the reason.
diedThe worker vanished without writing a final status; the row shows the log path.

Attach is a takeover handoff: a running worker pauses at the next safe boundary and exits, then the TUI resumes the session in place and continues the work. A pending permission request or interactive tool is re-raised interactively on attach. Interactive tools are deferred before execution, so work before a question is never replayed. Nothing is auto-denied while a session runs in the background, which also means the permission mode governs unattended progress: a session in a mode that asks for approval stops at the first request with needs input. Inside the TUI, /bg list opens the same manager as a picker: attach here, stop, show the log path, or remove an entry.

A background session stays an ordinary session. Once the worker cleanly releases its lease, xal resume <id> works as usual. While a lease is active, resuming is refused so two processes never write one transcript. If a worker dies without releasing its lease, attach the died entry to recover it safely.

Task agents

Task agents are available for explicit delegation, not as the primary model's default workflow. The primary model is instructed to dispatch them only when the user or applicable AGENTS.md or skill instructions ask for sub-agents, delegation, or parallel agent work. Requests for depth, thoroughness, research, investigation, or detailed codebase analysis alone do not authorize delegation.

The task tool's model-facing schema describes its mechanics and constraints but does not add a second global workflow prompt. Delegation policy is a separate primary-session prompt section so capability does not imply authorization.

The task tool dispatches a batch of up to 8 independent assignments. Each assignment becomes its own agent session that starts without conversation history: the batch's shared context plus the assignment text is everything it knows. The call returns agent ids immediately; up to agents.maxConcurrent agents run at once and the rest queue.

Each task declares its access:

Dispatching any write task asks for approval. Sub-agents cannot ask for approval themselves; any action that would need it is denied automatically. Each agent runs until it produces a final report, is explicitly stopped, or exceeds its turn budget: after agents.maxTurns completed turns the agent is told to wrap up, and at 1.5× the budget its last report is returned as-is instead of running forever. The primary agent can inspect and extend the soft turn budget while the task runs. agents.timeoutMinutes can add an operator-configured wall-clock safety limit, but its default value of 0 leaves agent lifetime unlimited and the primary agent cannot change it.

A task agent should work independently, but it can call ask_parent when a parent-only decision or missing context truly blocks useful progress. The tool suspends that child tool call and shows Waiting for parent… without starting another provider turn or polling. Each child can have one pending question. A configured task deadline bounds the wait; cancellation or parent failure also releases it with an unavailable result. Questions are process-local live state and are not resumed after teardown.

The parent receives a persisted, expandable question notice in the transcript and TUI plus a transient model instruction. It answers with job_send; while a question is pending, the next accepted job_send or TUI agent message is the answer rather than ordinary guidance. If the parent finishes without answering, it gets one transient correction. Finishing again releases the child as parent-unavailable. A question wakes wait_agent and an explicit job_output(wait) so the parent cannot deadlock while waiting for the blocked child. Historical question events remain visible after restart, but no actionable instruction is restored into provider history. Assignments should still be self-contained, and agents should not use this path for status questions.

A finished agent's report is delivered into the parent conversation automatically as a system notice, with no polling needed. If the active turn is blocked on agent work, wait_agent subscribes to task-agent activity and returns when a result or question is queued, new user input arrives, or its timeout expires. It does not collect or suppress the automatic report delivery. Alongside the in-conversation result, every agent writes two durable files into the session directory:

Background processes

bash with background:true starts the command as a managed job and returns its id immediately. Output is captured into a bounded in-memory buffer (oldest middle dropped past ~400 KB, marked with ... N characters omitted ...) and written completely to a .log file in the session directory. When the process exits, its result is delivered into the conversation automatically.

A running foreground bash command can be promoted to a background job at any moment with the jobs.background shortcut (default ctrl+b). Read-sandboxed commands requested together run concurrently, and the shortcut promotes every foreground command that is running. The command keeps running, its output keeps flowing into the job, and the result is delivered when it exits. Killing a promoted command that ran in the persistent shell tears the shell session down; the next command starts a fresh one.

Job tools

The model coordinates jobs with six tools. None of them are offered until the session has actually started a background job or task agent, so a session that never uses background work never carries their definitions. Once offered they stay offered for the rest of the session, including after every job has finished, so a job that settles mid-turn can still be collected. A scheduler wait does not count, because a schedule delivers nothing to collect. job_send, job_extend, and wait_agent additionally require that the session has started a task agent.

ToolPurpose
wait_agentWait for task-agent activity without collecting or consuming the automatically delivered report.
job_outputRead process output, collect an agent report, or inspect a schedule; configured deadlines add supervision checkpoints.
job_statusInspect processes, task agents, and schedules without consuming output.
job_sendAnswer a pending task-agent question, or queue guidance when no question is pending.
job_extendAdd up to 100 soft-budget turns to a queued or running task agent per call.
job_killStop a process, task agent, or schedule. A process that ignores the graceful stop is hard-killed after 2 seconds.

wait_agent defaults to 10 minutes, raises shorter requests to 5 minutes, and accepts waits up to one hour. It ends immediately for queued task-agent results, task-agent questions, or new user input, so the timeout only bounds how long silent agents are tolerated; a short timeout would just cost a model round to start the same wait again. Its timeout never stops an agent. An explicit job_output wait also returns without affecting agent execution. When an operator configures a nonzero runtime limit, job_output reserves a supervision window of up to one minute before the deadline and returns with live status so the parent can inspect, steer, or stop the task. If the configured runtime limit is reached, collection includes a bounded transcript tail labeled as incomplete alongside the durable task-record path.

Stopping a job from the TUI is never silent: the result is marked stopped by the user and still delivered so the model knows what happened. A task agent remains unsettled until its runner has finished cleanup and saved its task record.

TUI surfaces

Open the navigator with /agents (alias /jobs), the agents.open shortcut (default ctrl+x ctrl+a), or by pressing ↓ with an empty composer.

Navigator keys:

KeyAction
↑ ↓Move between rows; ↑ from main returns to the composer
enterOpen the viewer for the selected job
tabToggle an inline preview of the last output lines
x / kStop a running job, or dismiss a finished row
escClose the viewer, collapse the preview, or leave

Opening a process or schedule job takes over the screen with the raw-text viewer and follows its output live. While it is open, ↑/↓ keep moving the selection in the list below and enter switches the viewer to the selected job (or closes it on the viewed row), so you can hop between jobs without leaving the viewer. pgup/pgdn scroll the transcript, home jumps to the top, and end returns to the bottom and resumes following (scrolling up pauses following and shows · paused).

Opening a task agent instead clears the terminal scrollback and prints the agent's transcript (reasoning, tool activity, errors, and guidance) through the same native scrollback as the main session, with its own status bar showing the agent's model, mode, and live metrics; the navigator stays visible below it. The composer shows an agent-colored indicator while the page is open and steers the viewed agent: submitted text is queued into the agent's current turn, appears as a user block in its transcript, and is recorded as User guidance in the plain-text transcript surfaced by job_output. Slash commands and image input are rejected with an error in the transcript, and submissions bounce with a status-bar notice while the agent is queued, stopping, between turns, or finished. esc closes the page and restores the main transcript from memory; the steering draft is kept for the next visit. Approvals, prompts, and other popovers always close the agent page and render on the main page.

agents.stop-all (default ctrl+x ctrl+k) stops every running agent at once.

Configuration

Every field in the top-level agents object must be an integer and is validated strictly.

OptionDefaultRangeDescription
agents.maxConcurrent41–8Task agents running at once; further tasks queue.
agents.timeoutMinutes00–60Optional hard deadline per task agent; 0 disables the deadline.
agents.maxTurns241–100Soft completed turn-cycle budget; agents wrap up beyond it.

Set these values in the top-level agents object. See Configuration for file locations and merge behavior.