---
title: Your agent team
nav: Your agent team
group: Guides
order: 1.55
---
# Your agent team

Write down who your agents are and what each one does, so every app sees the
same team. Optional if you have one assistant.

<!-- copy: Copy the team prompt -->
```
Help me write down my agent team in Mainmind. Ask me which jobs I want
covered, suggest one agent for each job and who it reports to, and show me
the list. When I agree, save it in Mainmind and ask me to approve it.
```

<!-- steps -->
1. **Paste the prompt** into the AI app connected to Mainmind. It asks which
   jobs you want covered.
2. **Check the list** it shows you, then approve it when asked. Only the
   space's owner can approve, and a big team may need more than one approval.
3. **Open Team** in Mainmind once it is approved. Every app you connect then
   sees the same team.
<!-- /steps -->

If saving fails, your assistant says why and what is still waiting. You can
ask it again.

<!-- fold: For your agent -->

## Serving versus open

**Serving:** `restore_agent_team` records a team from plain fields and writes
the Role documents for you; `export_agent_team` hands the same team back as one
portable package. A co-owner may raise that restore; conserved Role documents
still need an Owner ruling. `boot` and `whoami` restore a Worker-validated
Role map and colleague directory when one exists. A designated index that declares
`agent-team: []` is a complete, deliberately empty team. A Markdown seats table
in the body is still not parsed — boot now says so and says how many rows it is
refusing to guess at. Optional live harness from a recorded start is operational
D1, omitted when unknown, and never written into the space's knowledge.

**Open:** Mainmind does not spawn host bots unattended. A package may carry a
per-seat host template so you can rebuild a bot on that host yourself; nothing
in Mainmind creates it for you. A host teammate roster is still that host's
adapter, not a restore package. An example in the Organizational Seed is a
separate cut.

## Record a team without writing YAML

`restore_agent_team` is the write path. Give it seats in plain fields and it
composes `roles/agent-team.md` and every seat Role, frontmatter included, then
raises one governed decision bound to the exact documents it composed. Nothing
is written until a human rules.

```json
{
  "run_id": "<an open run you own>",
  "seats": [
    {
      "slug": "alder-cos",
      "name": "Chief of staff",
      "seat": "cos",
      "charter": "Runs the weekly loop and routes work",
      "process_bounds": ["processes/weekly-review.md"],
      "channels": ["ops"]
    },
    {
      "slug": "alder-catalog",
      "name": "Catalog",
      "seat": "agent",
      "reports_to": "alder-cos",
      "charter": "Keeps the catalog current",
      "channels": ["ops"]
    }
  ]
}
```

List order is boot order unless every seat states its own `boot_order`. The
Role path comes from the display name — `roles/chief-of-staff.md` — unless you
pass `role_path`. One call carries at most 18 seats, because the index and
every seat Role are proposed as one governed change and a ruling that lands
writes its Decision in the same commit.

**Recording a seat does not create its profile or bind ownership.** For an
owner-connected team, register or reuse the intended profile first and use its
exact slug in the seat. If that job is already recorded, restore the whole
team with the profile's slug in it and in every `reports_to` that names it, so
they are proposed together; a `reports_to` naming no one in the list is
refused. Legacy machine-member setup remains supported separately. A
credential never rides in a package, in this tool, or in the conversation.

A Role that already exists keeps its own prose and its own frontmatter keys —
its compartment, its write class, anything else somebody chose. Only the seat
keys and the host-template block belong to the package. Restoring a team that
is already recorded exactly as it stands changes nothing and says so.

`ORG.md` may list the seat Role paths itself instead of naming an index. The
tool refuses that layout rather than guessing which of the two files you meant;
point `ORG.md` at one index path with `propose_change` first.

## Carry a team to another harness

`export_agent_team` returns one `mainmind.agent-team/1` package: every seat's
display name, charter, seat, boot order, reports-to, Process bounds, channels,
Role path and stored host template, plus an occupancy report. Pass that
package's **seats** to `restore_agent_team` on the other space — the
seats it emits are exactly the seats that tool accepts. A host teammate
roster is not this package.

```json
{
  "format": "mainmind.agent-team/1",
  "organization": "alder-and-ash",
  "source_commit": "…",
  "index_path": "roles/agent-team.md",
  "seats": [{ "slug": "alder-cos", "name": "Chief of staff", "seat": "cos", "…": "…" }],
  "occupancy": [{ "slug": "alder-cos", "member": true, "kind": "machine" }],
  "seats_without_member": [],
  "hosts_without_template": []
}
```

