feat(round-tables): ephemeral multi-agent deliberation sessions - #18
Conversation
Implements the Round Table protocol on top of the existing Groups API, as specified in decisive.gm work order (2026-02-28). A Round Table is a temporary, goal-oriented deliberation session with a shared thread, facilitator-only resolution, and automatic expiry. When created, ADMP auto-creates a backing group for multicast routing and sends a work_order invitation to each participant. Routes: POST /api/round-tables — create session GET /api/round-tables — list (filter by status/participant) GET /api/round-tables/:id — read session (participants only) POST /api/round-tables/:id/speak — append to thread + multicast POST /api/round-tables/:id/resolve — close session (facilitator only) Safety invariants (from adversarial review): - Max 20 participants per session - Max 200 thread entries per session - Backing group deleted on resolve and expiry (no orphaned groups) - Expiry runs in the existing cleanup loop alongside message/lease cleanup - Participant and facilitator access enforced on all endpoints Storage: - memory.js: roundTables Map + CRUD - mech.js (gitignored): admp_round_tables NoSQL collection (local only) Tests: 5 new tests covering full lifecycle, auth enforcement, and caps. 148 total tests — 133 pass, 15 fail (all pre-existing outbox/Mailgun). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
PR Review: feat(round-tables): ephemeral multi-agent deliberation sessionsOverall this is a solid feature addition. Clean structure, good test coverage for the happy path, and the safety invariants (participant cap, thread cap, facilitator-only resolve) are all properly enforced. A few issues below range from bugs to security concerns to style nits. Bugs / Correctness1. Memory leak: resolved and expired round tables are never purged
2. Race condition in In 3. In Security4. In 5. Participant IDs are not validated against registered agents Arbitrary strings can be stored in 6. No length cap on Both fields are validated as non-empty but are unbounded in size. A large topic string would be embedded in every thread multicast body and every work_order. A reasonable cap (e.g. 500 chars for topic, 2000 for goal) should be added. 7.
Code Quality8. The codebase uses a structured 9. Error-message string-matching for status codes is fragile
10. Duplicate participants not rejected
11. Resolving without a Test Coverage Gaps
Minor / Nit
Overall the implementation is clean and the core invariants hold. Main items I would want addressed before merge: #4 (participant list data exposure), #1 (memory leak), and #6/#7 (unbounded/unvalidated inputs). The race condition (#2) is low-risk today but worth fixing before adding a persistent storage adapter. |
Greptile SummaryThis PR implements the Round Table protocol as an ephemeral, goal-oriented multi-agent deliberation system built on top of the existing ADMP Groups API. The implementation adds 5 REST endpoints for creating, reading, speaking in, listing, and resolving round table sessions. Key changes:
Observations:
Confidence Score: 4/5
|
| Filename | Overview |
|---|---|
| src/routes/round-tables.js | New REST API routes for Round Table CRUD operations with proper authentication, validation, and error handling |
| src/services/round-table.service.js | Core business logic for ephemeral deliberation sessions with ADMP group integration, participant notifications, and automatic cleanup on expiry |
| src/storage/memory.js | Added roundTables Map with standard CRUD operations and participant filtering support |
| src/server.js | Registered round-tables routes and integrated expiry check into existing cleanup loop |
| src/server.test.js | Added comprehensive test coverage for Round Table lifecycle, authorization, validation, and edge cases |
Sequence Diagram
sequenceDiagram
participant F as Facilitator
participant API as Round Table API
participant GS as Group Service
participant IS as Inbox Service
participant P1 as Participant 1
participant P2 as Participant 2
F->>API: POST /api/round-tables<br/>{topic, goal, participants}
API->>GS: create(round-table-rt_xxx)
GS-->>API: group_id
loop For each participant
API->>GS: addMember(group_id, participant)
API->>IS: send work_order invitation
end
API-->>F: 201 {id, status: open, group_id}
P1->>API: POST /api/round-tables/:id/speak<br/>{message}
API->>API: Append to thread
API->>GS: postMessage(group_id, entry)
GS->>P1: Multicast to group members
GS->>P2: Multicast to group members
API-->>P1: 201 {thread_entry_id}
P2->>API: GET /api/round-tables/:id
API-->>P2: 200 {thread, status}
F->>API: POST /api/round-tables/:id/resolve<br/>{outcome}
API->>API: Update status: resolved
API->>GS: postMessage(resolution)
API->>GS: delete(group_id)
API-->>F: 200 {status: resolved, outcome}
Last reviewed commit: f586c1c
Security fixes: - Restrict GET /api/round-tables to caller own sessions only - Add size limits: topic/goal <=500 chars, outcome <=2000 chars - Validate timeout_minutes is a finite number in route layer - Validate each participant ID is a non-empty string <=255 chars Correctness fixes: - Deduplicate participants array on create - Check updateRoundTable return value in speak() and resolve() - Remove unused artifact_id field from stored schema Code quality: - Replace console.warn with structured pino logger throughout service - Use structured log objects instead of interpolated strings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Code Review — feat(round-tables): ephemeral multi-agent deliberation sessionsOverall this is a well-structured addition. The layering (route → service → storage) is consistent with the rest of the codebase, safety invariants are clearly documented, and the test coverage covers the main happy-path and access-control scenarios. A few issues are worth addressing before merge. Bugs1. Silent participant exclusion vs. In for (const participantId of uniqueParticipants) {
try {
await groupService.addMember(group.id, facilitator, participantId, 'member');
} catch (err) {
logger.warn({ participantId, ... }, '[RoundTable] Could not add participant');
}
}
2. In timeout_minutes: timeout_minutes || 30If a caller explicitly passes timeout_minutes: timeout_minutes ?? 303. Non-integer Line 47 accepts any finite number including if (timeout_minutes !== undefined && (!Number.isInteger(timeout_minutes) || timeout_minutes < 1)) { ... }Security / Correctness4. Lines 13-19 infer HTTP status codes by inspecting raw error message strings: if (msg.includes('Not a participant') || msg.includes('Only the facilitator')) return 403;Any future rewording of service error messages will silently change the HTTP response code with no test coverage to catch it. Given the security relevance of 403 vs. 400/500, prefer typed errors (e.g. a custom 5. In Missing Functionality6. No expiry notification to participants — Medium
7. Resolved/expired round tables accumulate in memory forever — Low There is no 8. No pagination on
Test Coverage Gaps9. Missing: facilitator can speak The tests verify participants can speak and outsiders cannot, but not that the facilitator (who is not in 10. Missing: duplicate participant deduplication A test that passes 11. Missing: There is no test that creates an already-expired round table and asserts 12. Missing: goal field validation The validation test exercises missing Minor Nits
Summary
The participant/group inconsistency (item 1) and the expiry notification gap (item 6) are the most important to resolve before merge. Everything else can land as follow-ups. Generated with Claude Code |
Summary
work_orderinvitation on creationEndpoints
/api/round-tables/api/round-tablesstatus,participant)/api/round-tables/:id/api/round-tables/:id/speak/api/round-tables/:id/resolveSchema
{ "id": "rt_abc123", "topic": "Should CircleSync v2 use push or pull?", "goal": "Reach consensus on sync strategy", "facilitator": "decisive.gm", "participants": ["circlesync.gm", "bootup.gm"], "group_id": "group://round-table-rt_abc123", "status": "open | resolved | expired", "thread": [{ "id": "...", "from": "circlesync.gm", "message": "...", "timestamp": "..." }], "outcome": null, "created_at": "2026-02-28T18:00:00Z", "expires_at": "2026-02-28T18:30:00Z" }Safety Invariants (from adversarial review)
Files Changed
src/routes/round-tables.js— 5 REST endpointssrc/services/round-table.service.js— business logic (create, speak, get, resolve, list, expireStale)src/storage/memory.js—roundTablesMap + CRUD methodssrc/server.js— route registration + cleanup loop hookTest plan
🤖 Generated with Claude Code