plugins/plugin-agent-orchestrator/README.md
The canonical orchestration plugin for ElizaOS task agents. Spawns local coding agents (codex, claude, opencode) via the acpx CLI using the structured Agent Client Protocol, routes their output back through the runtime so the main agent decides what to do, and bundles workspace lifecycle, GitHub PR integration, task share, and supporting services in a single package.
Naming: this plugin is not the same thing as
@elizaos/plugin-acp. That package is Shaw's ACP gateway client (IDE bridge over a remote ACP gateway).@elizaos/plugin-agent-orchestratoris the task backend that usesacpxto run coding agents as subprocesses on the same host as the runtime.
The plugin combines three concerns:
tool_call / tool_call_update, agent_message_chunk, cooperative session/cancel, parallel sessions in the same workspace, recoverable via session/load.task_complete, error, blocked) back into the runtime as synthetic inbound messages addressed to the original roomId/userId/messageId. The main agent's normal action layer then decides whether to REPLY to the user, SEND_TO_AGENT to push the sub-agent further, or both. See docs/sub-agent-routing.md.npm install @elizaos/plugin-agent-orchestrator
npm install -g acpx@latest
acpx --version
You also need at least one ACP-compatible agent CLI (codex, claude, or opencode) installed and authenticated.
import agentOrchestratorPlugin from "@elizaos/plugin-agent-orchestrator";
export default {
plugins: [agentOrchestratorPlugin],
};
| Action | Purpose |
|---|---|
ACPX_CREATE_TASK (CREATE_TASK) | One-shot: spawn + prompt + return. Captures origin metadata for routing. |
SPAWN_AGENT | Start a long-lived ACP coding-agent session. Returns data.agents[]. |
SEND_TO_AGENT | Send a follow-up prompt to a running session. The main agent uses this to push a sub-agent further when its proof is unsatisfying. |
STOP_AGENT | Cooperatively cancel + close a session. |
LIST_AGENTS | List active and persisted sessions. |
CANCEL_TASK | Cancel an in-flight task while preserving history. |
TASK_HISTORY / TASK_CONTROL / TASK_SHARE | ACP session lifecycle and sharing helpers. |
PROVISION_WORKSPACE / FINALIZE_WORKSPACE | Git workspace setup, commit, push, PR open. |
MANAGE_ISSUES | GitHub issue create/list/update/close. |
AVAILABLE_AGENTS — adapter inventory + raw session list.ACTIVE_SUB_AGENTS — cache-stable view of currently-routed sub-agent sessions; sorted by sessionId, structural fields only (no timestamps, no message excerpts), so the planner-visible block stays cached across status flips.ACTIVE_WORKSPACE_CONTEXT — live workspace/session state.CODING_AGENT_EXAMPLES — structured action call examples.AcpService — ACP subprocess lifecycle, NDJSON parsing, session state, event emission. Registers under ACP_SUBPROCESS_SERVICE.SubAgentRouter (canonical) — subscribes to AcpService.onSessionEvent, posts terminal-event synthetic memories to runtime.messageService.handleMessage. Per-session round-trip cap (ACPX_SUB_AGENT_ROUND_TRIP_CAP, default 32) force-stops runaway loops. Disable with ACPX_SUB_AGENT_ROUTER_DISABLED=1.CodingWorkspaceService — git workspace lifecycle helpers.import { AcpService, SubAgentRouter } from "@elizaos/plugin-agent-orchestrator";
const acp = runtime.getService("ACP_SUBPROCESS_SERVICE") as AcpService;
const { sessionId } = await acp.spawnSession({
agentType: "codex",
workdir: "/tmp/my-task",
approvalPreset: "permissive",
metadata: {
roomId: message.roomId,
userId: message.entityId,
messageId: message.id,
label: "fix bug 42",
},
});
const result = await acp.sendPrompt(sessionId, "what is 7 + 8?");
console.log(result.finalText); // "15"
console.log(result.stopReason); // "end_turn"
console.log(result.durationMs); // 4864
acp.onSessionEvent((sessionId, eventName, data) => {
// eventName: "ready" | "message" | "tool_running" | "task_complete" | "stopped" | "error" | "blocked" | "login_required" | "reconnected"
// data shape depends on eventName, see SessionEventName in src/services/types.ts
});
The task_complete event:
{ response: string, durationMs: number, stopReason: "end_turn" | "error" | string }
You usually don't subscribe directly — SubAgentRouter already does, and routes terminal events into the runtime. Subscribe only if you need raw access (e.g. dashboards).
All configuration is via environment variables. Sensible defaults; most users only need ELIZA_ACP_CLI if acpx is not on PATH.
| Variable | Default | Purpose |
|---|---|---|
ELIZA_ACP_CLI | acpx | ACPX executable name or absolute path. |
ELIZA_ACP_DEFAULT_AGENT | codex | Default agent type. |
ELIZA_ACP_DEFAULT_APPROVAL | autonomous | Approval preset (read-only, auto, permissive, autonomous, full-access). |
ELIZA_ACP_PROMPT_TIMEOUT_MS / ACPX_DEFAULT_TIMEOUT_MS | 1800000 (30m) | Per-prompt timeout. |
ELIZA_ACP_STATE_DIR | ~/.eliza/plugin-acpx | Where to persist session state when no runtime DB. |
ACPX_DEFAULT_CWD | runtime cwd | Base directory for spawned agent workdirs. |
ELIZA_ACP_MAX_SESSIONS | 8 | Concurrent session cap. |
ACPX_SUB_AGENT_ROUTER_DISABLED | unset | Set to 1 to keep the router service registered but unbound (test/staging). |
ACPX_SUB_AGENT_ROUND_TRIP_CAP | 32 | Per-session inject cap before force-stop to prevent ping-pong loops. |
Session state is persisted with a tiered backend:
runtime.databaseAdapter exposes SQL methods, sessions live in the acp_sessions table.$ELIZA_ACP_STATE_DIR/sessions.json (atomic writes via temp+rename).Map (warns that sessions won't survive restart).Two smokes ship with the repo:
# Raw AcpService against installed acpx + codex:
npm install -g acpx@latest
# authenticate codex first
npm run build
node tests/e2e/acp-codex-smoke.mjs
# Full router loop (vitest, gated):
RUN_LIVE_ACPX=1 bun run test
acp-codex-smoke.mjs spawns a real codex session, sends "what is 7 + 8?", and verifies task_complete fires with response "15". The vitest live test (__tests__/live/sub-agent-router.live.test.ts) verifies the synthetic Memory routes back from a real subprocess into a fake messageService.handleMessage with all routing keys intact. Both no-op (skip) when acpx isn't installed.
0.2.0 — package. ACP subprocess sessions are the only task-agent spawn path.
PRs welcome. Run npm run typecheck && npm test before opening.
MIT. See LICENSE.