Bernstein's README has a comparison table where it sits next to a few other CLI orchestrators - Composio's @aoagents/ao, umputun/ralphex, emdash. We just shipped adapters for the first two so Bernstein can drive each as a single agent inside a larger plan. As on-the-nose as it sounds.
the shape
Bernstein's adapter contract is small: spawn(prompt, …) -> SpawnResult and name() -> str. The base class handles process tracking, env isolation, timeouts, the worker-PID dance. Most of the 31 cooperating-agent adapters are 80-150 lines of glue between Bernstein's prompt format and a single CLI invocation.
Two new ones land in the same shape, except the wrapped thing is itself an orchestrator:
# src/bernstein/adapters/composio.py
class ComposioAdapter(CLIAdapter):
"""Spawn and monitor Composio Agent Orchestrator (`ao`) sessions.
This adapter wraps Composio's `@aoagents/ao` as a single Bernstein
agent. The wrapped orchestrator runs its own internal multi-agent
workflow inside a tmux session - Bernstein only observes the final
exit code and the captured log output.
"""composio runs ao spawn --prompt "<text>" (Composio's documented non-interactive entry point). ralphex is weirder: its CLI takes a markdown plan file rather than a prompt, so the adapter materialises the Bernstein prompt into .sdd/runtime/<session>-plan.md with the minimal ### Task 1 checkbox block ralphex requires, then runs ralphex --no-color <plan-file>. Both end the way Bernstein expects: a process exits, a log file, an exit code.
what we don't pretend to do
The framing matters more than the code. This is leaf-node delegation, not deep meta-orchestration.
Bernstein hands the wrapped tool a prompt and only sees one return value: did it exit zero. Sub-agent costs accumulated inside Composio. Per-step gates ralphex applied during its plan walk. Model routing the wrapped orchestrator did for its own internal stages. None of that crosses the boundary into Bernstein's accounting.
We could have papered over it. We didn't. The adapter docstrings, the new "Orchestrator delegation (leaf-node)" subsection in the README, the dedicated section in the adapter guide - all three say the same thing: cost transparency stops at the wrapper. The headline "31 adapters" claim stays where it was. The two new ones live in their own subsection because lumping them in with the cooperating-CLI count is the kind of drift that ages a blog post badly.
when it's actually useful
The marketing-meme reading is real but obvious. The real use case is migration.
You have a workflow built on ralphex. You like ralphex's plan walker. You don't want to rewrite it as a Bernstein plan, but you do want a larger flow around it - pre-stage that pulls issues from Linear, post-stage that runs Bernstein's gates. Or: your team already runs @aoagents/ao for one stage of your pipeline, and you want to drop a Bernstein code review after it without retiring the existing tool.
Adapter pattern is the right primitive. Each wrapped orchestrator becomes a single step in a Bernstein plan:
stages:
- name: implement
steps:
- { title: "Run the existing ralphex flow", role: backend, cli: ralphex }
- name: review
steps:
- { title: "Independent code review", role: reviewer, cli: claude }First step's CLI is ralphex; second is Claude Code. Bernstein switches adapters between steps the same way it switches between Codex and Gemini. (Per-step cli: lands in #964; the shape was already there.)
what's not in this release
- emdash. Electron app, no CLI. We can't wrap a window manager. Stays a competitor in the comparison table.
- A dozen more orchestrator adapters. Not an arms race. We wrapped the two we actually compare against. If a third shows up in the table, we'll think about it.
- Sub-agent visibility. Wrapped orchestrators run inside their own black box. We're not adding instrumentation to read their internal state, because that maintenance cost outpaces the marketing payoff in roughly one release cycle.
how we shipped it
Two parallel agents, same template (copilot.py + test_adapter_copilot.py), each told to verify the actual CLI surface from the upstream README before writing the spawn command. Came back with adapters in ~120 LOC apiece, three tests each, lint-clean. Integration work - registry, README, ADAPTER_GUIDE - landed serially after both adapters returned, so no merge fights on shared files.
About the time it took to draft the README copy. Adapter pattern earned its keep.
pipx install 'bernstein>=1.10.0'
npm install -g @aoagents/ao
go install github.com/umputun/ralphex/cmd/ralphex@latestbernstein run plans/migrate-via-ralphex.yaml --cli ralphexOr the new per-step cli: field if your plan mixes orchestrators. The autofix daemon post covers the other piece of the same release; this one is the meme-flavoured one. The v1.9 release notes collect both pieces in their broader context.