Jam architecture main at 59f67f5d0 · 2026-09-29
View source

#Architecture

On this pageBird's-eye viewAt a glanceCodemapKey objectsInvariantsCross-cutting concernsReading paths

Jam connects coding agents on one machine to Band, the hosted service that gives people and agents identities, rooms, and conversation. A long-lived local daemon, jamd, owns every Band connection, every agent process it runs, the encrypted local store, and all message routing. The desktop app and the band/jam CLI are thin clients that send requests to jamd over a private local socket.

Detailed chapters live in docs/architecture/.

#Bird's-eye view

The major pieces, with arrows pointing from caller to callee:

A message moves through Jam like this. A person or agent mentions an agent in a Band room. Band pushes the message over the agent's WebSocket to jamd. jamd writes it to a durable queue, tells Band it is processing, and delivers it to the agent's runtime. The agent does the work and replies, and jamd posts the reply to the room and marks the message processed. The desktop watches all of this through an event stream from jamd.

Agents are peers. No agent orchestrates another. Band mentions are how room-message work reaches an agent. Jam-local notices, such as a task assigned on a room board, can also deliver work and wake a dormant owned runtime. Key flows draws this and five other end-to-end paths.

#At a glance

Figures for commit 59f67f5d0 on main. Codebase metrics regenerates them from the tree.

Rust, production / test lines 300,858 / 276,482
TypeScript, production / test lines 131,887 / 248,316
Workspace crates 30
Control methods / daemon routes / desktop commands 255 / 253 / 250
SQLite tables / migrations 76 / 88
Daemon event kinds 59
Largest production file crates/jam-manager/src/manager.rs, 51,465 production lines

Lines of code by crate or app

Commits per week over the repository's history. Weekly volume has climbed through that history and has not tapered off, so the refactor lands on a tree that is still changing fast.

Commits per week

#Codemap

The list groups crates by responsibility. It is not a dependency order: for example, jam-manager depends on jam-core, jam-host, and jam-store, which appear after it. The desktop never links the daemon crates, and just no-daemon enforces that.

Shared foundation

  • jam-domain: every shared domain type. Identities (IdentityId, PeerKey, ChatId, RuntimeSessionId, RuntimeHostId), records (Peer, HostSession, HostRuntimeConfig), events (EventKind), and the transport catalog (HostTransport::catalog).
  • jam-contract: the Control trait, the one interface every client uses to reach the daemon, plus its DTOs and ControlError.
  • jam-wire: route constants, request and response bodies, and WIRE_VERSION.
  • Leaf utilities that the desktop may also use: jam-version, jam-service (login service and daemon lifetime lock), jam-setup (executable discovery), jam-tls, jam-secure-store, jam-managed-config, jam-analytics.

Clients

  • jam-client: implements Control by sending HTTP over the local socket, with bounded admission and per-route timeouts.
  • jam-ipc: the desktop's typed wrapper over jam-client and the reconnecting event relay.
  • apps/desktop/src-tauri: the Tauri core. Commands are thin forwards through jam-ipc.
  • apps/desktop/src: the React app. lib/ipc.ts is the only data import; stores/daemon.ts is the only writer of server-derived state.
  • bins/jam: the band and jam CLI binaries. The same package builds jamd with the daemon feature.

Daemon stack

  • jam-daemon: the axum router that exposes any Arc<dyn Control> over HTTP.
  • jam-manager: the real Control implementation. Accounts, peers, workers, runtime hosts, workspaces, tasks, questions, permissions, usage, and recovery all live here. Manager in manager.rs is the center.
  • jam-core: the provider-agnostic engine for one agent. Inbound pipeline, durable queue, mention resolution, outbound send, reply, and ack.
  • jam-transport and jam-transport-band: Band behind small role traits (UserApi, AgentApi, Subscriber, HumanSubscriber), and the one implementation.
  • jam-host: the provider seam. One adapter per coding agent behind the Host trait, the Docker Sandbox lifecycle, and the runtime tools offered to owned agents.
  • jam-store: the Store trait with an encrypted SQLite backend and a file backend.
  • jam-usage, jam-discovery, jam-auth, jam-update: usage and cost from provider logs, Nearby discovery, sign-in, and update checks.

