Background control tools
bg_list, bg_check, bg_cancel, and bg_wait — LM-facing tools for supervising background agent runs.
Background control tools
When a run carries an active BackgroundTaskDispatcher and the agent exposes at least one background-eligible tool, the runner auto-injects four built-in tools:
| Tool | Purpose |
|---|---|
bg_list | List in-flight tasks for the current agent + thread |
bg_check | Inspect status and recent trace events for one task |
bg_cancel | Request cooperative cancellation |
bg_wait | Block until one or more tasks reach a terminal state |
These mirror Python tools/builtin/background_control.py and require no per-agent wiring — they read the active RunContext via getActiveRunContext().
Auto-injection gate
Injection fires only when both conditions hold:
RunContext.dispatcheris set (fromManiac.backgroundTasksorRunOptions.dispatcher)- At least one resolved tool has
backgroundwithenabled !== false, or is a delegated sub-agent wrapper eligible for background dispatch
Without a dispatcher the bg_* tools have nothing to call; without an eligible tool the surface would be noisy for no benefit.
bg_list
Returns every pending task for the active (agent_id, thread_id):
{
"ok": true,
"value": {
"tasks": [
{
"task_id": "abc123",
"status": "running",
"tool": "research",
"enqueued_at": "2026-06-08T12:00:00.000Z"
}
]
}
}Calling bg_list outside an active run returns ok: false with a descriptive error instead of throwing.
bg_check
Pass a task_id from bg_list or a background tool's synthetic ack. Optionally set last_n_events to include recent trace digests:
await bgCheck.invoke({ task_id: "abc123", last_n_events: 5 });bg_cancel
Request cancellation of an in-flight task. Returns whether the cancel signal was delivered or the task was already terminal:
{ "ok": true, "value": { "ok": true, "status": "cancelled", "task_id": "abc123" } }bg_wait
Block until tasks terminate. The schema advertises a single task-targeting field — task_ids: string[] — plus optional mode and timeout_s:
// Single task
await bgWait.invoke({ task_ids: ["abc123"] });
// Fan-in: wait for all
await bgWait.invoke({ task_ids: ["a", "b", "c"], mode: "all" });
// First finisher
await bgWait.invoke({ task_ids: ["a", "b"], mode: "any" });mode | Behavior |
|---|---|
"all" (default) | Block until every listed task terminates |
"any" | Return when the first listed task terminates; others keep running |
timeout_s returns { status: "timeout", ... } without cancelling in-flight tasks.
Return shapes
- Single id in
task_ids:{ status, record, result }— same shape as legacy single-task waits - Multiple ids:
{ status, results, winner? }map keyed bytask_id
Legacy task_id fallback
The runtime silently accepts task_id: "<string>" when task_ids is absent (older traces and strict-mode hedges that emit task_id: "" alongside a populated array). When both are present, task_ids wins.
OpenAI-safe schemas
None of the bg_* input_schema objects use top-level oneOf, anyOf, allOf, enum, or not — OpenAI's function-parameter validator rejects those keywords at the schema root.
Supervisor example
import { runAgent, BackgroundTaskDispatcher, tool } from "@maniac-ai/agents";
const dispatcher = new BackgroundTaskDispatcher({
config: { enabled: true, global_concurrency: 4, per_agent_concurrency: 2 }
});
const spawn = tool({
name: "spawn_research",
description: "Start research in the background.",
inputSchema: {
type: "object",
properties: { query: { type: "string" } },
required: ["query"]
},
background: { enabled: true },
handler: ({ query }) => ({ query })
});
const result = await runAgent(
{ id: "supervisor", instructions: "Spawn then bg_wait.", model, tools: [spawn] },
"Research competitor pricing.",
{ dispatcher }
);The model can interleave bg_list / bg_check polling with bg_wait when it needs final answers before continuing.