feat: Add inter-agent conversation system (listen, ask, answer)

Enables parallel agents to communicate through a CLI-based conversation
mechanism coordinated via tRPC. Agents can ask questions to peers and
receive answers, with target resolution by agent ID, task ID, or phase ID.
This commit is contained in:
Lukas May
2026-02-10 13:43:30 +01:00
parent 270a5cb21d
commit a6371e156a
29 changed files with 632 additions and 46 deletions

View File

@@ -24,7 +24,7 @@
| `accounts/` | Account discovery, config dir setup, credential management, usage API |
| `credentials/` | `AccountCredentialManager` — credential injection per account |
| `lifecycle/` | `LifecycleController` — retry policy, signal recovery, missing signal instructions |
| `prompts/` | Mode-specific prompt builders (execute, discuss, plan, detail, refine) |
| `prompts/` | Mode-specific prompt builders (execute, discuss, plan, detail, refine) + shared inter-agent communication instructions |
## Key Flows
@@ -124,3 +124,22 @@ Agent output is persisted to `agent_log_chunks` table and drives all live stream
- Read path (`getAgentOutput` tRPC): concatenates all DB chunks (no file fallback)
- Live path (`onAgentOutput` subscription): listens for `agent:output` events
- Frontend: initial query loads from DB, subscription accumulates raw JSONL, both parsed via `parseAgentOutput()`
## Inter-Agent Communication
Agents can communicate with each other via the `conversations` table, coordinated through CLI commands.
### Prompt Integration
`INTER_AGENT_COMMUNICATION` constant in `prompts/shared.ts` is appended to all 5 agent mode prompts. It instructs agents to:
1. Start `cw listen --agent-id <ID> &` as a background process at session start
2. Handle incoming questions by answering via `cw answer` and restarting the listener
3. Ask questions to peers via `cw ask --from <ID> --agent-id|--phase-id|--task-id`
4. Kill the listener before writing `signal.json`
### Agent Identity
`manifest.json` now includes `agentId` and `agentName` fields. The manager passes these from the DB record after agent creation.
### CLI Commands
- `cw listen`: Polls `getPendingConversations`, prints first pending as JSON, exits
- `cw ask`: Creates conversation, polls `getConversation` until answered, prints answer
- `cw answer`: Calls `answerConversation`, prints confirmation JSON

View File

@@ -107,6 +107,15 @@ Uses **Commander.js** for command parsing.
| `list [--initiative <id>]` | List active previews |
| `status <previewId>` | Get preview status with service details |
### Inter-Agent Conversation (`cw listen`, `cw ask`, `cw answer`)
| Command | Description |
|---------|-------------|
| `listen --agent-id <id> [--poll-interval <ms>] [--timeout <ms>]` | Poll for pending questions, print JSON, exit |
| `ask <question> --from <agentId> [--agent-id\|--phase-id\|--task-id <target>] [--poll-interval <ms>] [--timeout <ms>]` | Ask question, block until answered, print answer |
| `answer <answer> --conversation-id <id>` | Answer a pending conversation |
All three commands output JSON for programmatic agent consumption.
### Accounts (`cw account`)
| Command | Description |
|---------|-------------|

View File

@@ -143,9 +143,29 @@ Self-referencing (parentMessageId) for threading. Sender/recipient types: 'agent
Index on `agentId` for fast queries.
### conversations
Inter-agent communication records.
| Column | Type | Notes |
|--------|------|-------|
| id | text PK | |
| fromAgentId | text NOT NULL | FK→agents ON DELETE CASCADE |
| toAgentId | text NOT NULL | FK→agents ON DELETE CASCADE |
| initiativeId | text | FK→initiatives ON DELETE SET NULL |
| phaseId | text | FK→phases ON DELETE SET NULL |
| taskId | text | FK→tasks ON DELETE SET NULL |
| question | text NOT NULL | |
| answer | text | nullable until answered |
| status | text enum | pending, answered |
| createdAt | integer/timestamp | |
| updatedAt | integer/timestamp | |
Indexes: `(toAgentId, status)` for listen polling, `(fromAgentId)`.
## Repository Interfaces
10 repositories, each with standard CRUD plus domain-specific methods:
11 repositories, each with standard CRUD plus domain-specific methods:
| Repository | Key Methods |
|-----------|-------------|
@@ -159,6 +179,7 @@ Index on `agentId` for fast queries.
| AccountRepository | + findNextAvailable (round-robin), markExhausted, clearExpiredExhaustion |
| ProposalRepository | + findByAgentIdAndStatus, updateManyByAgentId, countByAgentIdAndStatus |
| LogChunkRepository | insertChunk, findByAgentId, deleteByAgentId, getSessionCount |
| ConversationRepository | create, findById, findPendingForAgent, answer |
## Migrations
@@ -170,4 +191,4 @@ Key rules:
- See [database-migrations.md](database-migrations.md) for full workflow
- Snapshots stale after 0008; migrations 0008+ are hand-written
Current migrations: 0000 through 0018 (19 total).
Current migrations: 0000 through 0024 (25 total).

View File

@@ -25,6 +25,7 @@
| **Worktree** | `worktree:created`, `worktree:removed`, `worktree:merged`, `worktree:conflict` | 4 |
| **Account** | `account:credentials_refreshed`, `account:credentials_expired`, `account:credentials_validated` | 3 |
| **Preview** | `preview:building`, `preview:ready`, `preview:stopped`, `preview:failed` | 4 |
| **Conversation** | `conversation:created`, `conversation:answered` | 2 |
| **Log** | `log:entry` | 1 |
### Key Event Payloads

View File

@@ -212,3 +212,18 @@ Docker-based preview deployments. No database table — Docker is the source of
| `getPreviewStatus` | query | Get preview status: `{previewId}` → PreviewStatus |
Context dependency: `requirePreviewManager(ctx)` — requires `PreviewManager` from container.
## Conversation Procedures
Inter-agent communication for parallel agents.
| Procedure | Type | Description |
|-----------|------|-------------|
| `createConversation` | mutation | Ask a question: `{fromAgentId, toAgentId?, phaseId?, taskId?, question}` → Conversation |
| `getPendingConversations` | query | Poll for incoming questions: `{agentId}` → Conversation[] |
| `getConversation` | query | Get conversation by ID: `{id}` → Conversation |
| `answerConversation` | mutation | Answer a conversation: `{id, answer}` → Conversation |
Target resolution: `toAgentId` → direct; `taskId` → find running agent by task; `phaseId` → find running agent by any task in phase.
Context dependency: `requireConversationRepository(ctx)`, `requireAgentManager(ctx)`.