Agent Stack Architecture: The Four-Layer Model¶
Overview¶
The agent ecosystem has four distinct layers. Each project in the space occupies one or more of these layers. Understanding where each sits clarifies what Daedalus provides, what users bring, and where protocols like ACP and A2A fit.
┌─────────────────────────────────────────────────────────────────────┐
│ Layer 3: ORCHESTRATION PLATFORM │
│ Queue dispatch, elastic scaling, discovery, observability │
│ Daedalus, kagent │
│ │
│ Protocols: A2A (inter-agent), NATS (dispatch) │
├─────────────────────────────────────────────────────────────────────┤
│ Layer 2: AGENT WRAPPERS / FRAMEWORKS │
│ Session management, inter-agent comms, hooks, personas, memory │
│ copilot-bridge, GasTown, acpx/OpenClaw │
│ │
│ Protocols: ACP (to Layer 1), custom (internal) │
├─────────────────────────────────────────────────────────────────────┤
│ Layer 1: AGENT CLIs (the actual coding agents) │
│ LLM access, tool execution, code editing, MCP client │
│ copilot --acp, claude --acp, codex --acp, gemini --acp │
│ │
│ Protocols: ACP (to Layer 2/3), MCP (to tools) │
├─────────────────────────────────────────────────────────────────────┤
│ Layer 0: LLM APIs │
│ OpenAI API, Anthropic API, GitHub Copilot API, Google Gemini API │
└─────────────────────────────────────────────────────────────────────┘
Protocol Map¶
Three protocols serve different relationships. The key distinction is where each protocol operates and what transport carries it:
| Protocol | Relationship | Where Used | Transport | Role in Daedalus |
|---|---|---|---|---|
| ACP (Agent Client Protocol) | Client drives Agent | Intra-pod (proxy to agent) | stdio or TCP (JSON-RPC/NDJSON) | Proxy sidecar drives agent CLI |
| A2A (Agent-to-Agent Protocol) | Peer agents collaborate | Inter-agent (orchestrator to workers) | Data model on queue (not HTTP) | Message envelope format on NATS |
| MCP (Model Context Protocol) | Agent calls Tools | Agent to tool servers | stdio or SSE/HTTP | Agent runtime accesses tools |
A2A data model (on NATS queue, not HTTP)
Orchestrator ──────────────────────────────────────────────► Worker Pod
│ │
│ Future: A2A HTTP endpoint ACP (intra-pod)
│ for external callers Proxy ◄──────────► Agent CLI
│ │
▼ MCP (tools)
External Agent ──A2A HTTP──► Orchestrator Agent CLI ──► MCP Servers
(Phase 3, optional) (same envelope,
just HTTP ingress)
ACP is the LSP of agents - it standardizes the client-agent interface so any editor (or proxy, or orchestrator) can drive any agent. Just as LSP lets VS Code talk to any language server, ACP lets our proxy talk to Copilot CLI, Claude Code, Codex, Gemini, or any of 17+ ACP-compatible agents.
A2A provides the data model, not the transport. We use A2A's Task, Message, Part, and Artifact types as the queue message schema. We do not use A2A's HTTP/gRPC transport between orchestrator and workers - the queue handles that. This gives us a standardized, well-maintained schema without the overhead of HTTP hops. See A2A Protocol Decision for the full rationale.
A2A Protocol Decision: Data Model Yes, Transport No¶
This decision was reached after evaluating whether A2A protocol is needed in the internal factory loop, and if so, which parts.
The tension: A2A adds JSON nesting and protocol ceremony (jsonrpc, method, params) to what could be a flat JSON message. Is it worth it?
What A2A's data model costs us: A few extra fields per message. The envelope is ~30% larger than a minimal custom schema. Every developer touching the queue needs to understand A2A's Part, Message, Artifact nesting.
What A2A's data model buys us: - No schema to maintain - A2A community maintains the spec, SDKs validate it - Future interop is free - if we expose the queue as an A2A HTTP endpoint for external agents, the messages are already A2A-shaped. No translation layer, no schema migration. - AgentCards as a discovery format - even without A2A's HTTP discovery, the AgentCard JSON structure is a useful, standardized way to describe agent capabilities - Task lifecycle states - submitted, working, completed, failed, canceled - already defined and agreed upon by the industry
What we skip (A2A HTTP transport):
- No HTTP calls between orchestrator and workers - queue provides decoupling, buffering, scale-to-zero
- No /.well-known/agent-card.json served by workers - they're ephemeral, can't serve HTTP. Registry handles discovery.
- No SSE streaming from workers to orchestrator - status updates flow through queue subjects instead
The architectural payoff - external interop becomes trivial:
``` Today (internal only): Orchestrator --A2A envelope--> NATS --> Proxy --ACP--> Agent
Phase 3 (add external interop): External Agent --A2A HTTP POST--> Orchestrator --same A2A envelope--> NATS --> (same) │ └── Thin HTTP adapter (~50 lines) Accepts A2A message/send Enqueues to NATS Returns A2A Task ID Streams status via SSE from agent.status subject ```
The adapter is trivial because the internal messages are already A2A-shaped. If we'd used a custom schema, this adapter would need a full bidirectional translation layer.
Decision: Use A2A's data model as the queue envelope. Skip A2A's HTTP transport internally. Add A2A HTTP ingress on the orchestrator when external interop is needed (Phase 3). The marginal cost of A2A's JSON structure is low; the optionality it preserves is high.
Layer-by-Layer Analysis¶
Layer 1: Agent CLIs¶
These are the actual AI coding agents. They connect to LLM APIs, execute tools, edit code, and produce artifacts. They all now support ACP:
| Agent CLI | LLM Provider | ACP Support | Notes |
|---|---|---|---|
copilot --acp |
GitHub Copilot | ✅ native | Our primary runtime |
claude --acp |
Anthropic Claude | ✅ via adapter | Via claude-agent-acp |
codex --acp |
OpenAI | ✅ via adapter | Via codex-acp (Zed) |
gemini --acp |
Google Gemini | ✅ native | Native support |
cursor --acp |
Cursor AI | ✅ native | cursor-agent acp |
qwen --acp |
Alibaba Qwen | ✅ native | qwen --acp |
kiro --acp |
AWS Kiro | ✅ native | kiro-cli-chat acp |
opencode --acp |
Various | ✅ via npx | opencode-ai acp |
| + 9 more | Various | ✅ | See acpx agent registry |
Key ACP capabilities every CLI provides:
- initialize - capability negotiation
- session/new - create session with working directory + MCP servers
- session/prompt - send prompts, receive streaming responses
- session/cancel - cooperative cancellation
- session/load - resume previous sessions (if supported)
- session/request_permission - tool use approval flow
Layer 2: Agent Wrappers / Frameworks¶
These add capabilities on top of raw agent CLIs. They're optional - you can skip Layer 2 and go straight from Layer 3 to Layer 1.
copilot-bridge¶
What it adds: Chat platform adapters (Mattermost/Slack), agent personas (.agent.md), hooks (6 types), skills, scheduling (cron), custom tool injection, inter-agent calls (ask_agent), Beads memory integration.
Best for: Interactive chat-based agent workflows where you need personas, hooks, and human-in-the-loop via Mattermost.
Trade-off: Full-featured but heavy (~500MB image, 10-15s startup). Copilot-only (doesn't wrap other CLIs).
GasTown (Steve Yegge)¶
What it adds: Multi-agent orchestration (gt CLI), inter-agent messaging (gt nudge ephemeral, gt mail persistent), Beads/Dolt storage, full OTel observability (VictoriaMetrics + Grafana), Wasteland federation layer.
Best for: Complex multi-agent coordination where agents need to communicate with each other during execution, with full observability.
Trade-off: Go-based, tightly integrated with Beads/Dolt. Not ACP-native (uses its own orchestration protocol).
acpx / OpenClaw¶
What it adds: Headless ACP client, persistent sessions, named parallel sessions (-s api, -s docs), prompt queueing, fire-and-forget (--no-wait), cooperative cancel, crash reconnect with session/load, multi-agent support (17+ agents), TypeScript flow engine for multi-step workflows.
Best for: Headless automation where you want structured ACP communication without PTY scraping. Multi-agent with different CLIs in the same workflow.
Trade-off: Thin wrapper - doesn't add hooks, personas, or inter-agent messaging. But that thinness is a feature for our use case.
Layer 3: Orchestration Platforms¶
Daedalus (this project)¶
What it adds: Queue-based task dispatch (NATS JetStream), elastic scaling (KEDA ScaledJob, scale-to-zero), AgentCard discovery, structured branch naming, fan-out/fan-in pipelines, observability (OTel tracing).
Unique: Runtime-agnostic. The proxy speaks ACP to any agent, A2A externally.
kagent¶
What it adds: K8s CRDs for agents (Agent, ModelConfig, RemoteMCPServer), Go controller with reconciliation, Google ADK engine, A2A inter-agent communication, Web UI, CLI.
Different from Daedalus: Interactive (not batch), long-running pods (not ephemeral), ADK-based (not pluggable runtime).
Where Each Layer 2 Fits in Daedalus¶
``` Worker Pod options:
Option A: Proxy → CLI directly (thinnest, simple tasks) ┌──────────────┐ ┌─────────────────┐ │ Proxy sidecar│ACP │ copilot --acp │ │ (platform) │◄──►│ (or any CLI) │ └──────────────┘ └─────────────────┘
Option B: Proxy → acpx → CLI (thin wrapper, multi-step/parallel sessions) ┌──────────────┐ ┌───────┐ ┌──────────────┐ │ Proxy sidecar│ACP │ acpx │ACP │ codex --acp │ │ (platform) │◄──►│ │◄──►│ (or any CLI) │ └──────────────┘ └───────┘ └──────────────┘
Option C: Proxy → copilot-bridge → CLI (thick, personas/hooks/skills) ┌──────────────┐ ┌──────────────────┐ ┌──────────────┐ │ Proxy sidecar│ACP │ copilot-bridge │SDK │ Copilot CLI │ │ (platform) │◄──►│ (.agent.md,hooks)│◄──►│ │ └──────────────┘ └──────────────────┘ └──────────────┘
Option D: Proxy → GasTown → CLIs (multi-agent coordination) ┌──────────────┐ ┌──────────┐ ┌──────────────┐ │ Proxy sidecar│ACP │ GasTown │ │ Agent 1 CLI │ │ (platform) │◄──►│ (gt) │◄──►│ Agent 2 CLI │ └──────────────┘ └──────────┘ └──────────────┘ ```
Option A is ideal for single-shot tasks: "implement this function", "write these tests". The proxy creates an ACP session, sends a prompt, collects the result.
Option B adds acpx's session management for multi-turn tasks and its flow engine for multi-step workflows. The proxy delegates session lifecycle to acpx.
Option C is for tasks that need agent personas, pre/post tool hooks, or copilot-bridge's skill system. The bridge wraps the CLI and adds its own capabilities. The proxy talks ACP to the bridge (requires bridge ACP server mode - to be validated).
Option D is for complex tasks where multiple agents need to coordinate during execution (not just fan-out from the orchestrator).
Implications for Daedalus Design¶
1. The Proxy Speaks ACP, Not A2A, to the Agent¶
Our original design had the proxy speaking A2A HTTP to the agent. With ACP: - ACP is simpler for the client-agent relationship (session/new, session/prompt, session/cancel) - ACP has native session management (session/load for resume, session IDs) - ACP has permission relay (session/request_permission) - ACP has MCP server config (pass mcpServers[] at session creation) - The Copilot CLI already implements ACP - no custom wrapper needed
A2A remains the right protocol for the orchestrator side (inter-agent discovery and task delegation via queue). The proxy becomes a protocol bridge: A2A on the queue/external side, ACP on the agent side.
2. acpx Overlap with Our Proxy¶
acpx already implements several things our proxy needs:
| Need | Proxy (custom) | acpx |
|---|---|---|
| ACP client | Build | ✅ Built |
| Session lifecycle | Build | ✅ Built |
| Prompt queueing | Build | ✅ Built |
| Fire-and-forget | Build | ✅ Built (--no-wait) |
| Multi-agent support | Build | ✅ Built (17 agents) |
| Crash reconnect | Build | ✅ Built (session/load fallback) |
| Graceful cancel | Build | ✅ Built (session/cancel + SIGTERM) |
| Named sessions | Build | ✅ Built (-s name) |
| Flow engine | Not planned | ✅ Built (multi-step) |
| NATS integration | Build | ❌ Missing |
| A2A translation | Build | ❌ Missing |
| AgentCard serving | Build | ❌ Missing |
| Trace propagation | Build | ❌ Missing |
Options: 1. Use acpx as a library/dependency - import its ACP client and session management, add our NATS/A2A/tracing layer on top 2. Contribute NATS support to acpx - add queue-based prompt submission upstream 3. Build our own, study acpx - reference its session management patterns but build from scratch in Go for our needs
3. Session Resume via ACP¶
ACP's session/load method solves part of our session resurrection problem (R7/R18):
- Agent declares loadSession: true in capabilities
- Client calls session/load with a previous sessionId
- Agent replays conversation history as session/update notifications
- Client can then continue sending prompts
This means the CLI itself handles session persistence - we don't need to archive session-store.db blobs if the CLI supports session/load natively. The proxy just needs to remember the sessionId and pass it to session/load on restart.
What we still need externally: A sessionId registry (which sessions exist, what tasks they belong to, what branch they produced) - but that's metadata, not the full session state.
4. Permission Relay via ACP¶
ACP's session/request_permission addresses R5 (human-in-the-loop for headless workers):
Agent CLI: "I want to run `rm -rf /tmp/build`"
↓ session/request_permission
Proxy sidecar: receives permission request
↓ publish to agent.permissions queue
Orchestrator: dequeues, posts to Mattermost
↓ human approves in chat
Orchestrator: publishes approval to agent.permissions.response
↓ proxy consumes
Proxy sidecar: returns { outcome: "approved" }
↓ back to agent
Agent CLI: executes the command
Or for full autopilot: proxy returns { outcome: "approved" } for everything (equivalent to --approve-all).
Summary: What Changed¶
| Before (original hybrid design) | After (layered ACP + A2A) |
|---|---|
| Proxy speaks A2A HTTP to agent | Proxy speaks ACP to agent; A2A data model on queue (not HTTP) |
| A2A HTTP transport between all components | A2A data model only on queue; HTTP transport deferred to Phase 3 edge |
| copilot-bridge is the agent runtime | copilot-bridge is one option; any ACP agent works (17+) |
| Custom A2A server wrapper needed for bridge | Not needed - Copilot CLI speaks ACP natively |
| Session restoration via SQLite blob archive | ACP session/load may handle it natively |
| Permission relay undesigned (R5) | ACP request_permission provides the mechanism |
| 1 supported agent (Copilot) | 17+ agents via ACP ecosystem |
| Layer 2 always required | Layer 2 is optional - direct CLI for simple tasks |
| External interop requires new protocol work | External interop is trivial - messages already A2A-shaped, add HTTP ingress |