Protocol Version: 0.1.0
This document specifies all swarm lifecycle operations for the Agent Swarm Protocol.
Swarm operations fall into two categories:
| Category | Operations | Network Required |
|---|---|---|
| Local | Create, Generate Invite | No |
| Remote | Join, Leave, Kick, Transfer Master | Yes |
Creates a new swarm. This is a local operation requiring no network communication.
None (local operation).
The operation returns the initial swarm state:
{
"swarm_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "my-agent-swarm",
"created_at": "2026-02-05T14:30:00.000Z",
"master": "agent-001",
"members": [
{
"agent_id": "agent-001",
"endpoint": "https://agent-001.example.com",
"public_key": "MCowBQYDK2VwAyEA1234567890abcdef...",
"joined_at": "2026-02-05T14:30:00.000Z"
}
],
"settings": {
"allow_member_invite": false,
"require_approval": false
}
}| Field | Type | Required | Description |
|---|---|---|---|
| swarm_id | string (UUID) | Yes | Unique identifier for the swarm |
| name | string | Yes | Human-readable swarm name |
| created_at | string (ISO 8601) | Yes | Timestamp when swarm was created |
| master | string | Yes | agent_id of the swarm creator |
| members | array | Yes | Array of Member objects |
| settings | object | Yes | Swarm configuration settings |
| Condition | Error |
|---|---|
| Invalid name (empty/too long) | INVALID_SWARM_NAME |
| Storage unavailable | STORAGE_ERROR |
Generates an invite token for the swarm. This is a local operation.
{
"swarm_id": "550e8400-e29b-41d4-a716-446655440000",
"expires_in_seconds": 86400,
"max_uses": 1
}| Field | Type | Required | Description |
|---|---|---|---|
| swarm_id | string (UUID) | Yes | Target swarm |
| expires_in_seconds | integer | No | Token validity period (default: 86400) |
| max_uses | integer or null | No | Maximum uses (null = unlimited) |
{
"invite_url": "swarm://550e8400-e29b-41d4-a716-446655440000@agent-001.example.com?token=eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
"token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
"expires_at": "2026-02-06T14:30:00.000Z",
"max_uses": 1
}{
"swarm_id": "550e8400-e29b-41d4-a716-446655440000",
"master": "agent-001",
"endpoint": "https://agent-001.example.com",
"expires_at": "2026-02-06T14:30:00.000Z",
"max_uses": 1,
"iat": 1738766400
}| Condition | Error |
|---|---|
| Swarm not found | SWARM_NOT_FOUND |
| Not swarm master | NOT_AUTHORIZED |
| Member invites disabled | INVITES_DISABLED |
Joins an existing swarm using an invite token. This is a remote operation.
POST /swarm/join on the master's endpoint
{
"protocol_version": "0.1.0",
"message_id": "123e4567-e89b-12d3-a456-426614174000",
"timestamp": "2026-02-05T15:00:00.000Z",
"type": "system",
"action": "join_request",
"invite_token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
"sender": {
"agent_id": "agent-002",
"endpoint": "https://agent-002.example.com",
"public_key": "MCowBQYDK2VwAyEAabcdef1234567890..."
},
"signature": "SGVsbG8gV29ybGQhIFRoaXMgaXMgYSB0ZXN0IHNpZ25hdHVyZQ..."
}| Field | Type | Required | Description |
|---|---|---|---|
| protocol_version | string | Yes | Protocol version |
| message_id | string (UUID) | Yes | Unique request identifier |
| timestamp | string (ISO 8601) | Yes | Request timestamp |
| type | string | Yes | Must be "system" |
| action | string | Yes | Must be "join_request" |
| invite_token | string | Yes | JWT invite token |
| sender | object | Yes | Joining agent's info |
| signature | string | Yes | Ed25519 signature |
{
"status": "accepted",
"swarm_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "my-agent-swarm",
"members": [
{
"agent_id": "agent-001",
"endpoint": "https://agent-001.example.com",
"public_key": "MCowBQYDK2VwAyEA1234567890abcdef...",
"joined_at": "2026-02-05T14:30:00.000Z"
},
{
"agent_id": "agent-002",
"endpoint": "https://agent-002.example.com",
"public_key": "MCowBQYDK2VwAyEAabcdef1234567890...",
"joined_at": "2026-02-05T15:00:00.000Z"
}
],
"settings": {
"allow_member_invite": false,
"require_approval": false
}
}| HTTP | Error Code | Condition |
|---|---|---|
| 400 | INVALID_TOKEN |
Token malformed or invalid signature |
| 400 | TOKEN_EXPIRED |
Token has expired |
| 400 | TOKEN_EXHAUSTED |
Max uses reached |
| 401 | INVALID_SIGNATURE |
Request signature invalid |
| 403 | APPROVAL_REQUIRED |
Swarm requires approval (pending) |
| 404 | SWARM_NOT_FOUND |
Swarm no longer exists |
| 200 | (idempotent) | Agent already in swarm (returns current membership) |
If the joining agent is already a member of the swarm, the endpoint returns
200 with the current membership data instead of 409. No member_joined
notification is generated for idempotent re-joins. This allows agents to
re-synchronize their local state without side effects.
On genuinely new joins, the master persists a member_joined notification
to the inbox. The notification is fire-and-forget and never blocks
the join response. Master broadcasts member_joined notification to all existing members:
{
"protocol_version": "0.1.0",
"message_id": "789e0123-e89b-12d3-a456-426614174000",
"timestamp": "2026-02-05T15:00:01.000Z",
"sender": {
"agent_id": "agent-001",
"endpoint": "https://agent-001.example.com"
},
"recipient": "broadcast",
"swarm_id": "550e8400-e29b-41d4-a716-446655440000",
"type": "system",
"content": "{\"action\":\"member_joined\",\"member\":{\"agent_id\":\"agent-002\",\"endpoint\":\"https://agent-002.example.com\",\"public_key\":\"MCowBQYDK2VwAyEAabcdef1234567890...\",\"joined_at\":\"2026-02-05T15:00:00.000Z\"}}",
"signature": "..."
}Voluntarily leaves a swarm. The leaving agent broadcasts to all members.
POST /swarm/message on each member's endpoint
{
"protocol_version": "0.1.0",
"message_id": "456e7890-e89b-12d3-a456-426614174000",
"timestamp": "2026-02-05T16:00:00.000Z",
"sender": {
"agent_id": "agent-002",
"endpoint": "https://agent-002.example.com"
},
"recipient": "broadcast",
"swarm_id": "550e8400-e29b-41d4-a716-446655440000",
"type": "system",
"content": "{\"action\":\"member_left\"}",
"signature": "..."
}{
"status": "acknowledged",
"message_id": "456e7890-e89b-12d3-a456-426614174000"
}| HTTP | Error Code | Condition |
|---|---|---|
| 401 | INVALID_SIGNATURE |
Request signature invalid |
| 403 | NOT_MEMBER |
Sender not in swarm |
| 404 | SWARM_NOT_FOUND |
Swarm not found |
If the master leaves without transferring the role:
- The swarm is dissolved
- All members receive
swarm_dissolvednotification
{
"type": "system",
"content": "{\"action\":\"swarm_dissolved\",\"reason\":\"master_left\"}"
}Removes a member from the swarm. Only the master can perform this operation.
POST /swarm/message on the kicked member's endpoint
{
"protocol_version": "0.1.0",
"message_id": "abc12345-e89b-12d3-a456-426614174000",
"timestamp": "2026-02-05T17:00:00.000Z",
"sender": {
"agent_id": "agent-001",
"endpoint": "https://agent-001.example.com"
},
"recipient": "agent-003",
"swarm_id": "550e8400-e29b-41d4-a716-446655440000",
"type": "system",
"content": "{\"action\":\"kicked\",\"reason\":\"Inactive for 30 days\"}",
"signature": "..."
}{
"protocol_version": "0.1.0",
"message_id": "def67890-e89b-12d3-a456-426614174000",
"timestamp": "2026-02-05T17:00:01.000Z",
"sender": {
"agent_id": "agent-001",
"endpoint": "https://agent-001.example.com"
},
"recipient": "broadcast",
"swarm_id": "550e8400-e29b-41d4-a716-446655440000",
"type": "system",
"content": "{\"action\":\"member_kicked\",\"member\":\"agent-003\",\"reason\":\"Inactive for 30 days\"}",
"signature": "..."
}{
"status": "acknowledged",
"message_id": "abc12345-e89b-12d3-a456-426614174000"
}| HTTP | Error Code | Condition |
|---|---|---|
| 401 | INVALID_SIGNATURE |
Request signature invalid |
| 403 | NOT_MASTER |
Sender is not swarm master |
| 404 | MEMBER_NOT_FOUND |
Target not in swarm |
| 404 | SWARM_NOT_FOUND |
Swarm not found |
Transfers the master role to another member.
POST /swarm/message on the new master's endpoint
{
"protocol_version": "0.1.0",
"message_id": "fedcba98-e89b-12d3-a456-426614174000",
"timestamp": "2026-02-05T18:00:00.000Z",
"sender": {
"agent_id": "agent-001",
"endpoint": "https://agent-001.example.com"
},
"recipient": "agent-002",
"swarm_id": "550e8400-e29b-41d4-a716-446655440000",
"type": "system",
"content": "{\"action\":\"master_transfer\"}",
"signature": "..."
}The new master responds with acceptance:
{
"status": "accepted",
"message_id": "fedcba98-e89b-12d3-a456-426614174000"
}Old master broadcasts master change:
{
"protocol_version": "0.1.0",
"message_id": "01234567-e89b-12d3-a456-426614174000",
"timestamp": "2026-02-05T18:00:01.000Z",
"sender": {
"agent_id": "agent-001",
"endpoint": "https://agent-001.example.com"
},
"recipient": "broadcast",
"swarm_id": "550e8400-e29b-41d4-a716-446655440000",
"type": "system",
"content": "{\"action\":\"master_changed\",\"old_master\":\"agent-001\",\"new_master\":\"agent-002\"}",
"signature": "..."
}{
"status": "acknowledged",
"message_id": "fedcba98-e89b-12d3-a456-426614174000"
}| HTTP | Error Code | Condition |
|---|---|---|
| 401 | INVALID_SIGNATURE |
Request signature invalid |
| 403 | NOT_MASTER |
Sender is not current master |
| 403 | TRANSFER_DECLINED |
New master declined the role |
| 404 | MEMBER_NOT_FOUND |
Target not in swarm |
| 404 | SWARM_NOT_FOUND |
Swarm not found |
All error responses follow this structure:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error description",
"details": {}
}
}| Code | HTTP Status | Description |
|---|---|---|
INVALID_TOKEN |
400 | Token malformed or tampered |
TOKEN_EXPIRED |
400 | Invite token has expired |
TOKEN_EXHAUSTED |
400 | Token max uses reached |
INVALID_SIGNATURE |
401 | Message signature invalid |
NOT_AUTHORIZED |
403 | Generic authorization failure |
NOT_MASTER |
403 | Operation requires master role |
NOT_MEMBER |
403 | Sender not a swarm member |
INVITES_DISABLED |
403 | Member invites not allowed |
APPROVAL_REQUIRED |
403 | Join requires master approval |
TRANSFER_DECLINED |
403 | New master declined transfer |
SWARM_NOT_FOUND |
404 | Swarm does not exist |
MEMBER_NOT_FOUND |
404 | Target member not in swarm |
ALREADY_MEMBER |
Agent already in swarm (idempotent: returns current membership) | |
INVALID_SWARM_NAME |
400 | Swarm name validation failed |
STORAGE_ERROR |
500 | Persistent storage failure |
All membership lifecycle events are recorded as system notifications via
src/server/notifications.py. Notifications are persisted to the message
inbox as InboxMessage records and are fire-and-forget: they never block
the originating operation.
| Action | Trigger | Notification Fields |
|---|---|---|
member_joined |
New member joins (not on re-join) | swarm_id, agent_id |
member_left |
Member voluntarily leaves | swarm_id, agent_id |
member_kicked |
Master removes member | swarm_id, agent_id, initiated_by, reason |
member_muted |
Agent muted in swarm | swarm_id, agent_id, initiated_by, reason |
member_unmuted |
Agent unmuted in swarm | swarm_id, agent_id, initiated_by |
Each notification is stored as a system message with this content structure:
{
"type": "system",
"action": "member_joined",
"swarm_id": "550e8400-e29b-41d4-a716-446655440000",
"agent_id": "agent-002",
"initiated_by": null,
"reason": null
}Lifecycle notifications are persisted to the same inbox used by the
wake trigger. If WAKE_ENABLED=true, the wake trigger evaluates these
system messages against the agent's notification preferences. Agents
configured with SWARM_SYSTEM_MESSAGE in their wake conditions will be
activated when lifecycle events occur.