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.
Flexible Hierarchy
Section titled “Flexible Hierarchy”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’sprojectgives 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 themanages[]hierarchy like any other role and are fully visible to task routing; a root-level role can carry its ownprojectidentity. Knowledge read scope for every agent is the org-wideorg.Organization.KnowledgeScopeonly.
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.
Common Org Patterns
Section titled “Common Org Patterns”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:
| Aspect | Root-level role | Unit role |
|---|---|---|
| Knowledge scope | Org-wide (reads are role-independent — see Knowledge System) | Org-wide (same) |
| MCP env inheritance | No parent unit to inherit from | An agent seat inherits its unit’s mcp_env; a human seat inherits nothing |
| Lead auto-management | N/A (no unit lead concept) | Auto-managed by the unit lead unless another direct member of the unit already manages it |
org.Organization.UnitFor | Returns nil | Returns the containing unit |
Flat Startup (no departments)
Section titled “Flat Startup (no departments)”units: - name: "Product Team" type: team lead: "Founder" roles: - name: "Founder" manages: ["Dev 1", "Dev 2"] - name: "Dev 1" - name: "Dev 2"Departments with Teams (traditional)
Section titled “Departments with Teams (traditional)”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: [...]Division > Department > Team (enterprise)
Section titled “Division > Department > Team (enterprise)”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: [...]Spotify Model (Tribes + Squads)
Section titled “Spotify Model (Tribes + Squads)”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: [...]Pod-Based (cross-functional)
Section titled “Pod-Based (cross-functional)”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: [...]Unit Types
Section titled “Unit Types”The type field on a unit can be any string. These well-known types are provided for convenience:
| Type | Description | Typical Use |
|---|---|---|
division | Large business unit | Top-level grouping in enterprises |
department | Functional area | Engineering, Product, Marketing |
group | Cross-functional group | Working groups, task forces |
team | Core delivery unit | Backend, Frontend, DevOps |
squad | Autonomous cross-functional unit | Spotify model |
pod | Small cross-functional group | 3-5 person focused teams |
guild | Interest-based community | Knowledge sharing groups |
chapter | Skill-based group | Design chapter, QA chapter |
unit | Generic default | When no specific type fits |
Custom types are welcome — use whatever fits your org. The type is informational and does not affect behavior.
Key Concepts
Section titled “Key Concepts”Role = Seat
Section titled “Role = Seat”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.
What anyone can read about a seat
Section titled “What anyone can read about a seat”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 flatllm_<phase>fields over thellmmapping, the seat’sllmfor a phase naming nothing, then the company’sdefaultprovider or its first. A key is the labelproviders.llmgives an entry; the model and credentials behind it are not shown. - Its tool sources (
tool_sources) —builtin, thenmcp:<server>for each MCP server the seat is granted: every shared server, and ashared: falsetemplate only where the seat or its unit declares credentials for it undermcp_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.
Handle-Based Identity
Section titled “Handle-Based Identity”Every agent gets a deterministic handle slug derived from its role name:
Role Name Handle───────────────── ────────────────Sarah Chen sarah-chenMarcus Rivera marcus-riveraAlex Kim alex-kimHandles 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.
Names and handles are unique
Section titled “Names and handles are unique”Three identities must each name exactly one thing in the whole company:
| Identity | Unique across | Why |
|---|---|---|
| Seat handle | Every seat, agent and human | It 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 name | Every seat, at any depth | A 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 none | Every unit in the tree, not only siblings | The 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"). ...The name rules are admission rules
Section titled “The name rules are admission rules”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/setupsubmission that changes the document,crewlet config import,crewlet validateand a company filecrewlet runimports as a new revision (-companyinto an empty store,-import-companyover 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
-companyor-import-companynaming a file that is that revision, or a-companyfile the store’s own company outranks), andPOST /config/reload, a/setupcredential 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 provisionand their siblings,crewlet llm status) read such a file too. Each node logsorg_admission_warningonce for every violation when it applies the epoch, naming the revision and the entities, with the document path of each underpaths. - Always readable.
GET /config, the revision reads, diffs,crewlet config showandcrewlet config exportserve the stored document as it is, so the duplicates can be seen and corrected.
A unit’s id is what survives a rename
Section titled “A unit’s id is what survives a rename”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: ENGid 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.
Management Hierarchy
Section titled “Management Hierarchy”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_askduring Execute) reach the right person - Task assignment is the unit lead’s responsibility — the lead agent reasons about its members and assigns tasks
Managing by unit name
Section titled “Managing by unit name”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 unitIf 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.
Unit Lead
Section titled “Unit Lead”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 unitBackendthatDev Asits in, shieldsDev Afrom 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.
Lead inheritance
Section titled “Lead inheritance”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-managesDev AandDev 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 at Any Level
Section titled “Roles at Any Level”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’s unit reference
Section titled “A seat’s unit reference”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.
Dangling references
Section titled “Dangling references”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.
ref | Reported when | from | to |
|---|---|---|---|
lead | A unit’s own lead names no seat | The unit | The lead as written |
unit | A root seat’s unit names no unit | The seat | The unit as written |
manages | A manages entry names neither a seat nor a unit | The seat | The entry as written |
gitlab_access_level | A key of integrations.gitlab.provisioning.access_levels is no seat’s handle | integrations.gitlab.provisioning.access_levels | The 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.
Onboarding convention
Section titled “Onboarding convention”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.
Hot Reload
Section titled “Hot Reload”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.