Repository navigation
Controllers
A controller is the runtime that actually executes a dispatched agent — the process conductor
spawns (or the API it calls) to turn a prompt into work. Every agent runs on the built-in paseo
controller unless you configure otherwise; controllers are entirely optional; a config with no
controllers: block resolves every agent to paseo and behaves exactly as before. Configuring
controllers: lets some or all agents run on a different runtime instead — an ACP agent over
stdio, opencode's own HTTP API, agent-deck, or a bare CLI recipe.
controllers: # named map, like `agents:`
gemini-review: # `agent:` + no transport → ACP; runs the gemini CLI over
agent: gemini # stdio. The runtime IS gemini, so provider/model on the
# agent profile below don't apply.
opencode: # `type: opencode` → opencode's HTTP API (`opencode serve`),
type: opencode # which routes by provider/model.
claude-cli: # `transport: cli` → a bare per-tool recipe (claude-code and
agent: claude-code # codex ship built-in recipes; anything else needs `command:`).
transport: cli
agents:
reviewer:
controller: gemini-review # runs gemini over ACP — no provider needed (the controller IS the agent)
planner:
controller: opencode # runs via opencode…
provider: anthropic # …which routes to this provider + model
model: claude-sonnet-4-5
fixer:
provider: claude # no controller: → the built-in paseo runtime runs claude
model: opus| Field | Meaning |
|---|---|
controllers.<name> |
One controller entry, named like an agents: profile. |
type |
Controller kind: paseo (built-in, always registered), opencode, or agent-deck. Omit for an ACP or cli controller — those are inferred from agent:/transport:. |
agent |
For ACP: the agent binary to spawn over stdio (gemini, claude-code-acp, codex-acp, goose, opencode, …). For transport: cli: the CLI recipe name (claude-code, codex, or any name paired with an explicit command:). |
transport |
Overrides the inferred transport. acp is the default when agent: is set and no type: is given. cli selects the bare-subprocess recipe path. native (with agent: opencode) selects opencode's own transport instead of ACP. |
default |
Flags this entry as the fleet default for agents with no explicit controller:. At most one entry may set this — validated at config load. |
command |
Explicit subprocess argv for a cli transport whose agent: name isn't one of the built-in recipes. |
agents.<name>.controller |
Selects a controllers.<name> entry for this agent profile. Unset → falls through the resolution order below. |
provider/model on the agent profile (not the controller) select the model for controllers
that route by provider — paseo, opencode, and agent-deck. For an acp or cli controller
the runtime is the named agent/command, so provider/model on the profile are ignored —
don't pair provider: claude with an acp controller pointed at gemini. See Agents for the
full agent-profile shape.
- An explicit
controller:on the agent's profile. - The controller entry flagged
default: true(at most one may be; validated at load). - The built-in
paseocontroller.
| Config | Runtime | Session model |
|---|---|---|
type: paseo (built-in, always registered as paseo) |
The existing paseo CLI dispatcher |
native |
agent: <name> (or explicit transport: acp) |
An ACP agent over JSON-RPC 2.0 on stdio — gemini, claude-code-acp, codex-acp, goose, opencode over ACP, … (Agent Client Protocol, Zed's open standard; conductor is the ACP client) | native, upgraded to resumable if the agent negotiates loadSession
|
type: opencode (or agent: opencode + transport: native) |
opencode's native HTTP server (opencode serve) — its own model routing/cost accounting |
resumable |
type: agent-deck |
The agent-deck CLI orchestrator (launch / session send / session show / remove) |
native |
transport: cli |
A bare per-tool command recipe run as a direct subprocess (built-in recipes for claude-code and codex; anything else needs an explicit command:) |
resumable (e.g. claude-code --resume) or oneshot (codex, a generic command:) |
An unrecognized type/transport stays registered as a stub: it negotiates capabilities but
returns an error when selected instead of silently falling back to another runtime, so config can
name a controller this build doesn't drive yet without an agent quietly running somewhere else.
- native — the controller owns the whole session lifecycle (paseo, agent-deck).
-
resumable — a session survives by id and is resumed on demand rather than held as a live
process (an ACP agent advertising
loadSession, opencode, or aclirecipe that can--resume). - oneshot — each turn is a fresh process; no persistent session.
Two portable capabilities sit above the controller layer, so a session behaves the same regardless of which runtime it runs on:
- The session broker keeps one live/resumable session per PR and funnels follow-ups to it — a
burst of triggers collapses onto the same session instead of spawning duplicates, and the
PR→session binding is persisted (
sessions.json) so an interactive hand-off survives a conductor restart — the next follow-up re-attaches by id instead of orphaning the old session. - An optional [[Hand-offs|
handoffs:]] block adds a portable web-link (or Slack/Discord) review channel:With it configured, ahandoff: web: base_url: https://conductor.example.com # public origin the draft link points at listen: :8099 # inbound address the draft page is served on (default :8099)
background: trueWorkflows's review runs over that channel's present → await → revise/submit loop instead of paseo's own UI — the only way to interactively review an agent on a controller with no native interactive surface (cli, or opencode's native transport). With nohandoffs:block, review hand-off keeps today's paseo-native behavior, unchanged.
Escalations and hand-off prompts go out through the same Notifications channels as everything else (journal, plus any configured Slack/Discord/ntfy/Pushover/Notifiarr) — controllers don't add a separate notification path.
The Dockerized e2e harness at test/e2e/ exercises the full controller matrix (every kind above,
resolution, the session broker, hand-off, and failure/escalation) against an isolated local forge +
mock GitHub API — make e2e (hermetic stubs, no keys, CI-safe) and make e2e-live (the real agent
CLIs installed on your box, manual).
- With no
controllers:block at all, resolution always yieldspaseoand existing behavior is unchanged — the block is purely additive. - Provider API keys for an ACP agent (e.g.
GEMINI_API_KEY) live in that agent's own process environment, not in the controller config — conductor spawns the process, it doesn't proxy credentials into it. - A controller is resolved once per dispatched agent, at dispatch time, from the agent profile that action names — not per event or per step, so a multi-step Workflows mixing agents on different controllers works step by step.
- Selecting a stub controller (an unrecognized
type/transport) is a hard error at dispatch, not a silent fallback topaseo— this is deliberate: naming a controller your build doesn't implement should fail loudly rather than run the agent on the wrong runtime.
An agent (see Agents) is a named profile — provider, model, mode, workspace policy,
guidance — and a controller is what actually runs it. The same fixer profile
(provider: claude) can execute on the built-in paseo runtime, on agent-deck, or via opencode's
HTTP API by pointing its controller: at a different entry, with no change to the profile itself.
The exception is ACP and cli controllers, where the controller entry's own agent:/command:
is the runtime being invoked — there's no separate provider/model to route, so those fields on
the profile are ignored for that agent. In short: the controller is the runner; the agent's
provider/model is what it talks to (when the runner supports routing at all).
Setup
The model
- Connectors
- Workflows
- Reuse
- Settings-and-Templating
- Packs
- Verbs
- Code-Steps
- Stores
- Runtimes
- Model-Selection
- Model-Discovery
- Steps
- Decide-Steps
- Grouping
- Memory
- Binary-Data
- Agent-Skill
- Policy
- Gates
- Teams
- Outcomes
- Cost-Accounting
- Secrets
- Hosts
- Isolation
- Trust-and-Isolation
Connectors
Operations
- One-Shot
- Callable-Service
- Runs
- Hand-offs
- Notifications
- Migration
- Controllers (legacy name → Runtimes)