Runtimes

Five adapters, one line of config, no coupling.

A runtime is what actually executes a turn. Five ship. An app picks one with a single line, and nothing else in the workspace changes.

apps:
  reviewer:
    name: Rev
    type: claude_code      # ← the only line that decides who answers
    soul: agents/reviewer/SOUL.md

Why you would care

Most agent frameworks are a commitment. You adopt one, you write your tools against its decorators, its schema, its session object — and moving costs a rewrite. That is a bet on a field that is changing every few months.

Runspace does not ask for that bet. The workspace, the channels, the message history, the scheduler, the widget rendering and the UI are all the same whichever runtime answers. Swapping is a one-line config change, not a migration, so you can:

  • Keep the subscription you already pay for. If you have a Claude or Codex CLI seat, the claude_code and codex adapters use it. No second API bill to run a workspace.
  • Use a different runtime per agent. One workspace can run a Codex-backed reviewer beside an in-process Agentino analyst. They share the channel and neither knows about the other.
  • Change your mind later. When something better ships, it is a new file and a config line, not a rewrite of your tools.

The five

type:ExecutesNeedsBinary override
agentinoin-process, no subprocesspip install agentino-framework
codexcodex exec --jsonthe Codex CLI on PATHCODEX_BIN
claude_codeclaude -p --output-format stream-jsonthe Claude Code CLICLAUDE_BIN
pipi --printthe Pi CLIPI_BIN
openclawopenclaw agent --local --jsonthe OpenClaw CLIOPENCLAW_BIN

The four CLI adapters shell out to a binary you install, so Runspace itself depends on none of them — they are not in install_requires, and a workspace that uses none of them installs none of them. agentino is an optional extra for the same reason.

model: on the app overrides the runtime's default, so the same adapter can run different models per agent. pi and openclaw accept a provider/model form.

Choosing

  • agentino — you want tools as decorated Python functions in the same process, with no binary to install and no subprocess per turn. Start here.
  • codex / claude_code — you already have the seat, and you want that model's coding behaviour without wiring an API key.
  • pi / openclaw — you are already using them, or you want a runtime whose sandbox and tool policy are managed outside your process.

The boundary is enforced, not just intended

app_registry.py dispatches on app.type and imports no runtime — it imports the adapter module, which is a different thing. Every module that does touch a runtime imports it lazily, inside the function that needs it.

The result is checkable rather than asserted: install without the [agentino] extra, import the gateway and the registry, and agentino is absent from sys.modules. A test also reads the registry's source and fails if a runtime import appears there, because "we intend to stay decoupled" degrades quietly and a failing test does not.

That is what makes a sixth adapter a new file rather than a refactor — and it is why the promise above is a property of the code rather than a claim in a README.

Writing a sixth

An adapter needs one async function:

async def chat(registry, app, message: str, session_id: str) -> dict:
    return {"text": ..., "tools_used": [...]}

For a CLI adapter most of the work is parsing the other tool's output — and most of the maintenance is surviving the day that output changes shape. Degrade rather than raise: an unparseable response should return empty text, not take the workspace down on someone else's point release.