Skip to content
Alternatives considered

Alternatives considered

Work in progress. The agent ecosystem moves fast. Each detailed evaluation states when it was checked and what would reopen it, and the page grows as new candidates appear.

What alternatives did we consider for each layer of the Agent Factory, and why did the current choices win? A new candidate gets a row in the table. If it could replace a whole layer, it also gets a section under projects evaluated in detail.

In short:

  • No candidate evaluated so far covers more than one or two layers. The factory, the rooms and the controls around them would stay ours whatever we adopted.
  • A choice can be deferred rather than refused. Each detailed evaluation lists the concrete triggers that would make us reconsider it.

How candidates are evaluated

  • Against code, not announcements. Claims are checked in the project’s repository at a pinned commit, and linked.
  • On the work still ahead. What is already built does not count in its favour; what adopting a candidate would still leave us to build does.
  • Against the platform’s requirements:
    • it runs on both EKS and GKE, on spot nodes;
    • every workload gets its own identity and network policy;
    • no long-lived credential reaches a sandbox;
    • open source first.

Where each tool fits

An agent platform has several layers. Most “agent platforms” cover one or two of them; ours needs all five.

    %%{init: {"flowchart": {"wrappingWidth": 400}}}%%
flowchart TB
  subgraph L5["Factory: issue → runs → PR, budgets, merge gate, stop switch"]
    direction LR
    F5["Ours: maku-factory controller<br/>+ Kueue + policy-bot"] ~~~ T5["Temporal: durable<br/>workflows (watched)"]
  end
  subgraph L4["Collaboration: shared sessions with humans and agents"]
    direction LR
    F4["Ours: rooms<br/>(maku-broker + Postgres log)"] ~~~ K4["kagent sessions:<br/>single owner"]
  end
  subgraph L3["Gateway: models, tools, per-run identity and budgets"]
    direction LR
    F3["agentgateway<br/>(chosen)"] ~~~ O3["otari: LLM gateway<br/>(watched)"]
  end
  subgraph L2["Harness: the agent loop"]
    direction LR
    F2["OpenHands<br/>(chosen)"] ~~~ A2["ax: Antigravity,<br/>Gemini only"] ~~~ K2["kagent: Claude Code,<br/>Codex, own ADKs"]
  end
  subgraph L1["Runtime: isolation, lifecycle, identity"]
    direction LR
    F1["agent-sandbox + gVisor<br/>(chosen)"] ~~~ S1["Agent Substrate (google/ax<br/>and kagent v1 run on it)"]
  end
  L5 --> L4 --> L3 --> L2 --> L1
  

Decisions at a glance

LayerChosenAlternatives consideredWhy
Runtimeagent-sandbox + gVisor, one pod per runAgent Substrate (with google/ax or kagent), Kata/Firecracker, OpenHands Enterprise, Coder, E2B/DaytonaOpen source; runs on EKS and GKE, on spot nodes; each run gets its own ServiceAccount and Cilium policy, so the constitution’s rules apply per run. Kata/Firecracker needs bare-metal hosts or nested virtualisation: the smallest AWS metal host has 128 vCPU, about $1.5/h spot or $5.4/h on demand (Vantage), against a few cents an hour for a run’s spot node. An agent step mostly waits on the model (about 11 s a reply), so gVisor’s system-call overhead does not show
HarnessOpenHands agent-serverHeadless Claude Code, kagent’s harnesses, ax’s Antigravity agentOpen source, headless (an HTTP API), works with any OpenAI-compatible model, supports MCP. Claude Code is proprietary; Antigravity is Gemini-only
GatewayagentgatewayAgent Router 1.1 on Envoy Gateway, otariSee gateways. otari has no MCP federation, token exchange, listener per data class or OpenTelemetry export, and no Kubernetes packaging: watched
RoomsA log we own: maku-broker on PostgreSQLOpenHands shared conversations, ACP, A2A through a gateway, Valkey Streams, NATS JetStream, ax or Substrate as the session layer, kagent sessionsTwo different questions. Protocols (OpenHands shared conversations, ACP, A2A, ax or Substrate, kagent sessions): none records who said what among several people and agents, or who may approve. Brokers (Valkey Streams, NATS JetStream): an event is written to Postgres first, which fixes its place in the log, then announced with LISTEN/NOTIFY. At a few events per second a second stateful system adds nothing, and delivering live before the write would let watchers see events the audit log never records
FactoryA small custom controller + KueueArgo Workflows, Tekton, Temporal, a Crossplane Task composition, gh-aw, OpenAI’s Symphony specification (below)The factory’s state lives in Kubernetes objects and GitHub. Waiting hours on CI or a review is a periodic re-check of that state, so a restart loses nothing. Budgets and the kill switch are domain logic any engine would still need. Argo has no budget concept and a Crossplane composition has no timers. Temporal suits workflows whose state lives in code, and is the one to watch (below)
Merge gatepolicy-bot behind a repository rulesetRequired reviews, rulesets alone, a custom check, Prow/tide, Mergify, KodiakThe policy lives in the repository and is reviewable, and the right to merge sits with one dedicated App

