---
status: ACTIVE
---
# CEO Plan: VTIT Agent Coding v1 — Agent-First Cross-Project Task Board

Generated by /plan-ceo-review on 2026-03-20
Branch: unknown (greenfield, no git repo yet) | Mode: EXPANSION
Repo: vtit-agent-coding

## Vision

### 10x Check
VTIT Agent Coding becomes the universal agent coordination layer — not just a task board, a protocol. Optional structured input fields on tasks, dependency chains for multi-step workflows, and smart CLI defaults that remove friction. The 10x vision (agent capability registry, event webhooks, multi-board orchestration) is the v2 trajectory — v1 lays the data model foundation.

### Platonic Ideal
A mission control for your personal AI workforce. You open the browser and see what every agent across every project is doing right now. A timeline shows history. You drag a card from "Idea" to "Todo" and an agent picks it up within seconds. Live progress streams in. When it's done, the card turns green with a PR link. The emotional arc: **confidence** — you trust your agents are working, you can see proof, you can intervene if needed.

## Scope Decisions

| # | Proposal | Effort | Decision | Reasoning |
|---|----------|--------|----------|-----------|
| 1 | Structured task input (`input` JSON field) | S | ACCEPTED | Optional freeform JSON field. Agents can pass structured payloads instead of relying on description parsing. |
| 2 | Task dependency chains (`depends_on` + blocked flag) | S | ACCEPTED | Foundation for v2 orchestrator. Without dependencies, auto-dispatch is just round-robin. |
| 3 | Smart CLI defaults (auto-detect project from git) | S | ACCEPTED | Zero-friction UX. Agents don't need to specify --project when running inside a repo. |
| 4 | GitHub PR status integration | S | ACCEPTED | Closes the loop — board shows not just "agent says done" but "code actually shipped." |
| 5 | Task duration display on cards | S | ACCEPTED | Data is free (already in task_logs). "Completed in 17 min" on cards is a dopamine hit. |
| 6 | First-run onboarding flow | S | ACCEPTED | First impressions are everything. Empty state IS the onboarding. Critical for HN demo. |
| 7 | Keyboard shortcuts | S | ACCEPTED | Power-user UX. Screenshot-worthy. Agent-future-proof (browser agents navigate by keyboard). |
| 8 | Dark mode | S | ACCEPTED | Table stakes for dev tools in 2026. CSS custom properties make it nearly free. |

## Accepted Scope — Detailed Specifications

### 1. Structured Task Input (`input` field)

An optional freeform JSON field on tasks. No schema validation — the field stores arbitrary JSON that agents can use for structured payloads. The field is opaque to the API (stored and returned as-is).

**CLI usage:**
```bash
vtit-agent-coding task create --title "Fix auth bug" --input '{"repo":"project-a","file":"auth.ts","symptom":"401 on valid tokens"}'
```

**API:** `input` appears as an optional field in `POST /api/tasks` and `PATCH /api/tasks/:id` request/response bodies. The API accepts a JSON object (not a pre-serialized string) and the server serializes it to TEXT for D1 storage, deserializes on read. Type in API: `object | null`. The API validates that `input` is valid JSON on write — returns 400 if malformed.

**Web UI:** Task detail view shows `input` as a collapsible JSON block (read-only).

### 2. Task Dependency Chains (`depends_on` field)

An optional JSON array of task IDs. A task with unmet dependencies is **blocked**.

**Key rules:**
- "Blocked" is a **computed UI-only flag**, not a column or stored status. The task remains in whatever column it's in. The UI overlays a "blocked" badge.
- `/claim` returns **409 Conflict** if any dependency task is not in the "Done" column.
- Blocked status is computed dynamically on read: check if all `depends_on` task IDs are in a "Done" column.
- **Circular dependency prevention:** When setting task A's `depends_on` to include task B, perform DFS starting from B, following `depends_on` edges. Reject (400) if A is reachable from B (which would create a cycle). O(n) worst case where n = number of tasks with dependencies. At personal scale (~hundreds of tasks), this is trivial.
- **Dependency target deletion:** Deleting a task that is referenced in another task's `depends_on` auto-removes the deleted ID from all dependents' `depends_on` arrays. Implementation: use `json_each()` to find dependents + batch UPDATE. Acceptable at personal scale.

