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.
7.7 KiB
Database Module
src/db/ — SQLite database via better-sqlite3 + Drizzle ORM with hexagonal architecture.
Architecture
- Schema:
src/db/schema.ts— all tables, columns, relations - Ports (interfaces):
src/db/repositories/*.ts— 10 repository interfaces - Adapters (implementations):
src/db/repositories/drizzle/*.ts— 10 Drizzle adapters - Barrel exports:
src/db/index.tsre-exports everything
All adapters use nanoid() for IDs, auto-manage timestamps, and use Drizzle's .returning() for atomic reads after writes.
Tables
initiatives
| Column | Type | Notes |
|---|---|---|
| id | text PK | nanoid |
| name | text NOT NULL | |
| status | text enum | 'active' | 'completed' | 'archived', default 'active' |
| mergeRequiresApproval | integer/boolean | default true |
| branch | text nullable | auto-generated initiative branch (e.g., 'cw/user-auth') |
| createdAt, updatedAt | integer/timestamp |
phases
| Column | Type | Notes |
|---|---|---|
| id | text PK | |
| initiativeId | text FK → initiatives (cascade) | |
| name | text NOT NULL | |
| content | text nullable | Tiptap JSON |
| status | text enum | 'pending' | 'approved' | 'in_progress' | 'completed' | 'blocked' |
| createdAt, updatedAt | integer/timestamp |
phase_dependencies
phaseId FK → phases, dependsOnPhaseId FK → phases. Both cascade delete.
tasks
| Column | Type | Notes |
|---|---|---|
| id | text PK | |
| phaseId | text nullable FK → phases (cascade) | |
| initiativeId | text nullable FK → initiatives (cascade) | |
| parentTaskId | text nullable self-ref FK (cascade) | decomposition hierarchy |
| name | text NOT NULL | |
| description | text nullable | |
| type | text enum | 'auto' | 'checkpoint:human-verify' | 'checkpoint:decision' | 'checkpoint:human-action' |
| category | text enum | 'execute' | 'research' | 'discuss' | 'plan' | 'detail' | 'refine' | 'verify' | 'merge' | 'review' |
| priority | text enum | 'low' | 'medium' | 'high' |
| status | text enum | 'pending_approval' | 'pending' | 'in_progress' | 'completed' | 'blocked' |
| requiresApproval | integer/boolean nullable | null = inherit from initiative |
| order | integer | default 0 |
| createdAt, updatedAt | integer/timestamp |
task_dependencies
taskId FK → tasks, dependsOnTaskId FK → tasks. Both cascade delete.
agents
| Column | Type | Notes |
|---|---|---|
| id | text PK | |
| name | text NOT NULL UNIQUE | human-readable alias (adjective-animal) |
| taskId | text nullable FK → tasks (set null) | |
| initiativeId | text nullable FK → initiatives (set null) | |
| sessionId | text nullable | CLI session ID for resume |
| worktreeId | text NOT NULL | path to agent's git worktree |
| provider | text NOT NULL | default 'claude' |
| accountId | text nullable FK → accounts (set null) | |
| status | text enum | 'idle' | 'running' | 'waiting_for_input' | 'stopped' | 'crashed' |
| mode | text enum | 'execute' | 'discuss' | 'plan' | 'detail' | 'refine' |
| pid | integer nullable | OS process ID |
| exitCode | integer nullable | |
| outputFilePath | text nullable | |
| result | text nullable | JSON |
| pendingQuestions | text nullable | JSON |
| userDismissedAt | integer/timestamp nullable | |
| createdAt, updatedAt | integer/timestamp |
accounts
| Column | Type | Notes |
|---|---|---|
| id | text PK | |
| text NOT NULL | ||
| provider | text NOT NULL | default 'claude' |
| configJson | text nullable | serialized .claude.json |
| credentials | text nullable | serialized .credentials.json |
| isExhausted | integer/boolean | default false |
| exhaustedUntil | integer/timestamp nullable | |
| lastUsedAt | integer/timestamp nullable | round-robin scheduling |
| sortOrder | integer | |
| createdAt, updatedAt | integer/timestamp |
proposals
| Column | Type | Notes |
|---|---|---|
| id | text PK | |
| agentId | text FK → agents (cascade) | |
| initiativeId | text FK → initiatives (cascade) | |
| targetType | text enum | 'page' | 'phase' | 'task' |
| targetId | text nullable | existing entity ID, null for creates |
| title, summary, content | text | markdown body |
| metadata | text nullable | JSON |
| status | text enum | 'pending' | 'accepted' | 'dismissed' |
| sortOrder | integer | |
| createdAt, updatedAt | integer/timestamp |
pages
| Column | Type | Notes |
|---|---|---|
| id | text PK | |
| initiativeId | text FK → initiatives (cascade) | |
| parentPageId | text nullable self-ref FK (cascade) | root page has NULL |
| title | text NOT NULL | |
| content | text nullable | Tiptap JSON |
| sortOrder | integer | |
| createdAt, updatedAt | integer/timestamp |
projects
| Column | Type | Notes |
|---|---|---|
| id | text PK | |
| name | text NOT NULL UNIQUE | |
| url | text NOT NULL UNIQUE | git repo URL |
| defaultBranch | text NOT NULL | default 'main' |
| createdAt, updatedAt | integer/timestamp |
initiative_projects (junction)
initiativeId + projectId with unique index. Both FK cascade.
messages
Self-referencing (parentMessageId) for threading. Sender/recipient types: 'agent' | 'user'.
agent_log_chunks
| Column | Type | Notes |
|---|---|---|
| id | text PK | |
| agentId | text NOT NULL | NO FK — survives agent deletion |
| agentName | text NOT NULL | snapshot for display |
| sessionNumber | integer | spawn=1, resume=prev+1 |
| content | text NOT NULL | raw JSONL chunk |
| createdAt | integer/timestamp |
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
11 repositories, each with standard CRUD plus domain-specific methods:
| Repository | Key Methods |
|---|---|
| InitiativeRepository | create, findById, findAll, findByStatus, update, delete |
| PhaseRepository | + createDependency, getDependencies, getDependents, findByInitiativeId |
| TaskRepository | + findByParentTaskId, findByPhaseId, findPendingApproval, createDependency |
| AgentRepository | + findByName, findByTaskId, findBySessionId, findByStatus |
| MessageRepository | + findPendingForUser, findRequiringResponse, findReplies |
| PageRepository | + findRootPage, getOrCreateRootPage, findByParentPageId |
| ProjectRepository | + junction ops: setInitiativeProjects (diff-based), findProjectsByInitiativeId |
| AccountRepository | + findNextAvailable (round-robin), markExhausted, clearExpiredExhaustion |
| ProposalRepository | + findByAgentIdAndStatus, updateManyByAgentId, countByAgentIdAndStatus |
| LogChunkRepository | insertChunk, findByAgentId, deleteByAgentId, getSessionCount |
| ConversationRepository | create, findById, findPendingForAgent, answer |
Migrations
Located in drizzle/. Applied via ensureSchema() on startup using Drizzle's migrate().
Key rules:
- Never use raw SQL for schema initialization
- Run
npx drizzle-kit generateto create migrations - See database-migrations.md for full workflow
- Snapshots stale after 0008; migrations 0008+ are hand-written
Current migrations: 0000 through 0024 (25 total).