Architecture
A minimal kernel, everything else a plugin, and one direction of dependency the build asserts.
One sentence
A minimal kernel — session actor, ordered event journal, turn state machine, permission gate, plugin host — with everything else as plugin crates behind stable traits, and every surface a client of one submission entry and one subscription.
The layers
bingo (bin) composes Vec<Box<dyn Plugin>>, picks a Surface
↑
plugins / surfaces depend on bingo-sdk only
↑
bingo-core depends on bingo-sdk only
↑
bingo-sdk serde, schemars, thiserror, async-trait, tokio(sync),
futures, ulid, jiff — nothing heavier
Dependency direction is strictly downward. Five edges are forbidden outright
and scripts/check_discipline.sh asserts them over cargo metadata
(ADR-0001):
- a plugin or surface crate depending on
bingo-core; bingo-coredepending on a plugin or surface crate;- anything but
bingo-surface-tuidepending onratatuiorcrossterm; bingo-coreorbingo-sdkresolvingreqwest,rmcp,ratatui,crossterm,imageorsyntectanywhere in their normal dependency tree;- a plugin depending on another plugin. Cross-plugin needs go through a service trait registered via the sdk.
The consequences are the point. The kernel is tested in-process against the sdk’s own fakes, with no subprocess. Touching a tool crate never relinks the terminal surface. The sdk — what an external plugin author downloads — cannot pull in ratatui or reqwest. And the terminal surface cannot reach the engine, because there is no edge for it to reach along.
The crates
Kernel and contract
| crate | what it holds |
|---|---|
bingo-sdk | ids, Message/ContentPart, Frame/Event/Item, SessionState and apply, every trait, HostApi, the service registry, testing fakes |
bingo-core | the session actor, journal and broadcast, the turn state machine, the permission gate, the tool executor, the plugin host, the context ruler, ContextView::fold |
Plugins
| kind | crates |
|---|---|
| providers | bingo-provider-anthropic, bingo-provider-openai (openai and codex), bingo-provider-fake |
| tools | bingo-tool-fs, bingo-tool-bash, bingo-tool-web, bingo-mcp, bingo-agents |
| policy | bingo-permissions, bingo-hooks-shell |
| session | bingo-store-jsonl, bingo-context |
| features | bingo-skills, bingo-rooms, bingo-tasks, bingo-experience, bingo-schedule |
| extension | bingo-plugin-rpc — the cross-process bridge |
| surfaces | bingo-surface-print, bingo-surface-rpc, bingo-surface-tui, bingo-channels |
| demo | bingo-demo-ui — off unless --demo-ui, and the worked example a plugin author reads first |
Libraries
A crate that declares [package.metadata.bingo] tier = "library" registers
nothing, depends on bingo-sdk and external crates only, and any plugin may
depend on it. bingo-auth-oauth is the first and so far only one: PKCE
loopback, device code, auth.json, single-flight refresh (ADR-0012).
The binary
bingo composes the plugin list explicitly — there is no self-registration
crate until plugins must load without the bin naming them — and picks a
surface from what the run is: the terminal surface when a person is at both
ends of the pipe, the print surface otherwise, the RPC surface for serve, the
channels surface for channels and gateway run.
One turn
A client calls SessionHandle::submit(intent, input) — synchronous, returning
nothing. The session actor appends a user Item, mints a seq, and hands the
turn to the state machine. The loop asks contributors for context, streams the
provider, folds the provider’s events into Items, gates each tool call
through hooks and the policy (an Interaction when a person must answer),
executes the tools, absorbs queued steering at the barrier, and closes with
exactly one TurnCompleted. Every frame is journaled first and then broadcast
to each subscriber’s bounded channel. Every client folds those frames with
SessionState::apply.
Sessions are the only conversational noun
A sub-agent is a session with a parent link. A room is a session without a
model. Both render through the same reducer and the same draw code, and the
kernel owns neither noun: room, team, hire, task, experience and
schedule appear nowhere in bingo-sdk or bingo-core. They are plugins’
words, carried on Event::Extension payloads the kernel does not enumerate.
Where to read next
- The event stream — the one vocabulary every surface consumes.
- Plugins in Rust — the
Plugintrait and what it may register. - Cross-process plugins — the same contributions, in any language.
- The ADR index — every boundary decision, one line each.
In the repository itself: ARCHITECTURE.md for the map, docs/adr/ for the
decisions, docs/plans/ for what is being built now, docs/design/ for the
proposals and the research behind the library choices.