03 · Backends

The office doesn't care which brain the boss has.

Three transports — OpenCode over a live server, Claude Code on a persistent CLI stream, and Codex through JSONL turns. Same floor, same board, same queue; only the wiring under the chat changes.

Choose your conversation backend

opencode (default)

The office spawns — or attaches to — an opencode serve process and streams its events over SSE. One shared server fronts every session for the working directory; this is what pre-existing brain.json files silently mean.

backend.name: "opencode"

claudecode

The office runs the claude CLI in headless stream-json mode as a persistent child process, streamed line by line into the same chat surface. Needs the claude CLI on PATH; absent, the office warns instead of failing.

backend.name: "claudecode"

Codex

Uses your installed Codex CLI, saved login, and model defaults. JSONL turns stream messages, tool activity, file changes, and usage. Later turns resume the exact saved thread.

theboringfloor --backend codex

Ctrl+N → title, team, backend. Saved conversations retain their original backend.

theboringfloor on the opencode backend: floor, chat thread, and panels
opencode (default)
Current cockpit with a Claude Code conversation and a project file explorer plan
claudecode — illustrative project and plan

Same office either way — the topbar badge tells you which brain is on duty.

Picking it, three ways

Flag beats env, env beats brain, brain beats default.

The precedence chain is short and total: --backend flag on the binary overrides THEFLOOR_BACKEND, which overrides brain.json's backend.name — which install.sh --backend seeds for you. Nothing set anywhere means opencode. An invalid name is rejected with a stderr warning and the office falls back to opencode rather than refusing to boot.

# 1 · seed it at install time

... | sh -s -- --backend claudecode

# 2 · pin it in ~/.theboringfloor/configs/brain.json

"backend": { "name": "claudecode" }

# 3 · pin one boot from the shell

THEFLOOR_BACKEND=claudecode theboringfloor

How the office primes your agent

One charter, both brains — primed before the first turn.

On boot the office writes the bundled oikonomos manager protocol to .opencode/oikonomos.md in your working directory and hands it to whichever transport is on duty. That one file is what turns a raw model into the boss: decomposition discipline (wide parallel dispatches, one owner per file), permission etiquette (asks are for commands, questions are for product forks), and the proof-of-work return contract every developer answers with — DONE, FILES, VERIFY, PROOF, ISSUES.

The delivery depends on the brain. On opencode the office merges ./.opencode/oikonomos.md into the instructions array of .opencode/opencode.json — a surgical, field-preserving merge; every other key survives verbatim. On claudecode it writes CLAUDE.md instead — created with @.opencode/oikonomos.md when absent, and when you already keep one, an idempotent marked block appended below your content. Either way the same system prompt reaches the boss.

# your CLAUDE.md, after one office boot

...everything you wrote, untouched...

<!-- theboringfloor charter -->

@.opencode/oikonomos.md

<!-- /theboringfloor charter -->

Your own files take precedence — the office appends, never overwrites: an existing CLAUDE.md keeps every byte above the block, and every other opencode.json field rides through untouched.

Swapping mid-flight

/backend swaps live — but only from an idle office.

Bare /backend prints the active transport. With a name — /backend claudecode — it swaps mid-flight, archives the current turn, persists the name to brain.json, and lands one status line: [theboringfloor] backend: opencode → claudecode. The topbar shows the active name between mode and agents at all times.

The gate is strict by design: a boss turn in flight, a queued backlog, live workers, or an unanswered question or permission each get a refusal that names the blockers — the swap never preempts work. Settle the floor first (/stop helps), then swap.

Sessions don't cross the aisle: session.json pins session IDs per transport (primaryIDs), so swapping back later resumes that transport's own session instead of cross-pinning IDs between backends. Resuming a specific session by ID is covered in getting started, and the key chip for every other slash command lives in keys & slash commands.

Codex: surviving an interrupted turn

Nothing resumes. What already finished comes back, clearly marked.

A codex turn cannot outlive the office that started it. The codex CLI runs one turn per codex exec process and the office reads its stdout synchronously — kill the office (a floor switch, a crash) and the turn dies with it. That ceiling belongs to the Codex CLI's own event model, not to a shortcoming in this office's design.

It gets worse before it gets better: Codex only ever emits or records completed items. There is no partial-item shape anywhere on the wire or on disk. Whatever it was mid-way through generating the instant the office died was never written down by anyone — gone, by design, and no mechanism recovers it.

What the CLI does keep is a durable, incremental record of everything that finished before that — one entry per completed item, appended as it lands, keyed by the thread id. The office now persists that thread id the moment Codex names it, not only at clean shutdown, so a hard-killed office can still find its own thread on the next boot.

So on startup, an office on the codex backend whose last turn never reached completion replays every completed item it can find back into the transcript — messages, reasoning, tool calls — each one visibly marked as recovered so it is never mistaken for a live reply. A leading notice states the count and says plainly that whatever codex was still generating at the moment of death was never saved and isn't part of what follows. Recovery restores completed history; it does not resume the interrupted turn.

A turn that finished cleanly recovers nothing — it is already in the transcript. A turn with nothing to recover — no persisted thread, no matching store, an unreadable file — recovers nothing either, and stays completely silent about it: no notice, no row. A member who never lost a turn never learns this feature exists.

What this doesn't do yet

Role models are best-effort — sub-agent model dispatch is opencode's call, not the office's. Codex uses its CLI model defaults, workspace sandbox for build, and read-only sandbox for plan turns. Run codex login first. Worker discovery, model selection, and remote-history paging vary by backend. Cursor and Pi are not supported transports.