Background Task
When a task (a sub-agent invocation or a shell command) is expected to take more than ~30 seconds, the agent has two choices:
- Block: wait for the task to finish, holding the conversation hostage.
- Background: launch it, get a handle, continue working, and check on it later.
This Skill is about knowing when to choose (2) and how to record the handle so the agent (or the user) can check on it later.
mcode 0.2.4 surface
There are two background-capable tools on mcode 0.2.4, and the right one depends on whether the background work is a sub-agent or a shell command.
Sub-agent background: task(run_in_background: true) + task_query / task_output / task_stop
The canonical task schema:
task(
description: string, // 3-5 word label, required
prompt: string, // the brief, required
agent_name: "explore" | "worker" | "verifier", // required
run_in_background?: boolean // optional; true = async, false = sync (default)
)
When run_in_background: true, mcode returns immediately with a task_id.
The companion tools (also canonical in cli.js):
| Tool | Purpose | Required fields |
|---|---|---|
task_query(task_id?, status?) |
List session tasks (omit task_id) or get one. |
task_id for single fetch; status filter optional. |
task_output(task_id, offset?) |
Read a task's output incrementally. | task_id (required); offset (optional, byte offset for long output). |
task_stop(task_id, reason?) |
Request a background task to stop. | task_id (required); reason (optional, human-readable). |
run_in_background: false (the default) blocks the calling turn until the
sub-agent finishes and returns its final result.
Shell background: bash(run_in_background: true)
The bash tool's canonical schema (from cli.js:xza):
bash(
command: string, // required
timeout?: number, // optional, seconds
run_in_background?: boolean // optional; true = async, false = sync (default)
)
When run_in_background: true, the bash call returns immediately with a
job handle. Conceptual pseudocode below. The exact shape of the returned
handle is NOT part of the public mcode 0.2.4 runtime contract; the host's
job-control API is the source of truth for the underlying process id. The
Skills below treat the handle as opaque and locate the process id by means
outside the mcode contract (the launch context, the host's job-control API,
or the calling agent's own bookkeeping). Windows: Stop-Process -Id <process-id>;
POSIX: kill <process-id>, both invoked through a foreground bash call
rather than any action="kill" field. The placeholder <process-id> is
whatever process id the host's job-control API identifies; the launching
agent must record it (or a way to resolve it) by its own means, separate
from this Skill. There is no task_name= and no action="kill"
field. The Codex-harness shape bash(task_name=..., run_in_background=true, action="kill") is not the mcode surface — mcode's bash validator
rejects any key outside command / timeout / run_in_background.
Killing a shell background job: invoke the host's job-control API in a
foreground bash call. Windows: Stop-Process -Id <process-id>. POSIX:
kill <process-id>. The Skills do not pretend bash(action="kill") exists
on mcode 0.2.4. The <process-id> value is host-internal (e.g. a Windows
PID resolved via Get-Process, or a POSIX pid resolved via ps -p); the
launching agent records it out-of-band from this Skill.
When to use
Activate when any of these is true:
- A sub-agent is expected to take > 1 minute (deep research, multi-file refactor, anything you cannot predict the duration of).
- A shell command is expected to take > 30 seconds (
cargo test,npm install,docker build, a long-running dev server, a large data download). - The user explicitly says "background" / "后台" / "non-blocking" / "in the background".
- You need a long-running process to coexist with ongoing work (a dev server, a watch script, a streaming pipeline).
- You would otherwise block the conversation on a result the user can come back to later.
When NOT to use
- The task / command finishes in <5 seconds.
- The user explicitly wants the output now (interactive REPL, vim, ssh, a build whose output the next step depends on).
- The command is interactive (it expects a TTY or human input).
Process
- Estimate the duration. If unsure, assume the worst case. The mcode
tasktool description (run_in_background) says "Set to true when the sub-task is open-ended or expected to take more than ~1 minute (deep research, multi-step investigation, large refactors, anything you cannot predict the duration of), so you can keep working and the result is reported back automatically when it completes. Leave false (the default) for short, well-scoped sub-tasks whose result you need right now to continue. When in doubt for a long or uncertain task, prefer true." - Choose a descriptive handle. The agent (and the user) will need to
recognise it later.
dev-serveris good.task1is bad. - Launch in the background using the matching tool:
- Sub-agent:
task(..., run_in_background: true); mcode returns atask_id. Store it. - Shell:
bash(command: "npm run dev", run_in_background: true); mcode returns a job handle (the exact shape is not part of the public runtime contract; the host's job-control API is the source of truth). Store the handle.
- Sub-agent:
- Record the handle. In a multi-step task, store the handle (task_id,
job id, log path) somewhere persistent — in a
world-state-trackingfile, asession-handoffnote, or in the running brief. - Continue working. The conversation does not block on the background task.
- When the result matters:
- Sub-agent:
task_query(task_id)for status,task_output(task_id)for output,task_stop(task_id)to stop. - Shell: foreground
bashcall against the host's job-control API (Get-Process -Id <process-id>/Stop-Process -Id <process-id>on Windows;ps -p <process-id>/kill <process-id>on POSIX). Read the log file or stdout from the original launch.<process-id>is the placeholder for the process id the launching agent recorded at launch time (out-of-band from this Skill); mcode 0.2.4 does not document thebashjob-handle shape.
- Sub-agent:
Output contract
After activating this Skill, the agent's next message MUST include:
- The chosen handle (
task_idor job id) and the tool that produced it (task/bash). - The expected duration estimate.
- The log or status path so a later turn can check on it.
- Whether the agent is continuing or blocking on the result.
Common pitfalls
- Launching and forgetting the handle — the user comes back in an hour, the agent has no idea which process was which. Always record the handle.
- Re-using a generic name —
task1collides;cargo-testdoes not. - Polling too eagerly — a 5-minute build polled every 5 seconds wastes context. Poll on a sensible cadence (every minute for builds, every 5 minutes for downloads).
- Killing without saving output — read the log first, then kill, otherwise the result is lost.
- Using Codex-only
bash(task_name=..., action="kill")syntax — mcode 0.2.4'sbashdoes not have those fields. Usetask_stop(task_id=...)for sub-agents and a foregroundbashcall to the host's job-control API for shell jobs. - Passing
model_config_idin a backgroundtask()call — thetasktool does not accept it (seemodel-router). The model is the session's current model.
Example
The example below is MiniMax Code 0.2.4 task / bash tool syntax. Two
background launches are demonstrated.
Sub-agent background
# Launch a long-running research sub-agent in the background.
# mcode returns a task_id we can later query / read / stop.
> task(
description="Research migration paths",
agent_name="explore",
run_in_background=true,
prompt="""
Investigate migration paths from <lib-A> to <lib-B> in the
<repo> codebase. Produce a markdown report at
<repo>/notes/migration.md comparing the top 3 candidates
with code samples, risk notes, and a recommended path.
This may take 10+ minutes; you can take your time.
"""
)
# Returns immediately with:
# { task_id: "tsk_01HXYZ...", status: "queued" }
# Later, check status:
> task_query(task_id="tsk_01HXYZ...")
# { task_id: "tsk_01HXYZ...", status: "running", ... }
# Read partial output (the report grows as the sub-agent works):
> task_output(task_id="tsk_01HXYZ...", offset=0)
# <partial markdown content>
# next_offset: 12345
# Stop it if the user changed their mind:
> task_stop(task_id="tsk_01HXYZ...", reason="user changed scope")
# { task_id: "tsk_01HXYZ...", status: "stopping" }
Shell background
# Launch a long-running dev server in the background.
# mcode returns a job id we can later target via the host's job-control API.
#
# CONCEPTUAL PSEUDOCODE for the "check / kill" steps: the mcode 0.2.4
# `bash` tool does not document the shape of the returned job handle
# and does not document a kill action. The launching agent must record
# the underlying process id (Windows PID / POSIX pid) at launch time,
# out-of-band from this Skill. The `<process-id>` placeholder below is
# that recorded value. On a real install, replace it with the actual
# process id; do not assume the handle is a raw integer PID or that it
# parses to one.
> bash(
command="npm run dev",
run_in_background=true
)
# Returns immediately with a job handle. The exact shape is not
# part of the public mcode 0.2.4 runtime contract; the host's
# job-control API is the source of truth. Treat the handle as
# opaque and pass it to the host's job-control API in a
# foreground `bash` call (e.g. `Stop-Process -Id <process-id>` /
# `kill <process-id>` on POSIX) when you need to stop the job.
# Later, check whether it is still alive (foreground bash call):
> bash(
command="Get-Process -Id <process-id> | Select-Object Id,ProcessName,StartTime"
)
# (or on POSIX: `ps -p <process-id> -o pid,etime,cmd`)
# Stop it when done (foreground bash call to the host's job-control API):
> bash(
command="Stop-Process -Id <process-id>"
)
# (or on POSIX: `kill <process-id>`)
The decision (background, with a recorded handle) is the same; the
execution mechanism depends on whether the background work is a sub-agent
(use task + task_query / task_output / task_stop) or a shell command
(use bash(run_in_background: true) + the host's job-control API).
Verification checklist
- Did you estimate the duration before choosing background vs blocking?
- Did you choose a descriptive handle (not
task1)? - Did you use the right tool —
taskfor sub-agents,bashfor shell commands? - Did you set
run_in_background: true(not Codex'sbash(task_name=...))? - Did you record the handle (task_id / job id / log path) in a persistent place?
- Did you tell the user "I launched X in the background, here's the handle and log path"?
- If you stopped it, did you use
task_stop(task_id=...)(sub-agent) orStop-Process/killvia a foregroundbashcall (shell)?