DEV Community

Cover image for OpenHands Agent Canvas: Multi-Backend Orchestration for Coding Agents
mech.app
mech.app

Posted on Originally published at mech.app

OpenHands Agent Canvas: Multi-Backend Orchestration for Coding Agents

OpenHands Agent Canvas is a self-hosted control plane that dispatches coding agents to heterogeneous execution environments. You can route Claude Code, Codex, Gemini, or any ACP-compatible agent to local Docker containers, remote VMs, or company infrastructure from a single interface. The project (85,455 stars, trending #11 in TypeScript) treats agents as persistent infrastructure rather than ephemeral chat sessions.

The interesting plumbing is in the backend abstraction layer and the Agent Communication Protocol (ACP) compatibility shim. Agent Canvas normalizes tool calls, session state, and response formats across different agent types, then routes execution to whatever backend you configure. Prebuilt automations handle recurring tasks like GitHub issue decomposition or Slack report publishing without manual intervention.

Architecture: Control Plane and Backend Routing

Agent Canvas runs as a TypeScript service that maintains a registry of agent backends and a queue of automation jobs. The control plane does not execute agent code itself. It dispatches requests to backends and manages session state in a local SQLite database (or Postgres for production).

Backend types:

  • Local Docker: Default. Spins up containers on the host machine with mounted workspace volumes.
  • Remote VM: Connects over SSH, runs agents in isolated environments with resource limits.
  • Cloud infrastructure: Integrates with Kubernetes or AWS ECS to schedule agent pods on demand.
  • Company infrastructure: Custom backend adapter that calls internal APIs or connects to on-prem clusters.

Each backend exposes a common interface:

interface AgentBackend {
  start(agentType: string, config: AgentConfig): Promise<SessionId>;
  sendMessage(sessionId: SessionId, message: string): Promise<void>;
  getState(sessionId: SessionId): Promise<SessionState>;
  stop(sessionId: SessionId): Promise<void>;
}
Enter fullscreen mode Exit fullscreen mode

The control plane selects a backend based on automation configuration or user preference. If a backend is unavailable, the control plane queues the request and retries with exponential backoff.

ACP Compatibility Layer: Normalizing Tool Calls

Agent Communication Protocol (ACP) is OpenHands' abstraction over different agent APIs. Claude Code uses Anthropic's tool-use format, Codex uses OpenAI function calling, and Gemini uses Google's function declarations. ACP translates these into a unified schema.

ACP message structure:

{
  "role": "assistant",
  "content": "I'll create the file.",
  "tool_calls": [
    {
      "id": "call_abc123",
      "type": "function",
      "function": {
        "name": "write_file",
        "arguments": "{\"path\": \"src/main.ts\", \"content\": \"console.log('hello');\"}"
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

The ACP adapter for each agent type maps inbound tool calls to this format, then translates responses back to the agent's native format. For example, Claude Code expects tool_use blocks with tool_use_id, while Codex expects function_call with name and arguments. The adapter handles this bidirectional translation.

Tool execution flow:

  1. Agent Canvas receives a tool call in ACP format.
  2. The backend executor validates the tool name against a whitelist.
  3. The executor runs the tool in a sandboxed environment (Docker container or VM).
  4. The result is wrapped in ACP format and sent back to the agent.
  5. The agent continues or terminates based on the result.

If a tool call fails, the executor returns an error message in ACP format. The agent can retry, request clarification, or abort the task.

Prebuilt Automations: GitHub Issue Decomposition

Automations are YAML files that define triggers, agent selection, and output destinations. The GitHub issue decomposition automation watches for new issues with a specific label, then dispatches an agent to break the issue into subtasks.

Automation definition:

name: decompose-github-issue
trigger:
  type: webhook
  source: github
  event: issues.labeled
  filter:
    label: "needs-breakdown"
agent:
  type: claude-code
  backend: remote-vm
  config:
    model: claude-3-5-sonnet-20241022
    max_tokens: 4096
steps:
  - action: read-issue
    tool: github_get_issue
  - action: decompose
    prompt: "Break this issue into 3-5 subtasks. Each subtask should be actionable and testable."
  - action: create-subtasks
    tool: github_create_issues
output:
  type: github-comment
  template: "Created subtasks: {{subtask_urls}}"
Enter fullscreen mode Exit fullscreen mode

The control plane polls the GitHub webhook endpoint, matches the event against registered automations, and queues a job. The job executor selects the configured backend (remote-vm in this case), starts a Claude Code session, and runs the steps sequentially. If a step fails, the executor logs the error and optionally retries or sends a notification.

Slack report automation works similarly but uses a cron trigger instead of a webhook. The agent queries a database or API, formats the results, and posts to a Slack channel via the Slack API tool.

Session State Management

Agent Canvas stores session state in a relational schema:

Table Columns Purpose
sessions id, agent_type, backend_id, status, created_at Tracks active and completed sessions
messages id, session_id, role, content, tool_calls, timestamp Stores conversation history
tool_executions id, message_id, tool_name, arguments, result, status Logs tool calls and results
automations id, name, trigger, agent_config, steps, status Defines automation workflows
jobs id, automation_id, session_id, status, error, created_at Tracks automation execution

When a backend crashes or a VM is terminated, the control plane marks the session as failed and retries the job on a different backend if configured. Session history is preserved, so you can inspect what the agent did before the failure.

State recovery: If the control plane itself restarts, it reads the jobs table and resumes any in-progress automations. This requires backends to support session resumption (not all do). Docker backends can resume if the container is still running. Remote VM backends require the agent process to write checkpoints to a shared volume.

Security Boundaries

Agent Canvas enforces three security layers:

  1. Tool whitelisting: Each backend has a list of allowed tools. Agents cannot execute arbitrary commands.
  2. Sandboxing: Docker and VM backends run agents in isolated environments with no network access by default.
  3. Credential isolation: API keys and secrets are injected into the backend environment at runtime, never stored in session state.

Failure mode: If an agent requests a tool that is not whitelisted, the backend returns an error. The agent can request a different tool or abort. If an agent attempts to escape the sandbox (e.g., by writing to /etc or opening a reverse shell), the backend terminates the session and logs the attempt.

Company infrastructure backends require additional configuration to integrate with internal secret managers (HashiCorp Vault, AWS Secrets Manager). The backend adapter fetches credentials at session start and injects them as environment variables.

Observability: Tracing Agent Execution

Agent Canvas emits structured logs and OpenTelemetry traces. Each session gets a trace ID that propagates through tool calls, backend requests, and automation steps.

Key metrics:

  • Session duration (p50, p95, p99)
  • Tool call latency by tool name
  • Backend availability and error rate
  • Automation success rate by automation name

The control plane exposes a /metrics endpoint in Prometheus format. You can scrape this with Prometheus and visualize in Grafana.

Trace example:

span: session.start (session_id=abc123, agent_type=claude-code)
  span: backend.select (backend_type=remote-vm)
  span: backend.start (vm_id=vm-456)
  span: agent.send_message (message="Decompose this issue")
    span: tool.execute (tool_name=github_get_issue)
    span: tool.execute (tool_name=github_create_issues)
  span: session.stop
Enter fullscreen mode Exit fullscreen mode

If a session hangs, you can inspect the trace to see which tool call is blocking.

Deployment Shape

Local development: Run npx @openhands/agent-canvas to start the control plane on localhost:3000. The Docker backend is enabled by default. No external dependencies.

Production: Deploy the control plane as a container or systemd service. Use Postgres instead of SQLite. Configure remote VM or cloud backends in config.yaml. Set up a reverse proxy (Nginx, Caddy) for HTTPS.

High availability: Run multiple control plane instances behind a load balancer. Use a distributed lock (Redis, etcd) to prevent duplicate job execution. Backends should be stateless or support session resumption.

Failure Modes and Mitigations

Failure Impact Mitigation
Backend unavailable Automation jobs queue up Configure multiple backends, enable retries
Agent timeout Session hangs indefinitely Set max_execution_time in agent config
Tool call error Agent cannot complete task Return structured error, let agent retry or abort
Control plane crash In-progress jobs lost Enable job persistence, resume on restart
ACP translation bug Agent receives malformed response Log raw messages, add schema validation

Observability gap: If a backend crashes without reporting status, the control plane does not know the session failed until the health check times out (default 30 seconds). You can reduce this by lowering the health check interval, but this increases network overhead.

Technical Verdict

Use Agent Canvas when:

  • You need to run multiple agent types (Claude Code, Codex, Gemini) from one interface.
  • You want to automate recurring tasks like issue triage or report generation.
  • You need to route agents to different execution environments (local, remote, cloud).
  • You want to self-host and control where agent code runs.

Avoid Agent Canvas when:

  • You only use one agent type and do not need backend abstraction.
  • You need real-time collaboration (Agent Canvas is optimized for batch automation).
  • You require sub-second latency (backend routing adds 100-500ms overhead).
  • You need built-in human-in-the-loop approval (you will need to build this yourself).

The ACP compatibility layer is the most fragile part. If an agent API changes (e.g., Anthropic adds a new tool format), the adapter breaks until OpenHands updates it. Monitor the OpenHands release notes and test automations after upgrading.

Source Links

Top comments (0)