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..0dddbcb 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. The facilitator and enrolled participants can speak. + +**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..aaaf050 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` | 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 | + +--- + ## Outbox (Email) Errors | Code | HTTP | Retryable | Description | Hint | diff --git a/llms.txt b/llms.txt index ac34159..46bb33b 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 @@ -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` @@ -125,6 +115,17 @@ 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 (body: {topic, goal, participants[], timeout_minutes?}) +GET /api/round-tables List mine (?status=open|resolved|expired) +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] +``` +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] ``` POST /api/agents/:agentId/outbox/domain Set domain (body: {domain}) @@ -153,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 @@ -215,6 +209,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 +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 / 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` | 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