**CLI usage:**
```bash
vtit-agent-coding task create --title "Update project-a" --depends-on abc123,def456
vtit-agent-coding task list --status todo  # Shows [BLOCKED] badge on blocked tasks
```

**API:** `depends_on` appears as an optional field in `POST /api/tasks` and `PATCH /api/tasks/:id`. Type: `string[] | null` (array of task ID strings or null). Storage: server serializes to JSON TEXT for D1, deserializes on read.

### 3. Smart CLI Defaults

**Precedence (highest to lowest):**
1. Explicit `--project` flag
2. `.vtit-agent-coding.json` in repo root (if found by walking up from cwd)
3. `basename $(git rev-parse --show-toplevel)` (if in a git repo)
4. Required — CLI errors if no project can be determined and the command needs one

**`.vtit-agent-coding.json` shape:**
```json
{
  "project": "project-a",
  "board": "main-board-id"
}
```

**Fallback behavior:** If not in a git repo and no `.vtit-agent-coding.json`, the `--project` flag is required. CLI prints a helpful error: `Could not auto-detect project. Use --project <name> or create .vtit-agent-coding.json`.

### 4. GitHub PR Status Integration

When a task has `pr_url` set, the API fetches PR status from GitHub.

**Implementation:**
- **Endpoint:** `GET https://api.github.com/repos/:owner/:repo/pulls/:number` (parse owner/repo/number from `pr_url`)
- **Data fetched:** `state` (open/closed), `merged` (boolean), `mergeable_state`
- **Display states:** `Open`, `Merged`, `Closed` — shown as a colored badge on the task card
- **Caching:** Store fetched status in a `pr_status_cache` D1 table with columns `(pr_url TEXT PK, status TEXT, fetched_at TEXT)`. TTL: 60 seconds when `GITHUB_TOKEN` is set (authenticated: 5,000 req/hr limit). If `GITHUB_TOKEN` is not set, TTL increases to 5 minutes (unauthenticated: 60 req/hr limit). Cache refresh uses stale-while-revalidate: return cached status immediately, trigger background refresh via `waitUntil()` on the CF worker context. This avoids blocking the response on GitHub API latency. Refresh triggered only on individual task reads (`GET /api/tasks/:id`), not on list endpoints. List endpoints return cached/stale status or null.
- **GitHub token:** Stored as `GITHUB_TOKEN` Cloudflare env var. **Known limitation:** single GitHub account — PRs in orgs the token can't access show status `unknown` (best-effort, not an error).
- **Optional:** If `GITHUB_TOKEN` is not set, PR status feature still works but with longer cache TTL and lower rate limit. If GitHub API is completely unreachable, cards show `pr_url` as a plain link with no status badge.

### 5. Task Duration Display

Duration is computed from `task_logs` timestamps, not a separate field.

**Computation:** `duration = (first log with action='completed').created_at - (first log with action='claimed').created_at`. This measures agent working time, not wall-clock time from task creation. If no `claimed` log exists (task completed without being claimed), fall back to `created` log.

**Display:** Task cards in "Done" column show "Completed in X min" (or "X hr Y min" for longer durations).

**No separate analytics endpoint in v1.** Duration display on individual cards is sufficient. A `GET /api/stats` endpoint is deferred — there's no data to analyze until the tool has real usage.

### 6. First-Run Onboarding Flow

Triggered when the web UI detects zero boards exist (`GET /api/boards` returns empty array).

**3 steps:**
1. **Create board** — name input (default: "My Board"), creates board with default columns (Todo, In Progress, Done)
2. **Create first task** — title input with project tag, creates a sample task in the Todo column
3. **Copy CLI config** — displays the API key generated in step 0 with a copy button + the CLI config command: `vtit-agent-coding config set api-url <url> && vtit-agent-coding config set api-key <key>`

**Relationship to SETUP_TOKEN:** The onboarding flow wraps the `SETUP_TOKEN` bootstrap as **Step 0** (before the 3 steps above). On first visit (zero API keys exist), the web UI shows a "Setup" screen where the user pastes their `SETUP_TOKEN` (which they set as a CF env var during deployment). The UI calls `POST /api/auth/setup` with this token to create the first API key. This key is used for all subsequent API calls in the onboarding flow. Step 3 re-displays this key for CLI configuration — it does not generate a second key. The `SETUP_TOKEN` is never stored client-side — it's used once and discarded.

