Skip to content
You are reading documentation for unreleased main. Read the 0.1 version.

Organization Model

The organization model (internal/org) is the foundational data structure representing the company hierarchy. It determines how agents communicate, what knowledge they can access, who they report to, and how tasks flow.


The org structure uses a recursive unit model (org.Unit) that can nest to any depth. This lets founders design their org however they want: flat teams, departments with sub-teams, divisions, squads, pods, or any custom structure.

The runtime tree the engine builds from a revision (org.Organization, with the Go type of each field):

Organization
├── Name, Mission, Vision string; Policies []string
├── TokenBudget TokenCeilings (org-wide ceiling per calendar window
│ — day, week, month — on the company
│ clock; an absent window is uncapped)
├── KnowledgeScope []string (knowledge.scope: the one org-wide
│ knowledge read scope)
├── Roles []*Role (root-level org-wide seats)
└── Units []*Unit
├── Name string; Type UnitType; Purpose, Lead string; Goals []string
├── ID string (units[].id: OPTIONAL stable identity.
│ Everything durable keys on it, so a
│ rename does not move what is filed
│ under the team. Absent, the name is
│ that key. See "A unit's id" below)
├── KnowledgeRefs []string
├── Channel string (team channel on the company's chat
│ surface, inherited by children)
├── Project string (tracker identity: lead-fallback routing,
│ the project the team files under, and the
│ unit an item filed into that project with
│ no unit of its own belongs to.
│ VENDOR-NEUTRAL: it names a native project
│ or a Jira one, whichever tracker.backend
│ the company runs)
├── Space string (knowledge identity: where its pages live
│ and where page activity routes. Vendor-
│ neutral likewise. Does NOT scope knowledge
│ reads)
├── MCPEnv MCPEnv (per-server tool credentials, inherited by
│ the unit's direct agent seats; human seats
│ inherit none)
├── Roles []*Role (seats directly in this unit)
├── Children []*Unit (nested sub-units, recursive)
└── Schedules []Schedule (unit recurring work, NOT inherited;
see Scheduling)
Role (a SEAT: can live at root level OR inside a unit)
├── Kind RoleKind (agent | human; default agent)
├── Name string; Responsibilities, BehavioralGuidelines []string
├── Contact *HumanContact (human seats: slack_user_id,
│ mattermost_user_id, atlassian_account_id,
│ github_login, gitlab_username,
│ crewlet_operator_id)
├── Availability string (human seats: rendered into rosters)
├── Backstory string (personality, background, expertise)
├── Goal string (individual mission)
├── DeclaredHandle string (the `handle` override; Role.Handle()
│ derives the slug when empty)
├── Email string (indexed so an address resolves to the seat)
├── Manages []string (seat names or unit names this seat manages)
├── MCPEnv MCPEnv (per-server tool credentials: env vars for
│ stdio servers, headers for http servers
│ like the remote GitHub MCP. Tool creds
│ only; the tracker and knowledge identity
│ is the Project / Space fields below)
├── Project string (a root-level seat's own tracker identity;
│ a seat inside a unit takes the unit's:
│ lead-fallback routing and write home, NOT
│ a credential)
├── Space string (likewise, the seat's own knowledge
│ container. Does NOT scope knowledge reads,
│ that is the org-wide knowledge.scope only)
├── TokenBudget TokenCeilings (this seat's own ceiling per window, on
│ top of the company's; absent = uncapped)
├── LLM, LLMReview, LLMSubagent,
│ LLMAuxiliary, LLMJudge,
│ LLMSandbox ProviderKeys (the executor's chain and the per-phase
│ satellites; see Turn Engine)
├── Workers []string (the worker templates this seat may use)
├── LearningEnabled Toggle (per-seat override for agent learning)
├── Sandbox *RoleSandbox (role.sandbox: the code-sandbox gate)
├── Placement (role.placement: which nodes may run it)
├── Slack SlackIdentity (role.integrations.slack: this seat's OWN
│ Slack app, bot_token and signing_secret)
├── Mattermost MattermostIdentity (role.integrations.mattermost: its bot)
└── Schedules []Schedule (role-scoped recurring work; see
[Scheduling](scheduling.md))

Roles can live in two places:

  • Inside a unit (units[].roles): scoped to that unit for MCP env inheritance and lead auto-management. The unit’s project gives the team its tracker “home” (routing + write target), but does not scope what the role can read.
  • At the root level (roles) — org-wide agents that don’t belong to any specific team. They participate in the manages[] hierarchy like any other role and are fully visible to task routing; a root-level role can carry its own project identity. Knowledge read scope for every agent is the org-wide org.Organization.KnowledgeScope only.

Every one of these identities is consulted. The tracker routes an item that names nobody to the lead of the unit that owns the project, and the knowledge base does the same for a page change nobody was mentioned in — whichever backend serves each, which is why the keys name neither. Neither narrows what an agent can READ: knowledge scope is the org-wide knowledge.scope only, because letting a unit’s identity double as a read scope is how an agent ends up unable to read the page it was told to follow. See Jira and Confluence.


Root-Level Roles (CEO/CTO above all teams)

Section titled “Root-Level Roles (CEO/CTO above all teams)”

Org-wide leaders can be defined at the root, outside any unit. They manage unit leads via manages[] and participate in task routing like any other role.

The root is also where the founder belongs — as a human seat above the top agent, so escalations terminate at a person and agents recognise the founder’s activity on Slack/Jira/GitHub:

roles:
- name: "Jane Founder"
kind: human
manages: ["CEO"]
contact: { slack_user_id: U0FOUNDER }
- name: "CEO"
goal: "Set company direction"
manages: ["VP Engineering", "VP Product"]
roles:
- name: "CEO"
goal: "Set company direction"
manages: ["VP Engineering", "VP Product"]
units:
- name: "Engineering"
type: department
lead: "VP Engineering"
roles:
- name: "VP Engineering"
manages: ["Backend Lead"]
children:
- name: "Backend"
type: team
lead: "Backend Lead"
roles: [...]
- name: "Product"
type: department
lead: "VP Product"
roles: [...]

Root-level roles differ from unit roles in a few ways:

AspectRoot-level roleUnit role
Knowledge scopeOrg-wide (reads are role-independent — see Knowledge System)Org-wide (same)
MCP env inheritanceNo parent unit to inherit fromAn agent seat inherits its unit’s mcp_env; a human seat inherits nothing
Lead auto-managementN/A (no unit lead concept)Auto-managed by the unit lead unless another direct member of the unit already manages it
org.Organization.UnitForReturns nilReturns the containing unit
units:
- name: "Product Team"
type: team
lead: "Founder"
roles:
- name: "Founder"
manages: ["Dev 1", "Dev 2"]
- name: "Dev 1"
- name: "Dev 2"
units:
- name: "Engineering"
type: department
lead: "VP Engineering"
children:
- name: "Backend"
type: team
lead: "Backend Lead"
roles: [...]
- name: "Frontend"
type: team
lead: "Frontend Lead"
roles: [...]
- name: "Product"
type: department
children:
- name: "Product Management"
type: team
lead: "PM"
roles: [...]

When only the top-level unit has a lead, it cascades down via lead inheritance:

units:
- name: "Technology"
type: division
lead: "CTO"
roles:
- name: "CTO"
children:
- name: "Engineering"
type: department # inherits CTO as lead
children:
- name: "Platform"
type: team
lead: "Platform Lead" # explicit — overrides inherited CTO
roles: [...]
- name: "Application"
type: team # inherits CTO as lead
roles: [...]
units:
- name: "Infrastructure Tribe"
type: tribe
lead: "Tribe Lead"
children:
- name: "Provisioning Squad"
type: squad
lead: "Squad Lead"
roles: [...]
- name: "Networking Squad"
type: squad
lead: "Squad Lead 2"
roles: [...]
units:
- name: "Auth Pod"
type: pod
lead: "Auth Lead"
roles:
- name: "Auth Lead"
manages: ["Auth Dev", "Auth Designer"]
- name: "Auth Dev"
- name: "Auth Designer"
- name: "Billing Pod"
type: pod
lead: "Billing Lead"
roles: [...]

The type field on a unit can be any string. These well-known types are provided for convenience:

TypeDescriptionTypical Use
divisionLarge business unitTop-level grouping in enterprises
departmentFunctional areaEngineering, Product, Marketing
groupCross-functional groupWorking groups, task forces
teamCore delivery unitBackend, Frontend, DevOps
squadAutonomous cross-functional unitSpotify model
podSmall cross-functional group3-5 person focused teams
guildInterest-based communityKnowledge sharing groups
chapterSkill-based groupDesign chapter, QA chapter
unitGeneric defaultWhen no specific type fits

Custom types are welcome — use whatever fits your org. The type is informational and does not affect behavior.


Each Role defines a unique seat with its own backstory, skills, personality, and domain expertise. A seat is held by an AI agent (kind: agent, the default) or a human teammate (kind: human). Each agent seat is one agent, identified by an id derived from the company name and its handle; human seats participate in the same hierarchy (manages, unit lead, rosters, escalation) but are addressable-only: no runtime, no inbox, no LLM. The founder defines each seat individually, and seats are not interchangeable. See Humans in the Org Chart.

The org chart is served as a public projection (GET /org, readable without a token under the default read posture), so what it carries is decided one field at a time. Beside the founder’s own prose and the hierarchy, it carries three things about how an agent seat RUNS, each resolved by the engine rather than left for a reader to work out:

  • Its token budget — the ceilings the document writes for the seat (and, at the top, for the company) per calendar window. The meters spending against them are GET /budgets.
  • Its model chain (llm) — every phase’s chain of provider keys, as a turn resolves it: the flat llm_<phase> fields over the llm mapping, the seat’s llm for a phase naming nothing, then the company’s default provider or its first. A key is the label providers.llm gives an entry; the model and credentials behind it are not shown.
  • Its tool sources (tool_sources) — builtin, then mcp:<server> for each MCP server the seat is granted: every shared server, and a shared: false template only where the seat or its unit declares credentials for it under mcp_env. It is the same rule the engine starts the seat’s own server instances by. The credentials themselves are never shown.

A human seat carries none of the last two, because it runs no model and no tools. Everything else about a seat — its contact identities, email, mcp_env, sandbox, placement, integrations, workers and schedules — is read only through the operator-gated configuration.

Every agent gets a deterministic handle slug derived from its role name:

Role Name Handle
───────────────── ────────────────
Sarah Chen sarah-chen
Marcus Rivera marcus-rivera
Alex Kim alex-kim

Handles are the canonical identity for notification routing and external system mappings (e.g. a Jira assignee, a GitLab service account). A seat’s email is matched too — inbound Jira and GitHub payloads identify people by address, and a plus-addressed form ([email protected]) resolves back to the handle. You can set a custom handle:

roles:
- name: Senior Engineer
handle: sr-eng # Override auto-derived "senior-engineer"

Removing a seat, and adding one back. Because identity is the handle, what a removed agent seat leaves behind is keyed by it too. Its mailbox, and the mail still addressed to it, is kept for 24 hours after the seat leaves the active revision and then retired, so a seat restored within a day finds its backlog and a seat added under the same handle later starts with an empty mailbox. Its coding runs are kept for the same 24 hours and ended when the mailbox is retired, each one announced as lost. Its memory (diary, episodes, counterparty profiles, onboarding markers) is kept, and because it is keyed by the handle or by the agent id derived from it, a seat added again under the same handle reattaches to it. Renaming a seat’s handle is a removal of the old handle and an addition of the new one. See Seat Ownership § The removed seat.

Three identities must each name exactly one thing in the whole company:

IdentityUnique acrossWhy
Seat handleEvery seat, agent and humanIt names the seat’s inbox, its derived agent id and its external accounts. Two seats on one handle share an inbox, or an agent absorbs a person’s activity.
Seat nameEvery seat, at any depthA unit’s lead and every manages entry name a seat and resolve to the first seat of that name. A second seat called the same is unreachable through either, even when its handle differs.
Unit key — its id, or its name where it declares noneEvery unit in the tree, not only siblingsThe key is what work, routing and pages are filed under, and what every manages entry and root seat unit: reference resolves against. Two teams called Platform under different departments read as distinct on every screen while one of them quietly receives the other’s work.

A seat name is compared as the exact string, the same way a lead: or a manages: entry resolves one — and a seat’s key is its handle, which is unique by its own rule, so nothing is ever filed under a seat’s name.

A unit key is compared folded, and an id is measured against other units’ names as well: a name is prose, Platform and platform are one team to every reader, and a unit’s name is its key wherever it declares no id — so id: platform beside a unit named Platform is the same collision arriving by a door nobody watches. A seat or unit with no name (or a name that derives no handle) is refused by its own rule and is never reported as a duplicate of another. A refusal names every entity that shares the key, in one message per key, and where each one sits:

duplicate unit name "Platform": 2 units carry it (under unit "Engineering"; under unit "Product"). ...

Handle uniqueness is a runnable rule: two seats on one handle share one inbox. Seat name and unit name uniqueness are admission rules: authoring hygiene that a company breaking one still runs under, and the part of the rule set a later build may add to or relax. The same holds for a unit: reference on a seat declared inside another unit. So they are enforced where a document is submitted and reported where a revision is applied — during a rolling upgrade a newer peer may activate a revision it admitted under rules this build does not share, and refusing it would split the fleet’s epoch:

  • Refused on every write. PUT /config, PATCH /config, a per-entity write, a /setup submission that changes the document, crewlet config import, crewlet validate and a company file crewlet run imports as a new revision (-company into an empty store, -import-company over a different company) all refuse a document with a duplicate seat or unit name, including a write to a company whose stored revision already carries one and a write that does not touch the duplicates. The write that corrects them is accepted.
  • Applied with a warning. A stored revision carrying a duplicate name (activated by a newer peer whose rules differ, during a rolling upgrade) is applied by every node, a node boots on it (including one started with -company or -import-company naming a file that is that revision, or a -company file the store’s own company outranks), and POST /config/reload, a /setup credential rotation (which reloads) and a revert to it still work. The vendor commands that act on a company file without storing it (crewlet gitlab provision, crewlet slack provision and their siblings, crewlet llm status) read such a file too. Each node logs org_admission_warning once for every violation when it applies the epoch, naming the revision and the entities, with the document path of each under paths.
  • Always readable. GET /config, the revision reads, diffs, crewlet config show and crewlet config export serve the stored document as it is, so the duplicates can be seen and corrected.

A unit’s name is what people read — in a prompt, on a board, in a channel topic — so it is renamed for the reasons prose is renamed. An id is chosen once and read by nobody, so what is keyed on it survives that rename:

units:
- name: Engineering
id: eng # optional; lowercase, starts with a letter
project: ENG

id is optional, and leaving it out changes nothing: a unit that declares none is keyed by its name, which is what every company runs as until somebody opts in. Everything durable — which team a work item is filed into, which team’s lead hears about it — is keyed on the id where there is one and the name where there is not.

Both spellings name the team, in any case, everywhere a stored reference is resolved — a unit on create_work_item, a routing_unit, every unit= filter, a project listing, a workload, a view strip’s container. So unit: engineering reaches Engineering, and so does unit: eng. Where a reference is one unit’s id and another unit’s name, the id wins; that pair is refused as a duplicate key on any document you submit, and it reaches a running company only through a revision applied under the runnable rules.

The references inside the org chart itself — a unit’s lead, a manages entry, a root seat’s unit: — are the exception: each names a unit by its name, as the exact string, because they are resolved against the document they are written in. One naming an id resolves to nothing and is reported as a dangling reference rather than silently ignored.

Adding an id to a team that already has work does not rewrite that work. Work filed before the id carries the name and work filed after it carries the id, and every filter matches the set of both — see Naming a team. What an id does not change is onboarding: that turns on the unit’s NAME, which is what an agent reads as its team, so renaming a unit still re-onboards the seats beneath it.

Hierarchy is encoded through manages relationships on roles. A Team Lead manages Engineers; a VP manages Team Leads. See Agent Runtime for how the hierarchy drives agent execution.

  • Permissions flow from hierarchy — a manager can assign tasks to reports, knowledge access is scoped, and the agent’s identity prompt names the manager so handoffs (a Slack mention, a Jira comment, or a2a_ask during Execute) reach the right person
  • Task assignment is the unit lead’s responsibility — the lead agent reasons about its members and assigns tasks

The manages list accepts both role names and unit names. When an entry matches an OrgUnit name (and does not match any role name), it is expanded to all roles contained in that unit, including roles in descendant child units. This avoids listing every agent individually when a role manages an entire team or department.

roles:
- name: "CEO"
manages: ["Engineering", "Product"] # unit names — expands to all roles in each unit
units:
- name: "Engineering"
type: team
lead: "Tech Lead"
roles:
- name: "Tech Lead"
- name: "Dev A"
- name: "Dev B"
- name: "Product"
type: team
lead: "PM"
roles:
- name: "PM"
- name: "Designer"

After expansion the CEO manages: Tech Lead, Dev A, Dev B, PM, Designer.

You can mix role names and unit names freely:

manages: ["CTO", "Backend"] # CTO is a role, Backend is a unit

If a name matches both a role and a unit, the role takes priority (no expansion happens for that entry). A unit name expands to every seat in that unit’s subtree except the seat that lists it: a lead that manages its own team by name does not manage itself. A name matching neither a role nor a unit is kept as written, so a seat that has not been added yet can already be named, and the engine reports it as a dangling reference.

A unit may designate a lead via the lead field. The lead is responsible for:

  • Task routing and assignment within the unit
  • Acting as the single point of contact for the unit
  • Reasoning about members’ profiles (background, goal, responsibilities) to assign tasks to the right individual

When a unit has direct roles and a lead is set, the lead auto-manages every direct member that no direct member of the same unit already manages. Three rules decide what counts as already managed, and all three read each manages entry the way unit-name expansion resolves it, so a unit name counts for every seat it reaches:

  • A member another direct member manages keeps that manager. A tech lead who lists Dev A, or who lists the unit Backend that Dev A sits in, shields Dev A from the unit lead.
  • A member the lead already manages is not listed twice.
  • A member that manages the lead is never claimed. An engineering manager who manages their own unit by name reaches the unit lead too, and claiming them back would make a two-seat management cycle.
units:
- name: "Engineering"
lead: "VP Engineering"
roles:
- name: "VP Engineering"
children:
- name: "Backend" # inherits VP Engineering as lead
roles:
- name: "Tech Lead"
manages: ["Backend"] # Dev A and Dev B, by unit name
- name: "Dev A"
- name: "Dev B"

Here VP Engineering auto-manages only Tech Lead. Dev A and Dev B report to Tech Lead alone.

Only the unit’s own direct members shield. A seat outside the unit that manages it, such as a root-level CEO with manages: ["Backend"], lists every seat in Backend but does not stop Backend’s lead from auto-managing those seats as well. That scope is deliberate: management is stored on the manager, so a CEO managing a whole division by name would otherwise leave every lead inside it with an empty roster. The consequence is that such a member has two managers. org.Organization.Manager reports the first seat in walk order that lists it, and root-level seats are walked first, so the CEO is the one an identity prompt names. To keep the unit lead as the primary manager, have the outside seat manage the lead (manages: ["Backend Lead"]) rather than the unit.

The dashboard’s org chart draws exactly this primary line. Each seat’s card hangs under the manager Organization.Manager names, and the seats of a unit its lead leads from outside it are boxed together under that lead with the unit’s name — so a member with two managers is drawn once, under the one an identity prompt names, and a chart that looks wrong is a manages list worth reading. See the org chart is the company running. Agents › Edit org’s Reporting chart draws the same line for a DRAFT, from the engine’s dry run of it, so a change to manages or to a unit’s lead can be read before it is saved — and when reordering seats moves a primary manager, the builder says so (The Org Builder).

The lead can be a human seat — a human manager running an AI team is a first-class pattern: agents escalate to the human with their own Slack/Jira tools (an @-mention), and the human assigns work in the PM tool. See Humans in the Org Chart.

The lead’s system prompt includes a roster of direct reports. Each member’s profile (background, goal, responsibilities, and for a human report its contact identities and availability) renders directly into the lead’s executor prompt from the in-memory Organization model.

When a child unit has no lead set, it automatically inherits the lead from its parent unit. This cascades through any number of levels — a division lead becomes the effective lead for every descendant that doesn’t specify its own.

units:
- name: "Engineering"
type: department
lead: "VP Engineering" # ← set here
roles:
- name: "VP Engineering"
children:
- name: "Backend"
type: team # no lead — inherits "VP Engineering"
roles:
- name: "Dev A"
- name: "Dev B"
- name: "Frontend"
type: team
lead: "Frontend Lead" # explicit — NOT overwritten
roles:
- name: "Frontend Lead"
- name: "Dev C"

In this example:

  • Backend has no lead, so it inherits VP Engineering. VP Engineering auto-manages Dev A and Dev B.
  • Frontend has an explicit lead (Frontend Lead), so the parent’s lead is ignored.

Inherited leads work the same as explicit leads for auto-management, task routing, org.Organization.IsUnitLead, and the Jira project-key mapping. The only difference is that the lead role lives in an ancestor unit rather than the current one. Use org.Organization.EffectiveLead to resolve the lead seat in code.

Roles can be placed at the org root or directly in any unit. A department-level role (like a VP) can sit alongside child teams, and org-wide roles (like a CEO) can sit at the root:

roles:
- name: "CEO"
manages: ["VP Engineering"]
units:
- name: "Engineering"
type: department
roles:
- name: "VP Engineering"
manages: ["Backend Lead", "Frontend Lead"]
children:
- name: "Backend"
type: team
lead: "Backend Lead"
roles: [...]

A seat declared at the root can name the unit it belongs to with unit:, which is how the per-entity configuration API adds a seat to a unit. The engine moves such a seat into that unit before anything else is derived, so it inherits the unit’s tool credentials and is auto-managed by the unit’s lead exactly as a seat written inside the unit is. A seat moved this way is still reported, and edited, where it was written.

The reference places a root seat and nothing else. A seat declared inside a unit is never moved by one, so a unit: on it that names a different unit reads as a placement and does nothing: the seat stays where it is written while the document says it belongs elsewhere. A document carrying one is refused at that seat’s unit; repeating the name of the unit the seat is declared in is accepted. This is an admission rule: a revision being applied that carries such a reference still runs, with a warning.

A lead, a root seat’s unit, or a manages entry names another entity by name, and that name may resolve to nothing: a misspelling, a seat that was removed, or a seat that has not been added yet. None of these refuses the revision. Live configuration changes build an organization in pieces (a unit can be added before the seat that leads it, and every node applies each intermediate revision), so refusing a partly wired organization would make that sequence impossible. Every reader treats the reference as absent instead: a unit whose lead is dangling runs with no lead, a seat whose unit names nothing stays at the root, and a manages entry naming nothing manages nobody.

The engine reports them rather than letting them pass silently. Each node logs every dangling reference once for each epoch it applies, as a warning named org_dangling_reference, and never for a revision it refused. A reference that is still logged after the organization is fully wired is a misspelling nothing else will report.

refReported whenfromto
leadA unit’s own lead names no seatThe unitThe lead as written
unitA root seat’s unit names no unitThe seatThe unit as written
managesA manages entry names neither a seat nor a unitThe seatThe entry as written
gitlab_access_levelA key of integrations.gitlab.provisioning.access_levels is no seat’s handleintegrations.gitlab.provisioning.access_levelsThe handle

Each line also carries epoch, revision and a detail sentence saying what the engine does meanwhile and how to resolve it.

What was written is reported, once. A dangling lead is reported on the unit that declares it, never on the child units that inherit it: they wrote nothing, and there is nothing to fix on them. A child unit that writes the same name itself is reported separately, because it is a second place to correct. A manages entry naming a unit that holds no seats resolves to nobody but is not a misspelling, so it is not reported.

A stale GitLab access level is worth removing promptly. Access level overrides are looked up by handle when a seat’s service account is provisioned, so the entry left behind by a removed seat grants its level to the next seat that derives the same handle. It is reported whether or not GitLab is currently enabled, since re-enabling it is exactly when the stale grant would take effect.


Each unit (and the organisation root) is expected to publish a page titled exactly Onboarding in its container of the knowledge base, its Confluence space. On an agent seat’s first turn for its current org chain, a dedicated onboarding pass runs before the executor, with a short ## First-turn onboarding block listing the unit chain (org → ancestor units → own unit). The agent reads each Onboarding page using its knowledge backend’s page-search and page-read MCP tools (confluence_search / confluence_get_page), captures the conventions that matter via reflect_and_persist, and calls mark_onboarded when done. After that, the hint disappears from subsequent prompts.

Re-onboarding fires automatically when the org structure changes (the role moves between units, a new ancestor unit is inserted, the role is renamed) — the engine recomputes a chain hash and the prior marker no longer matches. Source-page content drift is not automatic: the agent re-reads at its own discretion, or in response to a page-update notification routed through the existing notification pipeline.

This mirrors how a real new hire learns. A founder doesn’t need YAML config for which onboarding doc to point at — they just maintain an Onboarding page per scope, the same way they would for human team members. See Agent Learning for the full design.


The organization is part of the company configuration, so it changes without a restart. Activating a revision (PUT /config, PATCH /config, a per-entity write, a revert, or crewlet config import) moves the fleet’s activation pointer, and each node’s reconcile tick applies the revision it names. The stages of that apply, and what a refused one leaves behind, are in Live Propagation.

A running organization is never edited in place. The apply builds a new Organization from the revision, normalizes and validates it, and publishes it as part of a new epoch together with everything else built from the same document. Turns on many goroutines read the published tree at once, so editing it would be a data race with no owner. A turn pins the epoch it starts on and reads only that epoch until it ends, so an organization change reaches a seat at its next turn. A revision whose organization does not validate is refused before its epoch is published, and the node keeps serving the previous one.

Seats follow the new chart. After the swap the node creates a mailbox for every seat the revision adds, and seat ownership converges placement onto the new seat list, releasing a seat the organization no longer has — at the end of the apply, rather than on placement’s next periodic pass. An agent seat is identified by its handle, and its runtime id is derived from the company name and that handle (org.DeriveAgentID). A seat therefore keeps its identity and its memory through a rename or a move for as long as its handle is unchanged. A seat whose handle changes is a different seat, and renaming the company gives every agent seat a new id.

Part of Crewlet. Generated from crewlet/crewlet main at f665f5a. This is not the current version — see the latest docs.