bins/jam/src/jamd.rs is the composition root. It selects the principal injected implementations: the store backend, the Band transport, the host adapter for each session (build_host), the room board backend, and the discovery provider. Manager construction and orchestration still pick some concrete services themselves, such as the sandbox remover and WorkspaceManager.

The generated crate graphs show the exact dependency closure of the desktop, the CLI, and the daemon.

#Key objects

Six objects are easy to confuse. Code that treats two of them as one routes, cleans up, or attributes work to the wrong thing. Domain and authority covers them in full.

  • A Band agent (IdentityId) is the permanent identity on Band.
  • A peer (PeerKey, profile/scope) is Jam's local record for one Band agent under one account.
  • A host session binds a peer to exactly one room. A host session with no room is a runtime template.
  • A runtime session (RuntimeSessionId) is one local runtime context, independent of display names and rooms.
  • A runtime host (RuntimeHostId) is one execution boundary: a provider process and, for Docker, one microVM. A Shared host serves several runtime sessions of one agent.
  • A provider thread is the provider's own conversation ID inside that process.

#Invariants

These rules hold across the codebase. Several are enforced by a type, a test, or a CI check, as noted.

  • The desktop is a client. It configures agents and sends lifecycle requests to jamd. It never starts provider processes and never compiles the daemon crates (just no-daemon).
  • Secrets stay in Rust. Tokens and keys are held by SecretStore, which prefers the OS keychain and falls back to permission-restricted files when no keychain is available. The WebView never receives one.
  • Every desktop command is allow-listed. collect_commands! and both isolation allowlists must list the same commands, or the isolation layer rejects the call before Rust sees it.
  • Events are hints, queries are truth. The event stream is best-effort. Clients re-hydrate after any gap.
  • Delivery is at-least-once. An inbound message is durable before Band is told it is processing, and it is removed only on an explicit ack or reply.
  • Unrouted work waits. A message for a room that no session owns is queued, never sent to a guessed runtime.
  • Destructive operations are journaled. Workspace operations, whole-host cleanup, checkpoint payload deletion, and continuity resets write durable intent before their side effects, and startup replays them before workers start. Other multi-step flows, such as assigning work, can leave partial results on Band with no local replay; they report what succeeded instead.
  • Destructive actions carry their own authority. Archive, force discard, export deletion, continuity reset, and whole-host cleanup each re-inspect at the moment of mutation and require their own confirmation. An inspection never grants reusable permission.
  • Unrecoverable provider state fails closed. A bound provider identity with missing or corrupt state does not silently start a fresh thread.
  • The manager lock is never held across .await.
  • Stock provider executables only. Jam runs the user's installed codex, copilot, or claude, or Docker's stock agent image. It never bundles a custom provider binary.
  • Band owns social state. Rooms, membership, messages, and identities live on Band. Jam caches them.

#Cross-cutting concerns

  • Consistency and recovery. Journals, replay order, and the event model are in Consistency and recovery.
  • State machines. Every lifecycle and its owner is in State machines.
  • Errors. ControlError maps to HTTP statuses in jam-daemon and back in jam-client, then to user-facing IpcError messages in jam-ipc.
  • Observability. jamd writes a size-rotated logs/jamd.log and one event log per peer. Managed operations log through jam_core::ManagedOperationLog with a UUID per attempt and immutable owner IDs, and never log secrets, paths, or provider content.
  • Testing. crates/jam-manager/tests/lifecycle.rs exercises a real Manager against real stores and fake transports and hosts. crates/jam-daemon/tests/roundtrip.rs covers routes through the real client. Host-only tests that need Docker or a real provider are ignored by default and gated by JAM_LIVE_* variables. Packaged desktop journeys run through WebDriver in apps/desktop/e2e/real-agents.
  • Extending Jam. What adding a harness or a feature touches today is measured in Extension seams.

#Reading paths

Rules for changing code live in AGENTS.md. Product direction lives in VISION.md.

Scroll to zoom, drag to pan.