The full records are in the programme’s design documents, listed under Sources.

What would make us reconsider

Current choiceAlternative that could winReconsider when
Custom factory controllerTemporalThe task lifecycle needs multi-step compensation, timed human waits multiply, retry and resume rules grow, or the factory’s bugs cluster around timers and retries
Postgres LISTEN/NOTIFY for room fan-outNATS JetStream, for fan-out only: the log stays in Postgres and is written firstNotification latency or database load shows on the room dashboards, or rooms need many broker replicas
gVisorKata or Firecracker microVMsConcurrency reaches about 20 sandboxes, enough to fill a metal host; a gVisor incompatibility blocks a class of task; or nested virtualisation becomes available on our node types
agentgateway (agents), Envoy AI Gateway (people)otariIt ships declarative Kubernetes packaging, OpenTelemetry gen_ai export, per-run identity without a key per run and MCP federation (agents); or generic OIDC sign-in and prompt-based model choice (people)
agent-sandbox, one pod per runAgent SubstrateSee Reconsidering Substrate
One cold pod per run, held or ended while it waitsagent-sandbox’s own SandboxWarmPool and operatingMode: Suspended, in the pinned v1.0.5; the composition suspends only finished runsNow: a run starts in about 90 s on a fresh node (v1 review). A claim that sets env or volume templates bypasses the warm pool

Projects evaluated in detail

Candidates that could replace a whole layer. Each section states when it was checked.

google/ax

Checked in October 2026 at ac23328.

What it is. Google’s agent orchestrator: it runs each task as an agent on Agent Substrate and bootstraps Google’s Antigravity agent inside it.