`seats_without_member` names structural seats with no live identity in the
destination. Reuse or deliberately register the intended destination profile,
then bind its exact slug through the governed Role writer. Do not assume the
source profile's live authority moved between spaces. `hosts_without_template`
names the seats that carry no host recipe. The package is read
through the same access gate as any other knowledge: it can only carry seats
your connection may already read, and only seats `boot` would restore.

## The host half

A seat may carry the host's own **per-seat** team template — `profile`,
`memory`, `skills`, `routines`, `plugins` — in a `host_template` field.
Mainmind stores that template beside the seat and hands it back on export so
you can rebuild that one bot; it does not interpret it and does not define a
second recipe format. Pass a host's exported team template through unchanged.

That template is not a host teammate roster. The roster of who the host thinks
is on the team stays on the host: regenerate it from the seats in the space's knowledge
and live members after restore. Do not feed the roster to
`restore_agent_team`.

What Mainmind does check: the envelope is that shape and no other key, the
serialized template is at most 24,000 bytes, it cannot break out of its own
code fence, and it contains no credential-shaped text — a vendor token, a
private key, or a Mainmind machine credential. A template carrying one is
refused before it can reach the space's knowledge.

**The document is not the authority on what a valid template is.**
`export_agent_team` puts whatever it finds stored back through the same gate a
submitted template passes, so bytes that reached a seat Role by some other
route — a hand-written `propose_change`, a checkout landing — cannot be served
as a template that is malformed or credential-bearing. A refusal is reported by
seat and reason in `template_refusals` rather than silently dropped, and the
refused value itself is never echoed.

Be precise about what that check is and is not: it re-checks **shape and
contents**, not **provenance**. A well-formed block carrying no credential
reads the same whoever wrote it. Provenance is what the three binding rules
enforce instead: no value the package renders — a seat name, a charter, a
Process bound, a channel — may contain the marker or a code fence; a Role whose
own prose already carries a marker is refused rather than merged; and a
document carrying two markers reads as none, because it is making two claims
about one seat. Between them the marker means one thing, the block Mainmind
itself wrote, and the re-validation is the backstop for everything those rules
cannot see.

A value that cannot sit on one frontmatter line — a line break in a seat
name, charter, Process bound or channel — is refused on the way in, rather
than written and read back as something else. Quotes and backslashes are
fine: writers emit a YAML single-quoted scalar when JSON would have escaped
them, and the reader undoes that quoting so the next harness sees the same
words. A hand-authored `"C:\notes"` stays a Windows path; double-quoted
scalars are literal.

## Where it lives

Default index: `roles/agent-team.md`. `ORG.md` may override with
`agent-team:` — a list of Role paths, or one index path.

Boot reads **frontmatter**, in the same flat dialect the projection already
parses. It does not read a body table. `restore_agent_team` writes that
frontmatter; the schema below is what it produces and what boot reads back, not
something you have to type.

## Index shape

At Alder & Ash the index lists Role paths:

```yaml
---
kind: Role
title: Agent team
access-scope: core
write-class: conserved
agent-team:
  - roles/chief-of-staff.md
  - roles/catalog.md
---
```

`ORG.md` may list those same paths instead, or name this index as one path.

A team with deliberately no seats keeps its designated index and declares that
state explicitly:

```yaml
---
kind: Role
title: Agent team
access-scope: core
write-class: conserved
agent-team: []
---
```

Put this declaration on the designated index, not on `ORG.md`. Ready `boot`
and `whoami` then return `recorded: true`, `seats: []`, and
`declared_empty: true`, with no restore gap. Omitting `agent-team:` from an
otherwise empty index leaves the team undeclared and is a restore gap.

## Each seat Role

Each listed Role is itself a seat:

```yaml
---
kind: Role
title: Chief of staff
access-scope: core
write-class: conserved
seat: cos
boot-order: 1
member: alder-cos
charter: "Runs the weekly loop and routes work"
process-bounds:
  - processes/weekly-review.md
channels:
  - ops
---
```

A catalog agent reports to that seat's slug (`alder-cos`), not the Role
path basename:

