Back to Eliza

@elizaos/plugin-agent-orchestrator

plugins/plugin-agent-orchestrator/README.md

2.0.17.5 KB
Original Source

@elizaos/plugin-agent-orchestrator

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-orchestrator is the task backend that uses acpx to run coding agents as subprocesses on the same host as the runtime.

What it does

The plugin combines three concerns:

  1. Spawn coding agents via ACP. The ACP path uses typed JSON-RPC events — tool_call / tool_call_update, agent_message_chunk, cooperative session/cancel, parallel sessions in the same workspace, recoverable via session/load.
  2. Route sub-agent terminal events (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.
  3. Coordinate workspace lifecycle (clone, branch, commit, push, PR open) and GitHub issue management for repo-hosted tasks.

Installation

bash
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.

Quick start

ts
import agentOrchestratorPlugin from "@elizaos/plugin-agent-orchestrator";

export default {
  plugins: [agentOrchestratorPlugin],
};

Action surface

ActionPurpose
ACPX_CREATE_TASK (CREATE_TASK)One-shot: spawn + prompt + return. Captures origin metadata for routing.
SPAWN_AGENTStart a long-lived ACP coding-agent session. Returns data.agents[].
SEND_TO_AGENTSend 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_AGENTCooperatively cancel + close a session.
LIST_AGENTSList active and persisted sessions.
CANCEL_TASKCancel an in-flight task while preserving history.
TASK_HISTORY / TASK_CONTROL / TASK_SHAREACP session lifecycle and sharing helpers.
PROVISION_WORKSPACE / FINALIZE_WORKSPACEGit workspace setup, commit, push, PR open.
MANAGE_ISSUESGitHub issue create/list/update/close.

Providers

  • 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.

Services

  • 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.
ts
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

Subscribing to events

ts
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:

ts
{ 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).

Configuration

All configuration is via environment variables. Sensible defaults; most users only need ELIZA_ACP_CLI if acpx is not on PATH.

VariableDefaultPurpose
ELIZA_ACP_CLIacpxACPX executable name or absolute path.
ELIZA_ACP_DEFAULT_AGENTcodexDefault agent type.
ELIZA_ACP_DEFAULT_APPROVALautonomousApproval preset (read-only, auto, permissive, autonomous, full-access).
ELIZA_ACP_PROMPT_TIMEOUT_MS / ACPX_DEFAULT_TIMEOUT_MS1800000 (30m)Per-prompt timeout.
ELIZA_ACP_STATE_DIR~/.eliza/plugin-acpxWhere to persist session state when no runtime DB.
ACPX_DEFAULT_CWDruntime cwdBase directory for spawned agent workdirs.
ELIZA_ACP_MAX_SESSIONS8Concurrent session cap.
ACPX_SUB_AGENT_ROUTER_DISABLEDunsetSet to 1 to keep the router service registered but unbound (test/staging).
ACPX_SUB_AGENT_ROUND_TRIP_CAP32Per-session inject cap before force-stop to prevent ping-pong loops.

Persistence

Session state is persisted with a tiered backend:

  1. If runtime.databaseAdapter exposes SQL methods, sessions live in the acp_sessions table.
  2. Otherwise, JSON file at $ELIZA_ACP_STATE_DIR/sessions.json (atomic writes via temp+rename).
  3. Last resort: in-memory Map (warns that sessions won't survive restart).

End-to-end smoke tests

Two smokes ship with the repo:

bash
# 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.

Status

0.2.0 — package. ACP subprocess sessions are the only task-agent spawn path.

Contributing

PRs welcome. Run npm run typecheck && npm test before opening.

License

MIT. See LICENSE.