Execution engine that accepts a validated JSON plan
500
Execution engine that accepts a validated JSON plan over the Google A2A protocol, executes it as a directed acyclic graph (DAG) against external A2A agents, persists run and step state in SQLite, and returns structured results.
The orchestrator is a pure execution engine — no LLM, no planning logic. Upstream plan sources (plan_self_ask_agent, plan_judge_agent) submit plans as A2A tasks; the orchestrator validates, schedules, and drives them to completion.
message/send)on_failure is "replan" or "replan_with_approval"run_id and final status as an A2A artifact| Variable | Default | Description |
|---|---|---|
API_KEY | (empty) | Optional bearer token required on all requests except /health and /.well-known/* |
PORT | 8080 | Port to listen on |
DB_URL | sqlite+aiosqlite:///./orchestrator.db | Database URL (SQLAlchemy async format). Schema is auto-created on startup — no migrations required. |
AGENT_TIMEOUT_DEFAULT | 60 | Default per-step agent timeout in seconds |
MAX_PLAN_SIZE_BYTES | 1048576 | Maximum accepted plan payload size (1 MB) |
MAX_NODE_COUNT | 100 | Maximum number of nodes in a plan |
MAX_REPLANS | 3 | Maximum number of automatic replans per run |
SECRET_PREFIX | SECRET_ | Prefix for secret env var resolution (see below) |
TRAJECTORY_DIR | (unset) | If set, writes per-step execution files to this directory |
HIL_AGENT_URL | (unset) | URL of the Human-in-the-Loop agent (required for on_failure: "replan_with_approval") |
HIL_AGENT_API_KEY | (unset) | API key for the HIL agent |
HIL_APPROVAL_TIMEOUT | 86400 | Seconds to wait for human approval before timing out |
REPLAN_AGENT_URL | (unset) | URL of the plan agent used for automatic replanning (overrides plan.replan_agent if set) |
REPLAN_AGENT_API_KEY | (unset) | API key for the replan agent |
Secret refs in the plan (e.g. secrets/gmail/password) are resolved at execution time from environment variables:
SECRET_SECRETS_GMAIL_PASSWORD=hunter2
The ref is uppercased and / and - are replaced with _, then the SECRET_ prefix is prepended.
When TRAJECTORY_DIR is set, per-step files are written under TRAJECTORY_DIR/<run_id>/step_<step_id>/:
resolved_inputs.json — inputs after secret injection and upstream output propagationagent_request.json — full A2A JSON-RPC body sent to the agentagent_response.json — raw A2A response receivedstep_result.json — final status, outputs, error, duration in millisecondsBuild from inside this directory (apps/orchestrator_agent/).
The shared packages/a2a_agent package is passed as a named build context using
Docker BuildKit's --build-context flag (Docker Desktop or Docker Engine ≥ 23).
# From apps/orchestrator_agent/
docker build \
--build-context a2a_agent=../../packages/a2a_agent \
-t orchestrator-agent:latest \
.
docker run --rm \
-p 8080:8080 \
orchestrator-agent:latest
docker run --rm \
-e API_KEY=my-secret-token \
-p 8080:8080 \
orchestrator-agent:latest
docker run --rm \
-e TRAJECTORY_DIR=/trajectories \
-v /tmp/orch-trajectories:/trajectories \
-p 8080:8080 \
orchestrator-agent:latest
docker run --rm \
-e SECRET_SECRETS_GMAIL_PASSWORD=hunter2 \
-e SECRET_SECRETS_CHROME_AGENT_TOKEN=tok-xyz \
-p 8080:8080 \
orchestrator-agent:latest
docker run --rm \
-e DB_URL=sqlite+aiosqlite:////data/orchestrator.db \
-v /tmp/orch-data:/data \
-p 8080:8080 \
orchestrator-agent:latest
The orchestrator exposes a standard A2A endpoint at POST /. Upstream agents submit plans as A2A tasks.
Agent card: GET /.well-known/agent.json
Health check: GET /health → {"status": "ok"} (no auth required)
curl -s http://localhost:8080/ \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "message/send",
"id": "req-001",
"params": {
"message": {
"role": "user",
"messageId": "msg-001",
"parts": [{"type": "text", "text": "<plan JSON here>"}]
}
}
}'
On success the task artifact contains:
{"run_id": "uuid", "status": "running"}
On validation failure the task is marked failed and the artifact contains:
{
"valid": false,
"errors": [
{"code": "CYCLE_DETECTED", "message": "...", "location": "step_graph.edges", "severity": "error"}
]
}
Set execution_handoff.mode to plan_only in the plan. The orchestrator returns validation results without creating a run.
A minimal valid plan:
{
"plan_id": "550e8400-e29b-41d4-a716-446655440000",
"summary": "Example plan",
"original_request": "Find the latest invoice in Gmail",
"assumptions": [],
"pipeline_stages": ["research", "process"],
"required_agents": [
{
"name": "web.browser.chrome",
"url": "https://chrome-agent:8080",
"role": "research",
"price_per_turn": 0.0,
"auth": {"type": "none"}
}
],
"step_graph": {
"is_dag": true,
"entry_node_ids": ["S1"],
"exit_node_ids": ["S1"],
"nodes": [
{
"id": "S1",
"title": "Find invoice",
"stage": "research",
"assigned_agent_name": "web.browser.chrome",
"inputs": {
"request_fragment": "Open Gmail and return the filename of the latest invoice attachment.",
"context": {},
"secret_refs": {}
},
"outputs": ["attachment_filename"],
"risk_level": "high",
"execution_policy": {
"timeout_seconds": 120,
"max_retries": 1,
"on_failure": "fail_run"
}
}
],
"edges": []
},
"execution_handoff": {
"mode": "execute",
"ready_for_orchestrator": true
}
}
| Field | Type | Description |
|---|---|---|
plan_id | string (UUID) | Unique plan identifier |
summary | string | Human-readable plan description |
original_request | string | Original NL task that produced this plan (used as context when replanning) |
workspace_id | string | Shared S3 workspace prefix set by Core to the run ID |
replan_agent | AgentDef | null | Agent to call for automatic replanning; falls back to REPLAN_AGENT_URL env var |
assumptions | string[] | Planner assumptions |
pipeline_stages | string[] | Ordered stage names |
required_agents | AgentDef[] | Agents used by this plan |
step_graph | StepGraph | DAG of execution steps |
execution_handoff | ExecutionHandoff | mode: "execute" or "plan_only" |
execution_policy.on_failure values| Value | Behaviour |
|---|---|
"fail_run" | Mark the run as failed immediately (default) |
"replan" | Call the replan agent automatically and continue without human approval |
"replan_with_approval" | Call the HIL agent for yes/no approval, then call the replan agent if approved |
Replans are capped at MAX_REPLANS per run (default 3). Mid-run amendments sent via A2A do not count against this cap.
While a run is executing, send an amended plan JSON to the running A2A task to substitute the plan in-flight:
# Send amended plan to a running task (replace <task_id> and <plan_json>)
curl -s https://orchestrator:8080/ \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "message/send",
"id": "amend-001",
"params": {
"message": {
"role": "user",
"taskId": "<task_id>",
"messageId": "msg-amend-001",
"parts": [{"type": "text", "text": "<amended plan JSON>"}]
}
}
}'
The engine drains the inbox before each step. If the message parses as a valid Plan, it substitutes the running plan and restarts execution from the beginning of the amended plan. Steps already completed are not re-run — only remaining steps are executed.
Send a message with command: "pause" to suspend execution between steps:
{"parts": [{"type": "text", "text": "{\"command\": \"pause\"}"}]}
The engine completes the current step, then blocks until the next message arrives. The resume message may optionally carry an amended plan JSON to inject before resuming.
See INSTRUCTIONS.md for the full plan contract specification.
uv sync --extra server --extra dev
uv run pytest tests/ -v
uv run python -m app.server
The server starts on port 8080 with a self-signed TLS certificate at /tmp/agent.crt / /tmp/agent.key.
The agent image exposes an MCP (Model Context Protocol) server on the same port as A2A at /mcp. Any MCP client — Claude Desktop, Cursor, Windsurf, ChatGPT Connectors, OpenAI Agents SDK — can list and invoke these tools using the pod's API_KEY as Bearer.
In production, Core proxies https://orchestrator-agent.agents.forfetch.ai/mcp → the worker pod's /mcp.
| Tool (skill_id) | Description |
|---|---|
orchestrate | Execute a DAG plan by dispatching steps to sub-agents |
Input schemas are authored in apps/agents_mcp/app/seed.py. Skills without an explicit schema advertise a single {task: str} freeform parameter.
Content type
Image
Digest
sha256:202c1c528…
Size
148.3 MB
Last updated
4 months ago
docker pull superbizon007/orchestrator-agent