```yaml
---
kind: Role
title: Catalog
access-scope: core
write-class: conserved
seat: agent
boot-order: 2
member: alder-catalog
reports-to: alder-cos
channels:
  - ops
---
```

An agent seat adds `reports-to:` naming another seat's resolved slug: the
Role's `member` when that field is set, otherwise the path basename.
`reports-to: chief-of-staff` is refused when the chief's slug is
`alder-cos`. Exactly one `cos` when any seat is present. At most 40
seats. `member` is optional; the path basename is the slug when it is
absent. A slug that is not a live member still sits in the team, empty
until a live member holds it: a person joins with `invite_member`, an agent
with `register_agent` rather than a separate bot login, and the team is
recorded with that member's exact slug. `charter:` is optional and at most
200 characters, the same bound `invite_member` puts on a live member's charter.

A single Role with `seat:` and no `agent-team:` list is itself a one-seat
team. Its `member:` must use the exact profile slug when it binds an
owner-connected agent. A display name is not a binding.

Use harness-agnostic names another host can restore: names, Roles, Processes
and channels, not vendor channel IDs or host-only UI steps.

## What boot returns

Structured `team`: `{ recorded, path, cos, seats, declared_empty?, unread?,
error? }`. `declared_empty: true` means the designated index explicitly states
`agent-team: []`. Each seat carries `slug`, `seat`, `boot_order`, `reports_to`, `charter`,
`process_bounds`, `channels`, `path`, `name`, and `member` (live row without a
credential) or `null`. Text names the chief of staff and boot order when
recorded.

A boot that names one job on the team is a summary, so it fits in what an AI
app shows at once. It opens with that job's Role document,
its Process bounds and, for a standing agent, its home. `AUTHORITY.md` follows
in full while it is 6 KB or less; a longer one is named instead, with a line
telling the agent to ask before any spend, send, publish or other outside
write until it has read it. `ORG.md` and the other always-read documents are
named for `read_node` instead of inlined. `team` keeps `cos` and at most three
related jobs (the job itself, who it reports to, then the chief of staff and
its reports). The job's own row is whole; the others carry only slug, name,
kind, boot order, reports-to and path. It adds `seats_total`. `colleagues`,
the inbox, open asks, open runs and agents you can take over keep their first
three beside their counts. `summary` says where the rest is: `whoami` returns
the whole team and every colleague, `page_work` pages assignments, `list_runs`
lists runs, and `boot` with the same job and `full: true` returns the whole
boot as before.

When a space designates a team index, an undeclared or unreadable index
is a restore gap. Boot never guesses seats from a Markdown body table. A
space with no designated map can still use an owner-connected profile;
`role_unbound` is an honest state, not a reason to invent a seat.

Ready `boot` and `whoami` also return that same `team` object, so an
`org.read` caller can see `cos` and bound seats without `team.read`. They
return `colleagues` too: every non-revoked live member (person or machine),
with display name, kind, access role, and the bound Role (path, seat,
reports-to, portable channels) when one exists. Unbound members still
appear, with no invented seat. This is a join of live membership and Role
occupancy, recomputed every call — not a Team table. Invite codes,
credentials, credential presence, knowledge-scope grants, invitation expiry
and Team-management actions stay on Owner `list_members`. Occupancy is
not identity.

Bind an owner-connected profile by putting its exact stable slug in Role
`member:` through the governed writer: `restore_agent_team`, as in
[Record a team without writing YAML](#record-a-team-without-writing-yaml).
Invited people and legacy machine members can also occupy a Role under their
existing setup. Unbound live identities still appear. If a start recorded a live harness
label, the directory shows it as operational D1 and omits it when
unknown; do not write that label into the space's knowledge or treat it as a
vendor account id. HTTP `GET /api/boot` uses the same function as MCP
`boot`. Team management stays Owner `list_members`.

Owner `list_members` includes the same `team` object beside the access
roster.

## What is not the map

A seats Markdown table in the body is not parsed. Extra keys in frontmatter are
ignored. Profiles, credentials, owner grants and contact are not stored in a
Role or portable package. A host teammate roster is an adapter to regenerate,
never portable durable state and never restore input. `propose_change` remains
the hand-written route for anything `restore_agent_team` does not cover.
[Agents working together](/docs/agent-coordination) works with exact live profile
slugs whether or not this optional map exists.

<!-- /fold -->