### 7. Keyboard Shortcuts

| Key | Action |
|-----|--------|
| `n` | Open new task dialog |
| `/` | Focus search/filter input |
| `←` `→` | Navigate between columns |
| `↑` `↓` | Navigate between cards within a column |
| `Enter` | Open selected card detail |
| `Esc` | Close dialog/detail view |
| `1`-`9` | Move selected card to column N (left-to-right by position; no-op if column N doesn't exist) |
| `?` | Toggle keyboard shortcut help overlay |

Implemented via a custom React hook (`useKeyboardShortcuts`). Shortcuts are disabled when a text input is focused.

### 8. Dark Mode

- CSS custom properties for all colors (one token set, two value sets)
- Three modes: Light / Dark / System (follows `prefers-color-scheme`)
- Toggle in settings page, persisted in `localStorage`
- Default: System

## Updated Data Model

```
tasks (additions to design doc schema)
  input       TEXT              -- optional freeform JSON payload for agents
  depends_on  TEXT              -- JSON array of task IDs this task depends on

pr_status_cache (new table)
  pr_url      TEXT PRIMARY KEY  -- the PR URL being tracked
  status      TEXT NOT NULL     -- open | merged | closed | unknown
  fetched_at  TEXT NOT NULL     -- ISO 8601 timestamp of last fetch
```

## Updated API

Existing endpoints gain new fields:
- `POST /api/tasks`: accepts optional `input` (object|null) and `depends_on` (string[]|null). Server serializes `input` to TEXT for D1.
- `PATCH /api/tasks/:id`: accepts optional `input` (object|null) and `depends_on` (string[]|null)
- `GET /api/tasks/:id`: returns `input` (object|null), `depends_on` (string[]|null), computed `blocked` (boolean), `pr_status` (string|null), `duration_minutes` (number|null)
- `POST /api/tasks/:id/claim`: returns 409 if task has unmet dependencies

No new endpoints in v1 beyond the design doc. `GET /api/stats` deferred to post-launch.

## Updated Architecture Notes

- CSS custom properties for theming (light/dark/system)
- `useKeyboardShortcuts` React hook for keyboard navigation
- Onboarding component triggered on empty board state (zero boards)
- `GITHUB_TOKEN` CF env var for PR status (optional — feature degrades gracefully if unset)
- `pr_status_cache` D1 table for GitHub API response caching (60s TTL authenticated / 5min unauthenticated)
- PR status refresh: returns stale cache immediately, triggers background refresh via `waitUntil()` on CF worker context
- CLI git detection precedence: `--project` flag > `.vtit-agent-coding.json` > git basename > error

## Migration Strategy

This is a greenfield project — the initial D1 migration includes all fields from both the base design doc and this CEO plan. No ALTER TABLE needed. The `input`, `depends_on` columns on `tasks` and the `pr_status_cache` table are part of the initial schema. A single migration file (`0001_initial.sql`) covers everything.

## Additional Scope (added during review sections)

### 9. Stale Claim Detection
Configurable timeout (default: 2 hours). Claimed tasks that are not completed within the timeout are automatically released back to "Todo" column. A `timed_out` action is logged in task_logs. Implementation: check on every `GET /api/tasks` call — if `claimed` log exists and `completed` log does not, and `now - claimed_at > timeout`, auto-release. No separate cron needed.

### 10. Pagination on List Endpoints
Cursor-based pagination on `GET /api/tasks` and `GET /api/boards`. Query params: `after=<id>&limit=20` (default limit: 50, max: 100). Response includes `next_cursor` field (null if no more results). CLI `task list` paginates automatically (fetches all pages by default, `--limit N` to cap).

### 11. Task Archiving
Add `archived_at TEXT` column to tasks. `PATCH /api/tasks/:id` with `{ archived: true }` sets `archived_at` to current timestamp. Archived tasks excluded from list/board endpoints by default. `?include_archived=true` query param to include them. CLI: `vtit-agent-coding task archive <id>` and `vtit-agent-coding task list --archived`.

### 12. Agent Skill for CLI Usage
Create an installable skill (under `packages/skill/` in the monorepo) that teaches agents the vtit-agent-coding CLI workflow. The skill is installed per-user (not per-repo) and is available across all projects. Includes: CLI command reference, workflow (list → claim → log → complete), example usage, error handling, and best practices. Published alongside the CLI npm package.

The skill must explicitly document that agents can **create tasks** — not just execute them. Key use case: when an agent is working on a task and discovers a dependency gap or subtask that needs separate work, it should create a new task via `task create` and log the context on the original task. This is a foundational capability for autonomous agent collaboration across projects.

## Foundational Decisions (resolved during review)

| Decision | Choice | Rationale |
|----------|--------|-----------|
| CSS approach | Tailwind + shadcn/ui | Fastest to polished UI. Built-in dark mode. Accessible components. |
| CLI framework | Commander.js | Simple, stable, good TS support. No overhead of heavier frameworks. |
| Dev setup | wrangler pages dev + Vite proxy | Matches production topology. Single terminal. |
| Web UI auth | localStorage Bearer token | Simplest for personal tool. API key entered once during onboarding. |
| Poll interval | 30 seconds | Sufficient for "check on agent progress." Halves API load vs 10s. |
| Agent instructions | Installable skill (not CLAUDE.md) | Portable across repos. The whole point is cross-project coordination. |

## Eng Review Decisions (2026-03-20)

| Decision | Choice | Rationale |
|----------|--------|-----------|
| v1 Scope | Reduced to 6 items | Defer: dependency chains, PR status, keyboard shortcuts, stale claims, pagination, archiving |
| API routing | Single `[[path]].ts` catch-all with Hono | Simplest. Under 1MB CF limit. Split only if needed. |
| Data access | Thin repo layer (taskRepo.ts, boardRepo.ts) | DRY SQL, testable |
| Auth bootstrap | `wrangler d1 execute` (not SETUP_TOKEN endpoint) | Simpler code, no dormant secret in CF env |
| Error handling | Hono `onError` + `HTTPException` | Centralized error envelope |
| Shared types | Proper workspace package with build step | Works with CF bundler + npm publish |
| Skill location | `packages/skill/` in pnpm workspace | Version-controlled with CLI, install script copies to `~/.claude/skills/` |
| Claim atomicity | `db.batch()` for atomic claim | Prevents race condition on concurrent /claim |
| Agent identity | API key = Machine (not Agent). Leader identity is created explicitly, then reused by runtime. | One key per computer with stable per-runtime agent identity. |

### Identity & Auth Model (revised)

The original design had one API key per agent. Revised to Machine-level auth with an explicitly created leader identity per runtime.

**API key = Machine.** One key per computer. Configured once via `vtit-agent-coding config set api-key`. All agents on that machine share the same key.

**Leader identity = created explicitly once per runtime.** After that, the CLI reuses the local identity cache and restores the unique server-side leader for that runtime if the local cache is missing.

```
api_keys (represents a Machine)
  id          TEXT PRIMARY KEY
  key_hash    TEXT NOT NULL
  name        TEXT              -- machine name, e.g. "saltbo-macbook"
  created_at  TEXT NOT NULL

agents (new table, auto-populated)
  id          TEXT PRIMARY KEY  -- nanoid
  machine_id  TEXT NOT NULL     -- → api_keys.id
  name        TEXT NOT NULL     -- auto-generated, e.g. "claude-code-a1b2"
  role_id     TEXT              -- null in v1, → roles.id in v2
  created_at  TEXT NOT NULL

tasks (revised references)
  assigned_to TEXT              -- → agents.id (was: API key name)
  created_by  TEXT              -- → agents.id or "human"
```

**Claim flow:**
1. Agent CLI calls `POST /api/tasks/:id/claim` with Bearer token + optional `agent_name`
2. Server authenticates Machine via API key
3. Server finds or creates agent record (machine_id + name)
4. Atomic claim via `db.batch()`
5. Returns task + agent info

**Agent name generation:** CLI auto-generates from process info (e.g. `claude-code-{short-random}`). User can override with `--agent-name`. Same agent name on same machine reuses the existing agent record.

## Design Review Decisions (2026-03-20)

### Information Architecture

Screen map:
```
APP SHELL
├── HEADER: Logo/Name | Board Selector | Filter Bar | Settings icon
├── BOARD VIEW (default screen)
│   ├── Columns: Todo | In Progress | Done (with task counts)
│   ├── Cards within columns (sorted by position)
│   └── "+ Task" inline input at bottom of each column
├── TASK DETAIL (slide-out panel from right, ~50% width on desktop)
│   ├── Title (editable), Status badge, Project tag, Priority badge
│   ├── Assigned agent, Duration ("17 min")
│   ├── Description (editable)
│   ├── Input (collapsible JSON block, read-only)
│   ├── Activity Log (timeline)
│   └── Result + PR link
├── SETTINGS (separate route /settings)
│   ├── API Keys (list + create + revoke)
│   ├── Theme toggle (light/dark/system)
│   └── Board management
└── ONBOARDING (replaces board view when zero boards exist)
    ├── Step 1: Name your board (default: "My Board")
    ├── Step 2: Create first task (pre-filled example)
    └── Step 3: Copy CLI config (API key + commands)
```

Visual hierarchy per card:
1. Title — what is this task?
2. Project tag + priority badge — context
3. Assigned agent — who's on it?
4. Duration or last log entry — status

Key decisions:
- **Task detail:** Slide-out panel (board stays visible, agent monitoring context preserved)
- **Task creation:** Inline in column — click "+ Task", type title, Enter. Details via slide-out.
- **Filters:** Always-visible filter bar below header (dropdowns/chips for project, label, priority)

### Interaction States

| Feature | Loading | Empty | Error | Success | Partial |
|---------|---------|-------|-------|---------|---------|
| Board view | Skeleton columns + ghost cards | Onboarding flow | "Can't reach server" banner + retry | Board renders | Some columns loaded |
| Column (empty) | — | "No tasks yet" + "+ Task" button | — | Cards listed | — |
| Task detail | Skeleton panel | — | "Task not found" + back | Detail renders | — |
| Task creation | Spinner on submit | — | "Failed to create" toast + keep form | Card appears with highlight | — |
| Filter results | — | "No tasks match" + clear filters | — | Filtered view | — |
| Activity log | Skeleton lines | "No activity yet" | "Can't load logs" | Timeline renders | — |
| API keys | Skeleton list | — | "Failed to load" | Key list | — |
| Polling (30s) | Invisible (no spinner) | — | Silent retry (banner after 3+ fails) | Seamless update | Stale until next success |
| Auth failure | — | — | "Session expired" banner + re-auth link | — | — |

- Loading: Skeleton UI (not spinners). Maintains spatial layout.
- Task creation success: Brief highlight animation on new card (no toast for frequent action).
- Errors: Toast for write failures, banner for persistent connection/auth issues.

### User Journey & Emotional Arc

- Card column transitions animate on poll refresh (CSS ~300ms slide). Makes agent work feel "alive."
- Agent-claimed cards get accent border + agent icon badge — agents are visually first-class.
- "In Progress" column header pulses subtly when a card was updated in last 5 minutes.
- Agent log entries use monospace font + slightly different background — feels like agent "output."

### Design Tokens

See **DESIGN.md** for the complete design system. Key decisions:
- Font: Geist + Geist Mono (not Inter — Geist has more character)
- Accent: Cyan #22D3EE (not blue — every dev tool uses blue)
- Aesthetic: Industrial/Utilitarian
- Agent glow: cyan radial box-shadow on agent-active cards

### Responsive Behavior

| Breakpoint | Layout | Notes |
|------------|--------|-------|
| Desktop (≥1024px) | 3 columns side-by-side, slide-out panel 50% width | Default |
| Tablet (768-1023px) | 3 columns (narrower cards), panel 70% width | Filter bar collapses to icon |
| Mobile (<768px) | Tab switcher: Todo \| In Progress \| Done | One column at a time. Detail is full-screen. Filter via bottom sheet. |

### Accessibility

- Touch targets: 44px minimum (WCAG 2.5.5)
- Color contrast: WCAG AA (4.5:1 text, 3:1 large text)
- Keyboard: Tab through cards, Enter to open detail, Esc to close
- ARIA: `<main>` for board, `<aside>` for panel, `role="list"` for columns
- Screen reader: Column headers announce name + count. Cards announce title + priority + project.
- Focus management: Panel traps focus, Esc returns to originating card.

### Deferred Design Decisions

- No drag-and-drop in v1 — move cards via detail panel dropdown. Agents are primary movers.
- Onboarding copy/tone finalized during implementation (warm, brief, "your AI workforce starts here").

## Capacity Note

Six scope items (reduced from 12 per eng review). With CC + gstack, total estimated v1 implementation: ~3-5 hours. Feasible in a single focused session.
