Skip to content
Everruns Cloud is open in early access. Run agents without operating the platform.

Build a foreman agent

A foreman is an agent whose job is routing, not answering. It receives an ambiguous request, decides which specialist should handle it, delegates, and reports back. The specialists are ordinary Agents with their own prompts, tools, and models.

This is server-side delegation. If you want the application to chain agents in sequence, see Orchestrate multi-agent pipelines instead — that pattern keeps control in your code. A foreman keeps control in the agent, which is what you want when the trigger is a human asking for something in Slack.

  • Two or more Agents to delegate to, each already working on its own.
  • An agent to act as the foreman.
  • For the Slack front door: a Slack workspace where you can create apps.

Build and test each specialist on its own before wiring any delegation. A foreman that routes correctly to a broken worker looks like a broken foreman, and you will debug the wrong layer.

Give each one a narrow prompt and only the capabilities it needs. “Reviews Rust diffs for correctness bugs” routes better than “helps with code”, because the foreman picks targets from their descriptions.

Note their agent ids (agent_...).

Step 2 — Give the foreman the agent_handoff capability

Section titled “Step 2 — Give the foreman the agent_handoff capability”

Delegation targets are an explicit allowlist on the foreman. Configure the agent_handoff capability with one entry per worker:

{
"targets": [
{
"id": "code_reviewer",
"name": "Code Reviewer",
"description": "Reviews diffs and pull requests for correctness bugs",
"agent_id": "agent_...",
"required_connections": [],
"required_scopes": []
},
{
"id": "incident_responder",
"name": "Incident Responder",
"description": "Investigates alerts and production incidents",
"agent_id": "agent_..."
}
]
}

description is the field the model reads when choosing a target, so write it for that purpose. id is the stable key the foreman passes back in tool calls; keep it short.

Two properties worth knowing, because they shape what the foreman can do:

  • The foreman does not inherit the target’s tools, and never receives the target’s provider credentials. It can ask a worker to act; it cannot act as the worker.
  • required_connections gates a handoff on a provider connection existing before the run starts, which turns a mid-run credential failure into an up-front refusal.

required_scopes are audit labels in the current implementation, not enforced grants — a tool that needs hard authorization checks its own scopes before acting.

This is the part that decides whether the thing works, and the part no configuration can do for you. A foreman’s prompt needs four things:

  1. When to delegate and when to answer. Without this, a foreman either delegates trivia or answers things it should have routed. Be concrete: “If the request names a file, a diff, or a PR, hand off to code_reviewer.”
  2. How to choose between overlapping targets, and what to do when none fits — usually ask a clarifying question rather than guessing.
  3. What to do while work is running. Background handoffs return immediately; the foreman must know to report that it has dispatched, not to invent a result.
  4. How to report back. A foreman that delegates silently is worse than no foreman, because the requester cannot tell whether anything is happening.

Keep it short. A long routing prompt tends to produce a foreman that reasons about routing out loud in your Slack channel.

Step 4 — Understand what spawn_agent gives you

Section titled “Step 4 — Understand what spawn_agent gives you”

The foreman delegates with spawn_agent, using target.type = "agent" and the target.id from your config. The parameters that matter for a foreman:

ParameterWhy a foreman cares
modebackground (default) returns a task_id immediately so the foreman can dispatch several workers and stay responsive. foreground blocks until the child finishes. invite joins the target into the current session instead of a child one.
result_schemaA JSON Schema the child must satisfy. Turns “whatever the worker said” into a structured result the foreman can act on rather than re-parse.
public_contextNon-secret context appended to the child task. Use it for the Slack thread reference so the worker knows where the request came from.
instructionsThe work request. Must not contain credentials — the child has its own.

Background handoffs create a task with wake_policy = on_terminal, so the foreman is woken when a worker finishes. It does not poll, and you should not prompt it to.

While work is in flight the foreman manages it with the generic task tools: list_tasks, get_task, message_task to steer a worker mid-run, and cancel_task. These work identically for subagents, so a foreman can mix both kinds of delegation.

Both go through spawn_agent; the difference is what the child is.

target.type = "subagent"target.type = "agent"
Child configInherits the foreman’s harness and agent configurationThe target Agent’s own prompt, capabilities, MCP servers, model
Set-up costNone — spawn by nameBuild and allowlist the Agent first
Use whenThe work is the same kind of work, just parallelThe work needs different tools, a different model, or different authority

A foreman that fans out “review these six files” wants subagents. A foreman that routes “is this a code question or an incident?” wants handoff. Most real ones use both.

Subagent fan-out needs the separate subagents capability on the foreman — see Sub Agents. Nesting is depth-governed (max_subagent_depth) with root-tree caps on live and total descendant tasks, so a foreman cannot fork-bomb your org by accident.

Expose the foreman — and only the foreman — on Slack. The workers stay internal; they are reached through delegation, not by being mentioned.

Follow Publish an agent as a Slack app for the mechanics. Two choices matter for a foreman:

  • session_strategy: per_thread (the default). Each Slack thread becomes one foreman session, which is what you want: the thread is the unit of work, and the foreman keeps its delegation state for the life of that thread.
  • Enable the agent surface (agent_surface_enabled) if you want the foreman available in Slack’s assistant pane as well as in channels. The pane streams replies token-by-token and shows a status line while tools run.

Step 6 — Let the foreman act on Slack (optional)

Section titled “Step 6 — Let the foreman act on Slack (optional)”

Out of the box an agent can reply in its own thread and nothing else. It cannot add a reaction, send a DM, look someone up, or post to another channel.

If your foreman needs those — acknowledging a request with an emoji while work runs is the common one — attach a Slack MCP server as a capability on the foreman. MCP servers appear as virtual capabilities alongside built-in ones.

Be aware this means a second Slack token, separate from the channel’s bot token, with its own scopes to manage and rotate.

Step 7 — Test the routing, not the workers

Section titled “Step 7 — Test the routing, not the workers”

Send the foreman requests that are deliberately near the boundary between two targets, and requests that match none. Those are where routing fails. A request that obviously belongs to one worker will pass whether or not your prompt is any good.

Check that a dispatched request reports back into the thread that asked. A foreman that accepts work and reports somewhere else trains people to stop using it.

  • No approval buttons. Slack interactivity is not wired up, so a foreman cannot ask “approve this?” with a button and act on the click. It can only ask in prose and read the reply. For a foreman that dispatches consequential work, this is the real constraint.
  • Progress is per-turn, not per-task. The Slack status line reflects the foreman’s current turn. “3 of 5 workers finished” is available to the foreman via list_tasks but is not rendered into Slack for you; the foreman has to say it.
  • Two identities if you use a Slack MCP server, as above.