Agents and machines
Workspaces, users, the Agent record, the Machine that hosts a runtime, and how routing reads them
Not built yet. There is no Agent table and no Machine table, and no MCP server is running.
packages/database still ships a single stub model inherited from the template this repository
forked. Clerk authentication and organizations are wired up today; everything below the Workspace
and User sections describes the intended shape, taken from spec/01-domain-model.md.
An Agent in this system is a record, not a process. It has an identity, a persona, capabilities, a home machine and a status. Nothing is executed here. The Agent record is what a remote Claude Code or Codex session authenticates as when it claims work.
Workspace
The tenant boundary, mapped to a Clerk organization. It holds settings, the default board columns,
and policy flags such as requireApprovalForDestructiveTools.
Every row in the domain carries workspaceId. No cross-workspace read is possible, including for
agent tokens. Slugs are unique per workspace, not globally.
User
A human, backed by Clerk. Roles are owner, admin, member and viewer. Users request tasks
(requestedByUserId), answer blocking questions, and decide
approvals.
Agent
| Field | Notes |
|---|---|
name, slug, avatar | Identity in the UI. |
runtime | claude-code, codex or custom. |
persona | Markdown system prompt and operating instructions, injected into the remote session. |
capabilities[] | Free-form tags such as rust, next.js, browser, deploy. Used for routing. |
toolAllowlist[] | Which MCP tools this agent may call. |
defaultMachineId | Preferred runner. |
status | offline, idle, busy or paused. Derived from heartbeats, never hand-set. |
lastSeenAt, concurrencyLimit | Scheduling inputs. |
Persona
The persona is the operating instruction set for the remote session, not decoration. It is the difference between an agent that knows this repository's conventions and one that guesses them. Because it lives in the record rather than in someone's terminal configuration, every machine that runs this agent runs the same one.
toolAllowlist
The allowlist is enforced server-side. The MCP server refuses a tool the agent lacks; it does not merely hide it from the tool list. Hiding is a suggestion, refusing is a rule.
Status is derived
Status is computed from heartbeats and open leases. Nothing sets it by hand, and no UI control
writes it. An agent is offline because its machine stopped reporting, not because someone marked
it so. This is what makes the status trustworthy enough to route on.
Machine
A physical or remote workstation that can host a runtime.
| Field | Notes |
|---|---|
hostname, os, arch | What it is. |
connection | mcp, api or browser. Which door this machine uses. |
enrollmentTokenId | The credential used to register it. |
capabilities[] | What it can do, independent of the agents that run on it. |
lastHeartbeatAt, status | Liveness. |
Machines heartbeat. A machine silent past heartbeatTtl marks its agents offline and releases
their leases, which returns their tasks to the queue. Machine liveness is therefore the root of
agent liveness: one silent workstation cannot hold a fleet of tasks hostage.
How routing reads them
A claim is a query, not an assignment. When an agent asks for work, the intended inputs are:
- Capability filter. The agent's
capabilities[]intersected with what the task and its project require. - Priority. The highest-priority ready task wins. See tasks.
- Concurrency. An agent at its
concurrencyLimitis passed over. - Liveness. An
offlineagent is not a candidate, and apausedagent is deliberately held out of rotation. - Machine.
defaultMachineIdis the preferred runner; the machine actually used is recorded on the Run, because the two can differ.
agents_list and machines_list exist so an agent can read this table itself and hand work off to
a better-suited peer rather than doing it badly.
Tokens
An ApiKey or AgentToken is a scoped credential for machine access: scopes[], an optional agentId
and machineId, expiresAt, lastUsedAt. Tokens are hashed at rest and never returned after
creation.
Scoping is per agent and per machine so that one credential can be revoked without touching any other. Every request is logged with the agent id that made it, which is what connects a row in the database to the identity that wrote it.