#Domain and authority
On this page
The objectsHow they relateAttached and owned agentsDesired and effective configurationAuthorityScope keysLegacy shapes still in the modelA Band agent, the local record that represents it, the room it works in, the configuration it runs with, the process that executes it, and the conversation thread inside that process are six different objects with six different lifetimes. Code that treats two of them as one routes messages, scopes cleanup, or attributes usage to the wrong object. AGENTS.md records several such defects on the owned-runtime surface.
#The objects
Core Jam identifiers are newtypes in crates/jam-domain/src/ids.rs (generated by the string_id! macro), so the compiler refuses to pass one where another is expected. The exceptions are the provider's own session IDs and pull receive leases, which are plain String fields on HostSession even though they are different namespaces.
| Object | Type | What it identifies | Minted by | Lifetime |
|---|---|---|---|---|
| Band actor | IdentityId |
A Band user or agent UUID. The key for every identity match: sender, participant, mention, per-agent cost. | Band | Permanent on the platform |
| Local peer | PeerKey (profile/scope) |
One local record for one Band agent under one account profile. The worker key. | jamd at onboard or ensure |
Until the peer is removed or archived |
| Room | ChatId |
A Band chat room. | Band | Permanent on the platform |
| Host session alias | SessionId |
An operator-facing name for one room binding (--host-session). Used for routing inside one peer, never as identity. |
Operator or jamd |
Until detached |
| Runtime template | RuntimeTemplateId |
Durable desired configuration for one agent identity. | jamd when an owned agent is created |
Until the agent is deleted |
| Runtime session | RuntimeSessionId |
One local agent runtime context. Stable across renames and independent of room membership. | jamd when a room-bound session is materialized |
Until archived and cleaned up |
| Runtime host | RuntimeHostId |
One concrete execution boundary: a provider process and, for Docker, one microVM. | jamd host allocator |
Until whole-host cleanup |
| Room binding | RuntimeBindingId |
One runtime session bound to one room. | jamd |
Until unbound |
| Managed workspace | WorkspaceId |
Jam-owned workspace directory ownership. A path is a reference, never the identity. | jamd workspace manager |
Until archived and removed |
| Provider thread | String in HostSession::provider_session_id / agent_session_id |
The provider's own session or thread ID (Codex thread, Claude session UUID). | The provider | Provider-defined |
Two names deserve a warning. SessionId sounds like runtime identity and is not: it is a display alias for a room binding. RuntimeSessionId is the runtime identity. And HostSession, the persisted record type, is neither of them: it is one room-to-runtime binding row that carries both IDs.
#How they relate
The objects that exist for one agent, with cardinalities:
The cardinalities that matter:
- One Band agent has one local peer per account profile.
Peeris keyed by(profile, scope)in thepeerstable and carries the Bandagent_idand its API key (crates/jam-domain/src/peer.rs). - A peer has many host sessions, one per room. The module comment on
crates/jam-domain/src/host_session.rsstates the rule: exactly one room per host session, and inbound traffic is routed bychat_idto the session that owns that room. A room with no owning session is unrouted and its messages are queued rather than guessed. - A runtime template is a host session with no room.
HostSession::is_parkedisself.room.as_str().is_empty(). The worker builds no routing surface for a parked session, so a template never receives messages. A peer can hold more than one parked template. When the agent joins a room, the manager materializes a new room-boundHostSessionfrom the template, copying the template'sRuntimeTemplateIdand minting a freshRuntimeSessionId. - A runtime host holds zero or more runtime sessions.
runtime_host_sessionsmaps each session to exactly one host.RuntimeHostAllocatorpersists the host before it attaches the first membership, so a crash between the two writes leaves a host with no members, which recovery repairs. Placement decides how many sessions share a host:RuntimeHostPlacement::SharedAgentHostkeeps one live Shared host per agent identity and rejects a room whose configuration is incompatible with it, andDedicatedSessionHostallocates one host per session (crates/jam-domain/src/runtime_workspace.rs,crates/jam-manager/src/runtime_host.rs). A shared host still gives each room its own runtime session, working directory, and provider thread. - A process is not a session. One provider process can serve several runtime sessions on a shared host, and an attached coding-agent window (one PID) can back host sessions in several rooms. Code that assumes one process per session is wrong on both paths.
#Attached and owned agents
Every host session has a runtime mode, HostRuntime in host_session.rs:
AttachInbox(attached). The user runs the coding agent, for example Claude Code in a terminal. Jam delivers messages through a cooperating surface: the Claude Code teammate mailbox file, a pull lease that the agent reads withjamcommands, or the Copilot CLI extension bridge. Jam does not own the process. Its liveness is observed throughhost_pidand the host monitor loop.Owned.jamdstarts and stops the provider process and talks to it over a structured protocol.HostTransportnames the protocol:CodexAppServer,CopilotSdk,ClaudeCodeCli,Acp,Opencode, or the legacyPtymarker, whichbuild_hostinbins/jam/src/jamd.rsnow rejects.
IdentityClass on the peer (Terminal or Owned) records the same split at identity level. It is stamped at onboard time because host session rows alone cannot tell a terminal identity with no window from an ordinary agent (crates/jam-domain/src/peer.rs, IdentityClass).
#Desired and effective configuration
An owned runtime's configuration has two generations on the HostSession record:
desired_config_generationis durable user intent. It advances on every accepted save, even if no runtime is running.effective_config_generationis what a live runtime has confirmed. It moves only when the provider accepts the change.pending_changerecords the change in flight and survives restarts, so a change that failed between save and apply is still visible afterward.
HostSession::has_outstanding_config_change is the single comparison. Status surfaces must read it rather than compare the two numbers themselves. crates/jam-manager/src/runtime_txn.rs owns the transaction that moves a change from desired to effective.
#Authority
"Manager" means jam_manager::Manager inside jamd.
| Aggregate | Authority | Mutation coordinator | Durable representation | Projections | Deletion authority |
|---|---|---|---|---|---|
| Account and credentials | Band and FusionAuth issue; Jam stores | Manager account lifecycle lock (lock_account_lifecycle) |
accounts table; tokens and keys through SecretStore (crates/jam-store/src/secret_store.rs), which prefers the OS keychain and falls back to permission-restricted files |
Desktop account list | Sign-out joins the refresh loop, then deletes the token |
| Band agent identity | Band | Band API | Band; peers.agent_id locally |
Participant lists | Band agent delete through the Human API |
| Room and membership | Band | Band API, reached through Control methods |
Band; room_directory_cache, account_directories |
Desktop room list, EventKind::ChatAdded and related events |
Band; the agent owner may delete a room its agent owns |
| Room messages | Band | Band API | Band; room_message_cache_* tables as a local cache |
Desktop conversation, fan-out events | Band |
| Peer record | Manager | Per-peer worker lifecycle gate | peers table |
PeerStatus, EventKind::PeerAdded |
Explicit stop or delete |
| Host session and template | Manager | Worker lifecycle gate, runtime room-bind gate | host_sessions table (38 columns, primary key (profile, scope, id)) |
Sessions view, agent detail | Explicit detach, unbind, or delete |
| Runtime host and membership | Manager (crates/jam-manager/src/runtime_host.rs) |
Runtime host allocator | runtime_hosts, runtime_host_sessions |
Runtime sessions table in the desktop | Whole-host cleanup operation only |
| Managed workspace | Manager (crates/jam-manager/src/workspace.rs) |
Workspace operation journal | managed_workspaces, workspace_operations, workspace_operation_events; bytes under <app>/runtime-hosts/<host>/workspace-root/ |
Recovery and repository panels | Archive, then export deletion, each separately confirmed |
| Provider thread state | The provider | Provider adapter in jam-host |
Provider files; checkpoints in provider_checkpoints plus payload under the runtime host root |
Continuity panel | Continuity reset or payload deletion |
| Private task lane | Manager (crates/jam-manager/src/task_lane.rs) |
TaskLaneCoordinator, one gate per RuntimeSessionId |
work_items, work_snapshots, task_* tables |
Work view | Lane archive and retention sweep |
| Shared room board | Local store or Band, chosen per room (crates/jam-manager/src/bandboard.rs) |
RoutedRoomBoardBackend |
room_tasks, room_task_operations, or Band |
Work board | Board task operations |
| Usage | Provider logs and live events; Jam archives | Usage reconciliation outbox | usage_* tables (12) |
Usage and cost views | Never; the archive is grow-only |
| Desktop view state | The daemon (server-derived) or the desktop (UI-only) | useDaemonStore is the only writer of server-derived state |
Memory only | React components | Reset on reload |
The table reduces to three rules.
- Band owns everything social. Rooms, membership, messages, and agent identities live on Band. Jam caches them and receives change events, but a cache disagreeing with Band is resolved by re-reading Band.
jamdowns everything about local execution. The desktop and the CLI never write runtime, workspace, or task state directly. They callControlmethods, andjamdperforms the mutation.AGENTS.mdstates this as "the desktop and CLI expose that state without becoming a second runtime owner."- Destructive actions have their own authority. Archive, force discard, export deletion, continuity reset, and whole-host cleanup each require a fresh inventory at the moment of mutation and their own confirmation. A successful inspection never grants reusable deletion authority. The sandbox and workspace chapter covers each one.
#Scope keys
Maps, locks, log lines, and caches each need a key. The correct key for common operations:
| Operation | Correct key | Wrong key that has been used or is tempting |
|---|---|---|
| Route an inbound room message | (PeerKey, ChatId) to find the owning host session |
A guessed runtime, or the first live session |
| Identify a runtime for lifecycle, logs, or cleanup | RuntimeSessionId |
SessionId alias, room ID, provider thread ID |
| Decide host-wide scope (stop, restart, reset, cleanup) | RuntimeHostId, then its durable membership set |
The clicked room row, or the live worker registry |
| Match a sender, participant, or mention | IdentityId |
Handle, display name |
| Attribute usage | Provider session ID joined through the session ledger | Room ID |
| Name a sandbox and verify ownership | Band agent UUID plus RuntimeSessionId, with a host-side plan and guest nonce |
Display name |
| Apply a runtime template to rooms | RuntimeTemplateId (UUID) for compatibility; runtime_template_id (string) for the operator-facing template |
Room ID or binding alias |
#Legacy shapes still in the model
These exist on main. A refactor must keep each one or migrate it explicitly:
- Attach-inbox as the default.
HostRuntime::default()andHostTransport::default()are bothAttachInbox, so a record with no runtime columns decodes as a legacy attached session. - Optional placement and workspace source.
HostRuntimeConfig::host_placementisNoneon records written before placement existed, which resolves to Dedicated.workspace_sourceisNoneon legacy records and maps fromspawn.cwdplussandbox.no_clone. HostTransport::Unknown. A newer daemon can persist a transport string an older build does not know. The older build decodes it asUnknown, andjam-storewrites the original string back on save so the value is not lost. The pairing is guarded by the testundecodable_enum_survives_a_load_and_save_round_trip.agent_session_idversusprovider_session_id.agent_session_idcarries the provider's ID for attached sessions but a pull receive lease for pull sessions.provider_session_idwas added so attribution always has the provider's ID. Both fields remain.- Two template identifiers.
HostSession::runtime_template_idis a string naming the parked template for operators.HostRuntimeConfig::template_idis the immutableRuntimeTemplateIdUUID used for host allocation and compatibility. A materialized room session must carry both.