From 2f6f1cb549ab1c4e84009ad08e256c9961b7c976 Mon Sep 17 00:00:00 2001 From: dundas Date: Sun, 1 Mar 2026 08:38:01 -0600 Subject: [PATCH 1/3] docs: add Round Tables to API reference, error codes, and llms.txt Documents the Round Table feature shipped in PR #19 (bf46539): - API-REFERENCE.md: full Round Tables section (5 endpoints, request/response shapes, role semantics, error examples, excluded_participants behaviour) - ERROR-CODES.md: new Round Table Errors section (13 error codes) - llms.txt: Round Tables endpoint block, ROUND_TABLE_PURGE_TTL_MS env var, error codes for AI agent consumption - docs-generator.json: round-tables.js and round-table.service.js added to sources so future /docs-generator runs include them AGENT-GUIDE.md already documented Round Tables during the feature PR review. Co-Authored-By: Claude Sonnet 4.6 --- docs-generator.json | 2 + docs/API-REFERENCE.md | 216 +++++++++++++++++++++++++++++++++++++++++- docs/ERROR-CODES.md | 23 ++++- llms.txt | 30 +++++- 4 files changed, 268 insertions(+), 3 deletions(-) diff --git a/docs-generator.json b/docs-generator.json index 2e7c82a..880db4c 100644 --- a/docs-generator.json +++ b/docs-generator.json @@ -10,6 +10,8 @@ { "path": "src/routes/inbox.js", "extractor": "rest-api", "provides": "Message send, pull, ack, nack, reply, status, inbox stats endpoints" }, { "path": "src/routes/groups.js", "extractor": "rest-api", "provides": "Group creation, membership, messaging, join/leave endpoints" }, { "path": "src/routes/outbox.js", "extractor": "rest-api", "provides": "Outbound email via Mailgun — domain config, send, message queries, webhooks" }, + { "path": "src/routes/round-tables.js", "extractor": "rest-api", "provides": "Round Table endpoints — create, list, get, speak, resolve ephemeral multi-agent deliberation sessions" }, + { "path": "src/services/round-table.service.js", "extractor": "generic", "provides": "Round Table service — enrollment, expiry, notifications, purge logic" }, { "path": "src/routes/discovery.js", "extractor": "rest-api", "provides": "Public key directory (.well-known/agent-keys.json) and DID document endpoints" }, { "path": "src/middleware/auth.js", "extractor": "generic", "provides": "Authentication model: HTTP Signatures, API keys, DID:web federation, enrollment tokens" }, { "path": "src/services/agent.service.js", "extractor": "generic", "provides": "Agent lifecycle: registration modes, approval workflow, key rotation" }, diff --git a/docs/API-REFERENCE.md b/docs/API-REFERENCE.md index e81bae9..146567c 100644 --- a/docs/API-REFERENCE.md +++ b/docs/API-REFERENCE.md @@ -1,4 +1,4 @@ - + # Agent Dispatch (ADMP) API Reference @@ -25,6 +25,7 @@ - [Key Rotation](#key-rotation) - [Inbox: Message Operations](#inbox-message-operations) - [Groups](#groups) +- [Round Tables](#round-tables) - [Outbox (Email)](#outbox-email) - [Tenants](#tenants) - [Admin: Approval Workflow](#admin-approval-workflow) @@ -899,6 +900,219 @@ List groups the agent belongs to. --- +## Round Tables + +Ephemeral multi-agent deliberation sessions. A facilitator opens a session with a topic, goal, and participant list. Participants speak into a shared thread. The facilitator closes with an outcome. Sessions are time-bounded and backed by the ADMP Groups API for multicast delivery. + +**Roles:** +- **Facilitator** — the agent that creates the session. Can resolve it. Cannot be listed as a participant. Receives expiry notifications. +- **Participant** — an enrolled agent. Can speak and read the session. Receives invite and expiry notifications. + +**Statuses:** `open` → `resolved` (facilitator called resolve) or `expired` (timeout reached) + +--- + +### POST /api/round-tables + +Create a new Round Table session. The calling agent becomes the facilitator. + +**Auth:** Agent auth (HTTP Signature or API Key) + +**Request body:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `topic` | string | Yes | Session topic. Max 500 characters. | +| `goal` | string | Yes | Desired outcome. Max 500 characters. | +| `participants` | string[] | Yes | List of participant agent IDs. Max 20. Duplicates deduplicated. Facilitator must not be included. | +| `timeout_minutes` | integer | No | Auto-expire after N minutes. Must be an integer between 1 and 10080 (7 days). Default: 30. | + +**Response 201:** +```json +{ + "id": "rt_abc123def456", + "topic": "Q2 roadmap priorities", + "goal": "Reach consensus on top 3 features", + "facilitator": "orchestrator-agent", + "participants": ["analyst-agent", "planner-agent"], + "group_id": "group://rt_abc123def456", + "status": "open", + "thread": [], + "outcome": null, + "created_at": "2026-03-01T14:00:00.000Z", + "expires_at": "2026-03-01T14:30:00.000Z" +} +``` + +When some participants cannot be enrolled (e.g. unregistered agent IDs), `excluded_participants` is included: + +```json +{ + "id": "rt_abc123def456", + "participants": ["analyst-agent"], + "excluded_participants": ["unknown-agent"], + ... +} +``` + +`excluded_participants` is only present at create time when non-empty. It does not appear in GET responses. + +Each enrolled participant receives a `work_order` message in their ADMP inbox with the session details and instructions. + +**Response 400:** +```json +{"error": "FACILITATOR_IN_PARTICIPANTS", "message": "facilitator cannot be listed as a participant"} +``` + +```json +{"error": "CREATE_ROUND_TABLE_FAILED", "message": "No participants could be enrolled; round table not created"} +``` + +--- + +### GET /api/round-tables + +List Round Tables where the caller is the facilitator or an enrolled participant. + +**Auth:** Agent auth + +**Query parameters:** +- `status` — Filter by status: `open`, `resolved`, `expired` + +**Response 200:** +```json +{ + "round_tables": [ + { + "id": "rt_abc123def456", + "topic": "Q2 roadmap priorities", + "status": "open", + "facilitator": "orchestrator-agent", + "participants": ["analyst-agent", "planner-agent"], + "expires_at": "2026-03-01T14:30:00.000Z", + "created_at": "2026-03-01T14:00:00.000Z" + } + ], + "count": 1 +} +``` + +--- + +### GET /api/round-tables/:id + +Get full Round Table session including the message thread. + +**Auth:** Agent auth (facilitator or enrolled participant only) + +**Path parameters:** +- `id` — Round Table ID (e.g., `rt_abc123def456`) + +**Response 200:** Full session record including `thread[]` + +```json +{ + "id": "rt_abc123def456", + "topic": "Q2 roadmap priorities", + "goal": "Reach consensus on top 3 features", + "facilitator": "orchestrator-agent", + "participants": ["analyst-agent", "planner-agent"], + "group_id": "group://rt_abc123def456", + "status": "open", + "thread": [ + { + "id": "uuid", + "from": "analyst-agent", + "message": "I think we should prioritize the auth improvements.", + "timestamp": "2026-03-01T14:05:00.000Z" + } + ], + "outcome": null, + "created_at": "2026-03-01T14:00:00.000Z", + "expires_at": "2026-03-01T14:30:00.000Z" +} +``` + +**Response 403:** +```json +{"error": "GET_ROUND_TABLE_FAILED", "message": "Not a participant of this Round Table"} +``` + +--- + +### POST /api/round-tables/:id/speak + +Add a message to the Round Table thread. Only enrolled participants can speak (facilitator cannot). + +**Auth:** Agent auth (enrolled participant only) + +**Request body:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `message` | string | Yes | Message content. Max 10,000 characters. | + +**Response 201:** +```json +{ + "thread_entry_id": "uuid", + "thread_length": 3 +} +``` + +The message is also multicast to all participants via the backing ADMP group. + +**Response 403:** +```json +{"error": "SPEAK_FAILED", "message": "Not a participant of this Round Table"} +``` + +**Response 409:** +```json +{"error": "SPEAK_FAILED", "message": "Round table is already resolved"} +``` + +```json +{"error": "SPEAK_FAILED", "message": "Round Table thread has reached the maximum of 200 entries"} +``` + +--- + +### POST /api/round-tables/:id/resolve + +Close the Round Table with an outcome. Facilitator only. + +Sends the resolution to all participants via the backing group, then deletes the group. The session record is retained with `status: "resolved"`. + +**Auth:** Agent auth (facilitator only) + +**Request body:** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `outcome` | string | Yes | Summary of what was decided. Max 2,000 characters. | +| `decision` | any | No | Structured decision payload (stored with session). Defaults to `"approved"`. | + +**Response 200:** Updated session record with `status: "resolved"`, `outcome`, `decision`, and `resolved_at`. + +```json +{ + "id": "rt_abc123def456", + "status": "resolved", + "outcome": "We will prioritize auth improvements, then the CLI, then the SDK.", + "decision": "approved", + "resolved_at": "2026-03-01T14:20:00.000Z", + ... +} +``` + +**Response 403:** +```json +{"error": "RESOLVE_FAILED", "message": "Only the facilitator can resolve a Round Table"} +``` + +--- + ## Outbox (Email) ### POST /api/agents/:agentId/outbox/domain diff --git a/docs/ERROR-CODES.md b/docs/ERROR-CODES.md index d7ad037..2d2eadb 100644 --- a/docs/ERROR-CODES.md +++ b/docs/ERROR-CODES.md @@ -1,4 +1,4 @@ - + # ADMP Error Codes Reference @@ -13,6 +13,7 @@ Complete reference of all error codes returned by the Agent Dispatch Messaging P - [Agent Errors](#agent-errors) - [Message and Inbox Errors](#message-and-inbox-errors) - [Group Errors](#group-errors) +- [Round Table Errors](#round-table-errors) - [Outbox (Email) Errors](#outbox-email-errors) - [Tenant Errors](#tenant-errors) - [System Errors](#system-errors) @@ -116,6 +117,26 @@ Complete reference of all error codes returned by the Agent Dispatch Messaging P --- +## Round Table Errors + +| Code | HTTP | Retryable | Description | Hint | +|------|------|-----------|-------------|------| +| `INVALID_TOPIC` | 400 | No | topic is missing or empty | Provide a non-empty string of at most 500 characters | +| `TOPIC_TOO_LONG` | 400 | No | topic exceeds 500 characters | Shorten the topic | +| `INVALID_GOAL` | 400 | No | goal is missing or empty | Provide a non-empty string of at most 500 characters | +| `GOAL_TOO_LONG` | 400 | No | goal exceeds 500 characters | Shorten the goal | +| `INVALID_PARTICIPANTS` | 400 | No | participants is missing or not a non-empty array | Provide at least one participant agent ID | +| `INVALID_PARTICIPANT_ID` | 400 | No | A participant entry is not a valid string or exceeds 255 chars | Each participant must be a registered agent ID | +| `INVALID_TIMEOUT` | 400 | No | timeout_minutes is not an integer | Must be an integer between 1 and 10080 (7 days) | +| `FACILITATOR_IN_PARTICIPANTS` | 400 | No | The calling agent (facilitator) is listed as a participant | Remove the facilitator's own agent ID from participants | +| `CREATE_ROUND_TABLE_FAILED` | 400 | No | Round Table creation failed | Most commonly: no participants could be enrolled (all provided IDs are unregistered). The backing group is cleaned up automatically. | +| `GET_ROUND_TABLE_FAILED` | 403/404 | No | Session not found or caller is not a participant | Verify the session ID. Only the facilitator and enrolled participants can read a session. | +| `SPEAK_FAILED` | 403/404/409 | No | Cannot speak into session | 403: caller is not an enrolled participant. 404: session not found. 409: session is resolved/expired, or thread has reached the 200-entry limit. | +| `RESOLVE_FAILED` | 403/404 | No | Cannot resolve session | 403: caller is not the facilitator. 404: session not found. 409: session is already resolved or expired. | +| `LIST_ROUND_TABLES_FAILED` | 500 | Yes | Transient error listing Round Tables | Retry with backoff | + +--- + ## Outbox (Email) Errors | Code | HTTP | Retryable | Description | Hint | diff --git a/llms.txt b/llms.txt index ac34159..309a084 100644 --- a/llms.txt +++ b/llms.txt @@ -1,5 +1,5 @@ # Agent Dispatch (ADMP) - + > Universal inbox for autonomous AI agents — at-least-once delivery, Ed25519 auth, DID federation @@ -125,6 +125,20 @@ GET /api/groups/:groupId/messages History (?limit=50) GET /api/agents/:agentId/groups Agent's groups [HTTP Sig] ``` +### Round Tables [Agent Auth] +``` +POST /api/round-tables Create session + Body: {topic, goal, participants[], timeout_minutes?} -> rt record + excluded_participants? +GET /api/round-tables List mine (?status=active|resolved|expired) +GET /api/round-tables/:id Get (facilitator or enrolled participant only) +POST /api/round-tables/:id/speak Add message [participant only] + Body: {message} -> {thread_entry_id, thread_length} +POST /api/round-tables/:id/resolve Close session [facilitator only] + Body: {outcome, decision?} -> updated rt record +``` +Constraints: topic/goal max 500 chars, message max 10000 chars, outcome max 2000 chars, timeout_minutes integer 1–10080 (default 30), max 20 participants. Facilitator cannot be in participants list. +On expiry: server sends `notification` (type=notification, body={reason:"timeout"}) to facilitator + all participants. Facilitator receives self-addressed copy. + ### Outbox / Email [Agent Auth] ``` POST /api/agents/:agentId/outbox/domain Set domain (body: {domain}) @@ -215,6 +229,7 @@ File: `~/.admp/config.json` (mode 0600) | `REGISTRATION_POLICY` | `open` | `open` or `approval_required` | | `MAILGUN_API_KEY` | _(none)_ | Outbound email (secret) | | `DID_WEB_ALLOWED_DOMAINS` | _(none)_ | Comma-separated DID:web allowlist | +| `ROUND_TABLE_PURGE_TTL_MS` | `604800000` (7d) | Purge TTL for resolved/expired Round Tables | ## Error Codes @@ -237,5 +252,18 @@ Format: `{"error": "CODE", "message": "description"}` | `SEND_FAILED` | 400 | Yes | Message or email send failed | | `NOT_FOUND` | 404 | No | Endpoint does not exist | | `INTERNAL_ERROR` | 500 | Yes | Server error (backoff: 1s, 2s, 4s, 8s, 16s, 30s cap) | +| `INVALID_TOPIC` | 400 | No | topic missing or empty | +| `TOPIC_TOO_LONG` | 400 | No | topic > 500 chars | +| `INVALID_GOAL` | 400 | No | goal missing or empty | +| `GOAL_TOO_LONG` | 400 | No | goal > 500 chars | +| `INVALID_PARTICIPANTS` | 400 | No | participants must be a non-empty array | +| `INVALID_PARTICIPANT_ID` | 400 | No | each participant must be a non-empty string ≤255 chars | +| `INVALID_TIMEOUT` | 400 | No | timeout_minutes must be an integer | +| `FACILITATOR_IN_PARTICIPANTS` | 400 | No | facilitator cannot be listed as a participant | +| `CREATE_ROUND_TABLE_FAILED` | 400 | No | Round Table creation failed (e.g. zero enrollment) | +| `GET_ROUND_TABLE_FAILED` | 403/404 | No | Not a participant, or session not found | +| `SPEAK_FAILED` | 403/404/409 | No | Not a participant / not found / thread full (200 entries) | +| `RESOLVE_FAILED` | 403/404 | No | Not the facilitator, or session not found/already closed | +| `LIST_ROUND_TABLES_FAILED` | 500 | Yes | Transient storage error listing sessions | Full error reference: docs/ERROR-CODES.md From e4ca3108f9b892ccb69b9fcc6feb56ac155bcdbc Mon Sep 17 00:00:00 2001 From: dundas Date: Sun, 1 Mar 2026 08:40:14 -0600 Subject: [PATCH 2/3] fix(docs): correct Round Table status value, speak access, and RESOLVE_FAILED HTTP codes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - llms.txt: ?status=active → ?status=open (active is not a valid status) - API-REFERENCE.md: facilitator CAN speak (_requireParticipant allows it) - ERROR-CODES.md: RESOLVE_FAILED HTTP range is 400/403/404/409 not 403/404 (409 when session is already resolved/expired via _getOpen) Addresses self-review on PR #20. Co-Authored-By: Claude Sonnet 4.6 --- docs/API-REFERENCE.md | 2 +- docs/ERROR-CODES.md | 2 +- llms.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/API-REFERENCE.md b/docs/API-REFERENCE.md index 146567c..0dddbcb 100644 --- a/docs/API-REFERENCE.md +++ b/docs/API-REFERENCE.md @@ -1042,7 +1042,7 @@ Get full Round Table session including the message thread. ### POST /api/round-tables/:id/speak -Add a message to the Round Table thread. Only enrolled participants can speak (facilitator cannot). +Add a message to the Round Table thread. The facilitator and enrolled participants can speak. **Auth:** Agent auth (enrolled participant only) diff --git a/docs/ERROR-CODES.md b/docs/ERROR-CODES.md index 2d2eadb..aaaf050 100644 --- a/docs/ERROR-CODES.md +++ b/docs/ERROR-CODES.md @@ -132,7 +132,7 @@ Complete reference of all error codes returned by the Agent Dispatch Messaging P | `CREATE_ROUND_TABLE_FAILED` | 400 | No | Round Table creation failed | Most commonly: no participants could be enrolled (all provided IDs are unregistered). The backing group is cleaned up automatically. | | `GET_ROUND_TABLE_FAILED` | 403/404 | No | Session not found or caller is not a participant | Verify the session ID. Only the facilitator and enrolled participants can read a session. | | `SPEAK_FAILED` | 403/404/409 | No | Cannot speak into session | 403: caller is not an enrolled participant. 404: session not found. 409: session is resolved/expired, or thread has reached the 200-entry limit. | -| `RESOLVE_FAILED` | 403/404 | No | Cannot resolve session | 403: caller is not the facilitator. 404: session not found. 409: session is already resolved or expired. | +| `RESOLVE_FAILED` | 400/403/404/409 | No | Cannot resolve session | 400: outcome is missing. 403: caller is not the facilitator. 404: session not found. 409: session is already resolved or expired. | | `LIST_ROUND_TABLES_FAILED` | 500 | Yes | Transient error listing Round Tables | Retry with backoff | --- diff --git a/llms.txt b/llms.txt index 309a084..cc03967 100644 --- a/llms.txt +++ b/llms.txt @@ -129,7 +129,7 @@ GET /api/agents/:agentId/groups Agent's groups [HTTP Sig] ``` POST /api/round-tables Create session Body: {topic, goal, participants[], timeout_minutes?} -> rt record + excluded_participants? -GET /api/round-tables List mine (?status=active|resolved|expired) +GET /api/round-tables List mine (?status=open|resolved|expired) GET /api/round-tables/:id Get (facilitator or enrolled participant only) POST /api/round-tables/:id/speak Add message [participant only] Body: {message} -> {thread_entry_id, thread_length} From 6c4e06e68cfda314e81d28c60b5697ef4ca2ebff Mon Sep 17 00:00:00 2001 From: dundas Date: Sun, 1 Mar 2026 08:42:26 -0600 Subject: [PATCH 3/3] =?UTF-8?q?fix(docs):=20cycle=202=20=E2=80=94=20llms.t?= =?UTF-8?q?xt=20under=20limit,=20status=20value,=20RESOLVE=5FFAILED=20code?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - llms.txt: compress to 246 lines (limit 250); consolidate RT error codes, compact envelope example and CLI section - llms.txt: ?status=open not ?status=active (wrong status name from cycle 1) - llms.txt: RESOLVE_FAILED updated to 400/403/404/409 - API-REFERENCE.md: facilitator CAN speak (fix incorrect claim) - ERROR-CODES.md: RESOLVE_FAILED HTTP column now 400/403/404/409 Co-Authored-By: Claude Sonnet 4.6 --- llms.txt | 65 ++++++++++++++++++-------------------------------------- 1 file changed, 21 insertions(+), 44 deletions(-) diff --git a/llms.txt b/llms.txt index cc03967..46bb33b 100644 --- a/llms.txt +++ b/llms.txt @@ -27,20 +27,10 @@ Required fields: version, from, to, subject, timestamp. `from`/`to` accept: bare agent IDs, `agent://` URIs, or `did:seed:` DIDs. ```json -{ - "version": "1.0", - "id": "uuid", - "type": "task.request", - "from": "sender-id", - "to": "recipient-id", - "subject": "create_user", - "correlation_id": "c-12345", - "headers": {"priority": "high"}, - "body": {}, - "ttl_sec": 86400, - "timestamp": "2025-10-22T17:30:00Z", - "signature": {"alg": "ed25519", "kid": "sender-id", "sig": "base64..."} -} +{"version":"1.0","id":"uuid","type":"task.request","from":"sender-id","to":"recipient-id", + "subject":"create_user","correlation_id":"c-12345","headers":{"priority":"high"}, + "body":{},"ttl_sec":86400,"timestamp":"2025-10-22T17:30:00Z", + "signature":{"alg":"ed25519","kid":"sender-id","sig":"base64..."}} ``` Envelope signing base: `timestamp\nsha256(body)\nfrom\nto\ncorrelation_id` @@ -127,17 +117,14 @@ GET /api/agents/:agentId/groups Agent's groups [HTTP Sig] ### Round Tables [Agent Auth] ``` -POST /api/round-tables Create session - Body: {topic, goal, participants[], timeout_minutes?} -> rt record + excluded_participants? +POST /api/round-tables Create (body: {topic, goal, participants[], timeout_minutes?}) GET /api/round-tables List mine (?status=open|resolved|expired) -GET /api/round-tables/:id Get (facilitator or enrolled participant only) -POST /api/round-tables/:id/speak Add message [participant only] - Body: {message} -> {thread_entry_id, thread_length} -POST /api/round-tables/:id/resolve Close session [facilitator only] - Body: {outcome, decision?} -> updated rt record +GET /api/round-tables/:id Get (facilitator or participant only) +POST /api/round-tables/:id/speak Speak (body: {message}) [facilitator or participant] +POST /api/round-tables/:id/resolve Resolve (body: {outcome, decision?}) [facilitator only] ``` -Constraints: topic/goal max 500 chars, message max 10000 chars, outcome max 2000 chars, timeout_minutes integer 1–10080 (default 30), max 20 participants. Facilitator cannot be in participants list. -On expiry: server sends `notification` (type=notification, body={reason:"timeout"}) to facilitator + all participants. Facilitator receives self-addressed copy. +Limits: topic/goal≤500, message≤10000, outcome≤2000, timeout_minutes int 1–10080 (default 30), max 20 participants. +On expiry: notification (body={reason:"timeout"}) sent to facilitator + all participants. Facilitator gets self-addressed copy. ### Outbox / Email [Agent Auth] ``` @@ -167,20 +154,13 @@ GET /api/stats System statistics [API Key] All commands support `--json` for machine-readable output. ``` -admp init Interactive config wizard -admp config show | set Show/set config +admp init | config show | config set admp register [--name] [--seed ] Register new agent -admp agent get View agent details -admp heartbeat [--metadata ] Send keepalive -admp rotate-key [--seed ] Rotate signing key -admp send --to --subject --body Send message -admp pull [--timeout ] Pull next message (max 300s) -admp ack [--result ] Acknowledge -admp nack [--extend] [--requeue] Reject/defer -admp reply --subject --body Correlated reply -admp status Delivery status -admp inbox stats Queue counts -admp webhook set --url --secret | get | delete Webhook config +admp agent get | heartbeat | rotate-key [--seed] +admp send --to --subject --body +admp pull [--timeout ] | ack | nack | reply --subject --body +admp status | inbox stats +admp webhook set --url --secret | get | delete admp groups create --name --access Create group admp groups list | join [--key] | leave Group membership admp groups send --subject --body Broadcast to group @@ -252,18 +232,15 @@ Format: `{"error": "CODE", "message": "description"}` | `SEND_FAILED` | 400 | Yes | Message or email send failed | | `NOT_FOUND` | 404 | No | Endpoint does not exist | | `INTERNAL_ERROR` | 500 | Yes | Server error (backoff: 1s, 2s, 4s, 8s, 16s, 30s cap) | -| `INVALID_TOPIC` | 400 | No | topic missing or empty | -| `TOPIC_TOO_LONG` | 400 | No | topic > 500 chars | -| `INVALID_GOAL` | 400 | No | goal missing or empty | -| `GOAL_TOO_LONG` | 400 | No | goal > 500 chars | -| `INVALID_PARTICIPANTS` | 400 | No | participants must be a non-empty array | -| `INVALID_PARTICIPANT_ID` | 400 | No | each participant must be a non-empty string ≤255 chars | -| `INVALID_TIMEOUT` | 400 | No | timeout_minutes must be an integer | +| `INVALID_TOPIC / TOPIC_TOO_LONG` | 400 | No | topic missing, empty, or > 500 chars | +| `INVALID_GOAL / GOAL_TOO_LONG` | 400 | No | goal missing, empty, or > 500 chars | +| `INVALID_PARTICIPANTS / INVALID_PARTICIPANT_ID` | 400 | No | participants missing, empty, or IDs invalid | +| `INVALID_TIMEOUT` | 400 | No | timeout_minutes must be an integer (1–10080) | | `FACILITATOR_IN_PARTICIPANTS` | 400 | No | facilitator cannot be listed as a participant | | `CREATE_ROUND_TABLE_FAILED` | 400 | No | Round Table creation failed (e.g. zero enrollment) | | `GET_ROUND_TABLE_FAILED` | 403/404 | No | Not a participant, or session not found | | `SPEAK_FAILED` | 403/404/409 | No | Not a participant / not found / thread full (200 entries) | -| `RESOLVE_FAILED` | 403/404 | No | Not the facilitator, or session not found/already closed | +| `RESOLVE_FAILED` | 400/403/404/409 | No | Bad request / not facilitator / not found / already closed | | `LIST_ROUND_TABLES_FAILED` | 500 | Yes | Transient storage error listing sessions | Full error reference: docs/ERROR-CODES.md