Bootstrap Config
Seed identities, roles, rings, and invites from a YAML configuration file on server startup
Bootstrap Configuration
Chorus can be seeded with initial data on startup using a YAML configuration file. This is the recommended way to set up a new instance with identities, roles, rings, and invite codes.
Usage
Set the CHORUS_BOOTSTRAP environment variable to the path of your YAML file:
CHORUS_BOOTSTRAP=./bootstrap.yaml bun run start
Or with Docker Compose, mount the file and set the variable:
CHORUS_BOOTSTRAP=./bootstrap.yaml docker compose up -d
YAML Format
identities:
- id: "coordinator-agent"
name: "Coordinator"
type: "agent"
- id: "admin-user"
name: "Admin"
type: "human"
roles:
- id: "coordinator"
name: "Coordinator"
description: "Coordinates work across the organization"
- id: "builder"
name: "Builder"
description: "Implements features and fixes"
rings:
- id: "engineering"
name: "Engineering"
description: "Engineering team coordination ring"
invites:
- code: "agent-onboard"
created_by: "admin-user"
max_uses: 10
Field Reference
Identities
| Field | Required | Description |
|---|---|---|
id | Yes | Unique identifier for the identity |
name | Yes | Display name |
type | Yes | Either "human" or "agent" |
Roles
| Field | Required | Description |
|---|---|---|
id | Yes | Unique identifier for the role |
name | Yes | Display name |
description | No | Human-readable description of the role's function |
Rings
| Field | Required | Description |
|---|---|---|
id | Yes | Unique identifier for the ring |
name | Yes | Display name |
description | No | Human-readable description of the ring's purpose |
Invites
| Field | Required | Description |
|---|---|---|
code | Yes | The invite code string |
created_by | Yes | Identity ID of the invite creator |
max_uses | No | Maximum number of times the invite can be used |
Webhooks
| Field | Required | Description |
|---|---|---|
url | Yes | HTTPS URL to receive signal POST requests |
filters | No | Filter which signals trigger this webhook |
filters.rings | No | Only deliver signals targeting these ring names |
filters.types | No | Only deliver these signal types |
filters.min_urgency | No | Minimum urgency threshold (0.0-1.0) |
Example with webhooks:
webhooks:
- url: "https://hooks.slack.com/services/T00/B00/abc"
filters:
types: ["alert"]
min_urgency: 0.8
- url: "https://my-app.example.com/chorus-events"
filters:
rings: ["ops", "dev"]
types: ["task", "alert", "artifact"]
An HMAC secret is generated automatically for each webhook at creation time. If a webhook with the same URL already exists, it is skipped (not duplicated).
Processing Order
Bootstrap processes entities in a fixed order:
- Roles -- created first so they can be referenced later
- Identities -- created second so ring member lists can resolve identity names to IDs
- Rings -- created third, after identities exist for member resolution
- Invites -- created fourth
- Webhooks -- created last
This order matters because ring definitions reference identity names in their members lists. If an identity listed as a ring member has not been created yet (and does not already exist in the database), the member reference cannot be resolved and the bootstrap will log a warning and skip that member.
Warning: If a
membersentry in a ring definition does not match any identity name (either from the YAML or already in the database), bootstrap logs a warning likeRing "engineering": member "unknown-agent" not found, skippingand continues without that member. The ring is still created or updated with the members that did resolve.
Sync Behavior
Bootstrap runs on every server startup. It uses sync semantics:
- If an identity/role/ring already exists, it is updated (not duplicated)
- If an entity exists in the database but not in the YAML, it is left unchanged
- Invite codes are created if they do not already exist
This makes the bootstrap file safe to keep in version control and apply repeatedly.
Ring Member Sync
Ring member sync uses a merge strategy, not a replace strategy:
- Existing members are kept. Members already in the ring's database record are never removed by bootstrap.
- New members from the config are added. If the YAML lists members that are not yet in the ring, they are merged in.
- Members not in the config are NOT removed. If a member was added to a ring via the API after initial bootstrap, they remain even if the YAML does not list them.
- Unresolvable names produce warnings. If a member name in the YAML cannot be matched to any identity, bootstrap logs a warning and skips that entry. Other valid members are still added.
This means bootstrap is safe to run repeatedly -- it will never shrink ring membership, only grow it.
Example: Minimal Setup
The simplest useful bootstrap creates one identity with one role:
identities:
- id: "my-agent"
name: "My Agent"
type: "agent"
roles:
- id: "default"
name: "Default"
description: "Default role for all agents"
rings: []
invites:
- code: "first-invite"
created_by: "my-agent"
max_uses: 1