Why not.

  • Its control plane has no authentication or authorization. The issue is open; its reporter says Google’s security programme rated it critical (ax#376).
  • An unvalidated branch name reaches git fetch, a code-execution bug (ax#363).
  • Gemini only, with the API key copied into every task (client.go, reconciler.go).
  • Still being rewritten: v0.3.0 dropped its event log and replaced its harness, and the queue and controller were replaced a week later (dc4f36c, ac23328).

Adopting ax would replace the harness as well as the runtime, and lock the platform to one model provider.

Reconsider if ax closes #376 and #363 and supports other model providers without putting keys in the sandbox, on top of the Substrate triggers.

Agent Substrate

Checked in October 2026 at 16b863a.

What it is. A runtime that packs many gVisor-isolated agents into shared worker pods and can suspend and resume an agent, memory included, from snapshots.

What it would bring. Fork from a running snapshot, and memory snapshots on any cluster (see below). agent-sandbox has disk-backed suspend since v0.5.0 (operatingMode: Suspended, which deletes the pod and keeps its volumes) and GKE-only memory snapshots driven from its Python SDK; the composition suspends only finished runs.

Why not yet.

ConstraintEvidence
Several agents share one pod. Each gets its own egress policy, but Kubernetes identity and Cilium policy apply to the shared podglossary, EgressPolicy
Authorization is experimental and off by default--experimental-enable-authz
A reclaimed worker leaves its awake agents permanently crashed; the docs warn against spot nodessetup guide
Documented on kind and GKE only; needs Kubernetes certificate APIs that are stable only from 1.37README, Kubernetes 1.37
Pre-1.0; EKS-related fixes landed on main in October, unreleasedv0.3.0, #1898, #432

Reconsider: see the triggers.

kagent

Checked in October 2026 at bf8afa56 (v1) and v0.10.3.

What it is. A CNCF Sandbox project, mostly maintained by Solo.io, and really two products:

  • v1 (alpha, where the project invests) runs only on its own fork of Agent Substrate, with pluggable harnesses (Claude Code, Codex, its own, or yours) and sessions.
  • v0.10 (stable, called “legacy” by the project) runs one long-running Deployment per agent.

Why not.

GapEvidence
A session has one owner and one agent. Anyone joining through a share link is recorded as the owner, and no message records its authorshare.go, schema
The open-source controller authorizes every action and takes the user’s identity from a header, so anything reaching it directly can act as anyoneauth.go
v1 cannot run without Substrate, and gives an agent no ServiceAccount or pod spec of its ownapp.go, AGENTS.md
An agent identifies itself to the controller with an unsigned header until Substrate ships per-agent tokenssubstrate#1660
No GitHub intake, PR handling, merge gate, budgets, daily cap or stop switch, in either editionResearch
It dropped kubernetes-sigs/agent-sandbox in June: “substrate is the thing we’ll go with”#2049

Worth learning from: forking a session together with a snapshot of the running agent, which beats our fork. It depends on Substrate.

Reconsider: see the triggers.

Temporal

Assessed in October 2026 against the factory’s needs; no proof of concept.

What it is. A durable-execution engine (temporalio/temporal, MIT). Workflows are written as code, and the engine keeps their state through crashes, with durable timers, signals, retry policies and a full history of every execution.

What it would bring.

  • Timed human waits, such as remind after a day, escalate after two, close after a week, without re-check logic.
  • Retry and resume policies declared per step.
  • An execution history and UI per task.

It can keep its state in PostgreSQL, so on this platform the database would be a SQLInstance claim, not new infrastructure.

Why not now. The factory’s waits are re-checks of state that already lives in Kubernetes objects and GitHub, and a controller restart loses nothing. Temporal would add its server services and workflows that are versioned as code, which is heavy at today’s 20 tasks a day.

Reconsider under the conditions in the table above. The first candidate is the automatic resume of runs lost to spot reclamation: if resume rules multiply, a workflow engine becomes worth its weight.

OpenAI Symphony

Checked in October 2026 at the repository’s SPEC.md; last push in September 2026.

What it is. An Apache-2.0 specification for an orchestrator that polls an issue tracker, claims ready issues, gives each one an isolated workspace and runs a coding agent in it, with bounded concurrency, stall and turn-silence timeouts, exponential backoff and restart reconciliation without a database. An Elixir reference implementation exists; OpenAI calls it a “low-key engineering preview”. The agent is Codex only (its app-server protocol); the tracker is pluggable, with Linear, GitHub Issues, Jira Cloud, Asana and GitLab adapters in the reference.

Why not. It leaves our layers to each implementation: its non-goals include “Mandating strong sandbox controls beyond what the coding agent and host OS provide” and “Mandating a single default approval, sandbox, or operator-confirmation posture for all implementations” (SPEC §2.2). Workspaces are directories, locally or on SSH workers; tokens are tracked but not enforced; the merge is whatever the agent does from a hook.

Worth learning from: its continuation rule (SPEC §7.1): after each turn the worker re-checks the issue’s state and starts another turn while it stays active, up to max_turns per worker session, rather than spending fixed counters per trigger. It has no total per-issue budget. Its defaults are a baseline: stall 300 s, max_turns 20, backoff capped at 5 min, and a 1 h limit on a turn’s silence (not its length).

Reconsider if the spec gains a non-Codex agent adapter and a sandbox hook; even then it would replace polling and slot logic only.

otari

Checked in October 2026 in the code at e6226a5 (just after v0.30.1) and any-llm d6b76fd. Full evaluation, with a code link per claim: research.

What it is. Mozilla.ai’s open-source (Apache-2.0) LLM gateway: one Python process serving an OpenAI-compatible API and a dashboard, built on its any-llm SDK, over PostgreSQL. It runs alone, or as a data plane reporting to the hosted control plane otari.ai. First released in June 2026, it reached v0.30 four months later with 27 breaking changes.

What it does well.

  • Budgets reserved before the call: the worst case (prompt estimate plus max_tokens) is held, then settled. Ours charge after the call, so the call that crosses a budget completes.
  • Unpriced models refused by default (402), fallback models included; prices from your own table.
  • Z.ai, Anthropic with prompt caching, vLLM and OpenAI-compatible backends; aliases, failover; budgets in USD, tokens or requests per key, user, workspace or provider.

Why not.

Our needotari
A per-run identity checked on every callA JWT is accepted only once its sub is bound to a key by a rule: one key and one rule per run
MCP federation with per-role tool allow-listsMCP client only: no server agents can connect to
The token exchange with octo-stsNone
A listener per data classOne port; keys and workspaces only approximate it
OpenTelemetry gen_ai metrics and traces, JSON logsPrometheus gateway_* metrics and traces in its own database; no export
Declarative Kubernetes configurationNo Helm chart, operator or CRDs; allow-lists and identity rules are API-only, and the deprecated providers: block is copied to the database once, then ignored
No SaaS in the data pathHolds in standalone mode. In hybrid mode otari.ai sees every request’s metadata and hands back the provider keys
People’s gateway: model picked from the prompt, ZITADEL sign-inIts kNN router needs labelled examples; sign-in is password, passkeys, Google or GitHub only

Worth borrowing: the pre-call reservation, for our gateway budgets, and its re-check of pricing on every fallback model.

Reconsider under the conditions in the table above, or once it reaches 1.0 with a deprecation policy.

Would rebuilding on kagent + Substrate be simpler?

It is the strongest challenge to this design, so it is judged on the work still ahead, not on what is already built.

What it would simplify:

TodayOn Substrate
One pod per run with a bridge sidecar causes a class of failures: a lost pod restarting the conversation, transcripts lost at exitDurable storage, suspend and resume, and a durable task log remove those classes of bug
A run waiting on a human approval holds a pod or endsA parked agent costs nothing and resumes with its memory
A fork copies the log and starts a fresh runA fork carries a snapshot of the running agent
We maintain an identity sidecar to keep provider keys out of the sandboxSubstrate’s egress gateway injects them (static keys only)
Every run starts cold and pulls its imageAgents start from a pre-warmed snapshot

Warm pools, disk-backed suspend and GKE memory snapshots are already in agent-sandbox; a spike compared Substrate against them (spike).

What we would still build:

  • The factory, whole.
  • The core of rooms: several participants, roles, handoff and who may approve.
  • Authorization for kagent itself, which means compiling our own controller around kagent’s Go library: in effect, maintaining a fork of an alpha.
  • Short-lived GitHub tokens per run and role, through a custom Substrate credential provider.
  • Budgets, at the gateway.

What blocks it today: no EKS until Kubernetes 1.37 lands there, no spot nodes, a possible snapshot-restore failure for a Python harness such as OpenHands (kagent reports one), shared pods that would need an amendment to the constitution, and two alphas changing fast.

Net: today it adds moving parts we do not control rather than removing them. If we move, Substrate as a backend behind AgentRun beats kagent + Substrate: kagent adds little we lack and brings its authorization gap.

Reconsidering Substrate: not now, deliberately open

A spike ran OpenHands as a Substrate agent: it suspended, resumed and restored on another node with its conversation intact, and warm-started in seconds. Substrate is not adopted now: it needs beta kubelet gates, a second control plane, and it has no published images. A run on a reclaimed spot node is resumed automatically from its branch. Its state is on the status page.

The design keeps the door open:

  • AgentRun is the abstraction, so a Substrate backend would be a new composition behind it, not a rewrite.
  • Run identity accepts tokens from any issuer.
  • The room bridge avoids WebSocket, which Substrate’s egress blocks.

We reconsider as soon as any of these holds:

  • Substrate ships per-agent tokens (#1660) and enforces authorization by default;
  • EKS serves the Kubernetes certificate APIs as stable (EKS 1.37);
  • Substrate gains a story for spot nodes, such as suspending agents on preemption;
  • its costs above shrink upstream (stable kubelet gates, published images);
  • for kagent: open-source authorization, sessions with several attributed participants, per-agent egress (#3019), and a v1 release with a migration path.

Sources