Skip to article
Browse chapters

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):

  1. a plugin or surface crate depending on bingo-core;
  2. bingo-core depending on a plugin or surface crate;
  3. anything but bingo-surface-tui depending on ratatui or crossterm;
  4. bingo-core or bingo-sdk resolving reqwest, rmcp, ratatui, crossterm, image or syntect anywhere in their normal dependency tree;
  5. 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

cratewhat it holds
bingo-sdkids, Message/ContentPart, Frame/Event/Item, SessionState and apply, every trait, HostApi, the service registry, testing fakes
bingo-corethe session actor, journal and broadcast, the turn state machine, the permission gate, the tool executor, the plugin host, the context ruler, ContextView::fold

Plugins

kindcrates
providersbingo-provider-anthropic, bingo-provider-openai (openai and codex), bingo-provider-fake
toolsbingo-tool-fs, bingo-tool-bash, bingo-tool-web, bingo-mcp, bingo-agents
policybingo-permissions, bingo-hooks-shell
sessionbingo-store-jsonl, bingo-context
featuresbingo-skills, bingo-rooms, bingo-tasks, bingo-experience, bingo-schedule
extensionbingo-plugin-rpc — the cross-process bridge
surfacesbingo-surface-print, bingo-surface-rpc, bingo-surface-tui, bingo-channels
demobingo-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.

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.