Files
Codewalkers/docs/database.md
Lukas May 940b0f8ed2 feat: Add errands persistence layer — repository port, Drizzle adapter, migration, and tests
- Add errand-repository.ts port with ErrandRepository, ErrandWithAlias, ErrandStatus types
- Add DrizzleErrandRepository adapter with create, findById (left-joins agents for alias),
  findAll (optional projectId/status filters, desc by createdAt), update, delete
- Wire exports into repositories/index.ts and repositories/drizzle/index.ts
- Add migration 0035_faulty_human_fly.sql (CREATE TABLE errands) and drizzle snapshot
- Add 13 tests covering CRUD, filtering, ordering, agentAlias join, cascade/set-null FK behaviour
- Update docs/database.md to document the errands table and ErrandRepository

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:35:06 +01:00

10 KiB

Database Module

apps/server/db/ — SQLite database via better-sqlite3 + Drizzle ORM with hexagonal architecture.

Architecture

  • Schema: apps/server/db/schema.ts — all tables, columns, relations
  • Ports (interfaces): apps/server/db/repositories/*.ts — 14 repository interfaces
  • Adapters (implementations): apps/server/db/repositories/drizzle/*.ts — 14 Drizzle adapters
  • Barrel exports: apps/server/db/index.ts re-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' | 'pending_review' | 'completed' | 'archived', default 'active'
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' | 'pending_review'
mergeBase text nullable git merge-base hash stored before phase merge, enables diff reconstruction for completed phases
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'
category text enum 'execute' | 'research' | 'discuss' | 'plan' | 'detail' | 'refine' | 'verify' | 'merge' | 'review'
priority text enum 'low' | 'medium' | 'high'
status text enum 'pending' | 'in_progress' | 'completed' | 'blocked'
order integer default 0
summary text nullable Agent result summary — propagated to dependent tasks as context
retryCount integer NOT NULL default 0, incremented on agent crash auto-retry, reset on manual retry
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
email 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'
lastFetchedAt integer/timestamp nullable, updated by ProjectSyncManager
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).

chat_sessions

Persistent chat sessions for iterative refinement of phases/tasks.

Column Type Notes
id text PK nanoid
targetType text enum 'phase' | 'task'
targetId text NOT NULL phase or task ID
initiativeId text FK → initiatives (cascade)
agentId text FK → agents (set null) linked agent
status text enum 'active' | 'closed', default 'active'
createdAt, updatedAt integer/timestamp

Indexes: (targetType, targetId), (initiativeId).

chat_messages

Messages within a chat session.

Column Type Notes
id text PK nanoid
chatSessionId text FK → chat_sessions (cascade)
role text enum 'user' | 'assistant' | 'system'
content text NOT NULL
changeSetId text FK → change_sets (set null) links assistant messages to applied changes
createdAt integer/timestamp

Index: (chatSessionId).

errands

Tracks errand work items linked to a project branch, optionally assigned to an agent.

Column Type Notes
id text PK caller-supplied
description text NOT NULL human-readable description
branch text NOT NULL working branch name
baseBranch text NOT NULL default 'main'
agentId text FK → agents (set null) assigned agent; null if unassigned
projectId text FK → projects (cascade) owning project
status text enum active, pending_review, conflict, merged, abandoned; default 'active'
createdAt, updatedAt integer/timestamp

review_comments

Inline review comments on phase diffs, persisted across page reloads.

Column Type Notes
id text PK nanoid
phaseId text FK → phases (cascade) scopes comment to phase
filePath text NOT NULL file in diff
lineNumber integer NOT NULL line number (new-side or old-side for deletions)
lineType text enum 'added' | 'removed' | 'context'
body text NOT NULL comment text
author text NOT NULL default 'you'
resolved integer/boolean default false
createdAt, updatedAt integer/timestamp

Index: (phaseId).

Repository Interfaces

14 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, 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
ChatSessionRepository createSession, findActiveSession, findActiveSessionByAgentId, updateSession, createMessage, findMessagesBySessionId
ReviewCommentRepository create, findByPhaseId, resolve, unresolve, delete
ErrandRepository create, findById, findAll (filter by projectId/status), update, delete

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 generate to create migrations
  • See database-migrations.md for full workflow
  • Snapshots stale after 0008; migrations 0008+ are hand-written

Current migrations: 0000 through 0035 (36 total).