switch), and why “preserve, don’t homogenize” is the rule that lets a mixed-runtime team work.
What it is, and what it isn’t
A team groups agents and a leader; it is not a workspace, a tenant, or an execution context. It owns identity (name, icon, color), an optional colorCollectionId and templateId, an optional leaderAgentId, and an isArchived flag. It does not own agents; agents reference their team through a nullable teamId foreign key, so an agent belongs to at most one team (or none), and “the agents on team X” is a query, not a stored list. A team’s agentCount is computed on read (SELECT COUNT(*) FROM agents WHERE team_id = ?).
The shared/private split is not a privilege boundary and not a sandbox. It is a coordination boundary. The shared plane is the single source of truth the whole team agrees on; the private plane is where a runtime’s own cognition compounds. The split exists so that a team can mix runtimes without forcing every runtime into a lowest-common-denominator behavior; a runtime that has rich native memory or self-improving skills keeps them, while still pulling shared facts and pushing shared decisions through the same MCP servers as every other runtime.
Teams are single implicit tenant today. The
teams row (and every other table) carries a dormant tenant_id column, but no per-tenant filtering is active. Multi-tenant scoping is a future seam, not a shipped feature.The model
A team is N agents, each on some runtime. They all share one shared plane; each keeps its own private plane. The solid arrows are the shared plane; every agent reads and writes it the same way. The dashed arrows are private planes; each is owned by its runtime; Clawboo observes them but routes nothing between them.How it works
Teams are a thin grouping row
Theteams table holds identity and a leader pointer; agents carry the membership. This keeps a team a label over the agent registry rather than a container that owns runtime state. Re-coloring a team, renaming it, or changing its leader touches only the teams row; moving an agent between teams is a write to that agent’s teamId. Because membership lives on the agent, the agent registry of record stays the single owner of who exists, and the team is just how Clawboo scopes coordination.
The shared plane is the same for every runtime
Every shared-plane subsystem is keyed by team, and every runtime reaches it through the same surface:- The board is canonical for task/coordination state. A delegation becomes a board task; a claim is atomic and single-assignee; an outcome lands on the board. Chat is narration of the board, never a write path back to it. See The board.
- The team chat room is
team:<teamId>, one room per team by default. A post is delivered to every teammate except its own author (the per-(roomId, authorAgentId)echo guard is just the read’sexcludeAuthorId). A teammate’s post arrives tagged as evidence, never as a user instruction. See Peer chat. - Shared memory is team-shared by construction. When a run auto-saves a fact, it is tagged with the team only (
agentIdleft null), so any teammate on any runtime recalls it; reads see team-shared plus global plus this-agent-private, never another team’s. See Memory. - The capability inventory and Routines are likewise team-scoped. Routines are the single external wake for every runtime class; a scheduled fire is just another board-task dispatch through the standard executor pipeline (budgets, verification, observability all apply), never a side channel into a runtime’s own cron.
teamId.
The private plane is derived from capabilities, not runtime ids
Which native powers a runtime keeps is declared by the RuntimeAdapter, not hardcoded by Clawboo. A runtime’scapabilities() carries a native-preservation seam: runtimeClass, a nativeHome claim (scope + persist), and nativeSkills / nativeMemory / nativeChannels / nativeScheduler flags. The host turns those claims into a normalized integration plan with one pure function, and branches only on that plan, never on a runtime id:
runtimeClass: 'wrapped-oneshot' with an ephemeral home and nothing preserved; so omission never silently preserves or strips state beyond the plain one-shot path. A persistent home is granted only when the claim is scope: 'per-identity' and persist: true; the preserve flags are clamped to a persistent home, so a misdeclared adapter degrades to the safe default rather than producing a contradictory plan (preserving state inside a throwaway home is incoherent). For a connected-substrate runtime the host manages no filesystem home at all; the substrate owns its world entirely and the host drives it over its live connection.
Here is how each of the five runtimes declares its private plane, and what the host does with it:
| Runtime | runtimeClass | nativeHome | nativeMemory | nativeSkills | nativeChannels | What the host manages |
|---|---|---|---|---|---|---|
openclaw | connected-substrate | (omitted) | preserve | preserve | gateway | Nothing; the Gateway owns its state dir; runs ride its live connection |
clawboo-native | native | per-identity, persist | preserve | none | none | A persistent per-identity home holding persisted transcripts |
hermes | wrapped-oneshot | per-identity, persist | preserve | preserve | none | A persistent per-identity home where MEMORY.md, state.db, and skills/ compound |
claude-code | wrapped-oneshot | (omitted) | none | none | none | Nothing; the SDK runs against the user’s real home |
codex | wrapped-oneshot | per-run, persist:false | none | none | none | An ephemeral per-run home (auth lives in CODEX_HOME) |
home.kind === 'persistent', the host materializes one stable home per (runtime, agentId) under Clawboo’s own state dir, owner-only (0700), before the driver touches it; that home is where the runtime’s private memory and skills accrue across runs. The runner only computes the path; the driver provisions inside it.
Observe, but never clobber
“Never clobber” is enforced at every write boundary:- Hermes home. Clawboo writes exactly two things into a Hermes home:
mcp.json(Clawboo-owned, refreshed every run because the server port can change) and a one-timeconfig.yamlseeded copy-if-absent from the user’s real~/.hermes. It never overwrites the seeded config and never writes back. Everything else in that home,MEMORY.md,state.db, self-created skills, is Hermes’s, and it persists across runs. - The verification critic. The builder-≠-judge reviewer is built without a
homeDiron purpose: the critic must not share the builder’s native memory. Preservation is per-identity precisely so a fresh, independent reviewer can run on the same runtime without inheriting the builder’s private state. See Verification. - The AgentSource sync. When the Gateway syncs agents into SQLite, the upsert touches only Gateway-owned columns;
teamId,personalityConfig,execConfig,avatarSeed,runtime, andcapabilitiesare Clawboo-native and preserved across every re-sync. A runtime’s contribution to the shared registry never overwrites the team’s local decisions about that agent.
Design rationale and trade-offs
The split exists because the alternative, a single homogenized agent model, throws away exactly what makes a multi-runtime team worth assembling. A runtime that has built a richMEMORY.md or a library of self-created skills is more useful with that state intact; flattening every runtime to a stateless worker would erase its accumulated competence. So Clawboo preserves the private plane and unifies only the coordination plane.
Deriving the boundary from capabilities rather than runtime ids is what keeps that promise honest. Because the host branches on resolveRuntimeIntegration(caps).home.kind and never on runtime === 'hermes', a new runtime declares its own integration depth and slots in unchanged, and a misdeclared adapter degrades to the conservative one-shot path instead of silently corrupting state. The trade-off is a second persistence surface per preserved runtime (a per-identity home beside the shared board and memory), and a strict discipline at every write boundary so a shared-plane write never reaches into a private plane.
The shared plane is canonical for a related reason: with N runtimes contributing to one team, the team needs exactly one place that everyone agrees on. Making the board, chat, memory, and Routines the canonical surfaces, and treating each runtime’s own kanban, channels, or cron as private, avoids two sources of truth, the drift and double-dispatch races that come with them.
Boundaries and non-goals
- A team is not a tenant. Every table carries a dormant
tenant_id, but no per-tenant scoping is active in v0.2.0. Multi-tenant teams are a future seam, not a shipped feature. - An agent belongs to at most one team. Membership is a single nullable
teamIdforeign key; there is no many-to-many agent↔team join. A documented future seam (anagent_team_membershipstable) would change this; it is not built. - Clawboo does not bridge private planes. It never syncs a runtime’s native kanban, never serves a runtime’s messaging channel, and never co-runs a runtime’s own scheduler for teammate dispatch (
coRunScheduleris hard-typedfalse). Cross-runtime collaboration happens only on the shared plane. - The split is coordination, not isolation. It does not enforce a security or privilege boundary. Concurrency isolation for file-mutating work is the job of per-task worktrees, not of the plane split.
This documents the v0.2.0 working tree (commit
03b206a). The current npm latest is clawboo@0.1.9, so npx clawboo installs 0.1.9 until the v0.2.0 tag is published. Differences are noted in Known Issues.See also
- The agent model: Boo, Boo Zero, and the five runtime classes
- The board: the shared plane’s canonical coordination substrate
- Peer chat: the team chat room and how peer posts arrive
- Memory: the shared memory tier vs. each runtime’s private tier
- Scheduling: Routines as the single external wake
- Verification: why the critic runs without the builder’s home
- Connecting runtimes: bringing a runtime online for a team
- Glossary: canonical term definitions