---
phase: 04-agent-lifecycle
plan: 01
type: execute
wave: 1
depends_on: []
files_modified: [src/db/schema.ts, src/db/repositories/agent-repository.ts, src/db/repositories/drizzle/agent.ts, src/db/repositories/drizzle/agent.test.ts, src/db/repositories/drizzle/index.ts, src/db/repositories/index.ts]
autonomous: true
---

<objective>
Add agents table to database schema and create AgentRepository for persistence.

Purpose: Enable agent state persistence for session resumption (AGENT-04) and listing (AGENT-03).
Output: Database schema with agents table, AgentRepository port and Drizzle adapter with tests.
</objective>

<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/04-agent-lifecycle/DISCOVERY.md

@src/db/schema.ts
@src/db/repositories/task-repository.ts
@src/db/repositories/drizzle/task.ts
@src/db/repositories/drizzle/test-helpers.ts
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add agents table to database schema</name>
  <files>src/db/schema.ts</files>
  <action>
Add agents table to schema.ts following existing patterns:

```typescript
export const agents = sqliteTable('agents', {
  id: text('id').primaryKey(),
  name: text('name').notNull().unique(),      // Human-readable name (e.g., 'gastown', 'chinatown')
  taskId: text('task_id')
    .references(() => tasks.id, { onDelete: 'set null' }),  // Task may be deleted
  sessionId: text('session_id'),              // Claude CLI session ID for resumption (null until first run completes)
  worktreeId: text('worktree_id').notNull(),  // WorktreeManager worktree ID
  status: text('status', { enum: ['idle', 'running', 'waiting_for_input', 'stopped', 'crashed'] })
    .notNull()
    .default('idle'),
  createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
  updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
});
```

Note: `name` is unique, human-readable identifier. `sessionId` nullable - populated from CLI JSON output after first run. `waiting_for_input` status is for when agent pauses on AskUserQuestion.

Add relations:
```typescript
export const agentsRelations = relations(agents, ({ one }) => ({
  task: one(tasks, {
    fields: [agents.taskId],
    references: [tasks.id],
  }),
}));
```

Export types:
```typescript
export type Agent = InferSelectModel<typeof agents>;
export type NewAgent = InferInsertModel<typeof agents>;
```

Note: taskId is nullable with onDelete: 'set null' because agent may outlive task.
  </action>
  <verify>npm run build passes with no TypeScript errors</verify>
  <done>agents table defined in schema with relations and exported types</done>
</task>

<task type="auto">
  <name>Task 2: Create AgentRepository port interface</name>
  <files>src/db/repositories/agent-repository.ts, src/db/repositories/index.ts</files>
  <action>
Create AgentRepository port interface following same pattern as TaskRepository:

```typescript
// src/db/repositories/agent-repository.ts
import type { Agent, NewAgent } from '../schema.js';

export type AgentStatus = 'idle' | 'running' | 'waiting_for_input' | 'stopped' | 'crashed';

export interface AgentRepository {
  create(agent: Omit<NewAgent, 'createdAt' | 'updatedAt'>): Promise<Agent>;
  findById(id: string): Promise<Agent | null>;
  findByName(name: string): Promise<Agent | null>;  // Lookup by human-readable name
  findByTaskId(taskId: string): Promise<Agent | null>;
  findBySessionId(sessionId: string): Promise<Agent | null>;
  findAll(): Promise<Agent[]>;
  findByStatus(status: AgentStatus): Promise<Agent[]>;
  updateStatus(id: string, status: AgentStatus): Promise<Agent>;
  updateSessionId(id: string, sessionId: string): Promise<Agent>;
  delete(id: string): Promise<void>;
}
```

Export from index.ts barrel file.
  </action>
  <verify>npm run build passes</verify>
  <done>AgentRepository interface exported from src/db/repositories/</done>
</task>

<task type="auto">
  <name>Task 3: Create DrizzleAgentRepository adapter with tests</name>
  <files>src/db/repositories/drizzle/agent.ts, src/db/repositories/drizzle/agent.test.ts, src/db/repositories/drizzle/index.ts</files>
  <action>
Create DrizzleAgentRepository following existing patterns (see drizzle/task.ts):

1. Implement all AgentRepository methods using Drizzle ORM
2. Use eq() for queries, set() for updates
3. Fetch after insert to ensure schema defaults applied
4. Throw on not found for update/delete operations

Tests should cover:
- create() returns agent with timestamps
- create() rejects duplicate names
- findById() returns null for non-existent
- findByName() finds agent by human-readable name
- findByTaskId() finds agent by task
- findBySessionId() finds agent by session
- findAll() returns all agents
- findByStatus() filters correctly (including waiting_for_input)
- updateStatus() changes status and updatedAt
- updateSessionId() changes sessionId
- delete() removes agent

Use createTestDatabase() helper from test-helpers.ts for isolated test databases.

Export DrizzleAgentRepository from drizzle/index.ts.
  </action>
  <verify>npm test -- src/db/repositories/drizzle/agent.test.ts passes all tests</verify>
  <done>DrizzleAgentRepository implemented with passing tests, exported from barrel file</done>
</task>

</tasks>

<verification>
Before declaring plan complete:
- [ ] npm run build succeeds without errors
- [ ] npm test passes all agent repository tests
- [ ] agents table has correct columns and relations
- [ ] AgentRepository interface is exported
- [ ] DrizzleAgentRepository is exported from drizzle/index.ts
</verification>

<success_criteria>
- All tasks completed
- All verification checks pass
- No errors or warnings introduced
- Agent persistence layer ready for AgentManager adapter
</success_criteria>

<output>
After completion, create `.planning/phases/04-agent-lifecycle/04-01-SUMMARY.md`
</output>
