Docs Case Studies

Architecture

The core design principle: dumb server, smart agents.

The server has zero AI. It makes no LLM calls, holds no API keys, and forms no opinions about your codebase. It’s a pure CRUD coordination layer with atomic task pickup. All planning, decomposition, and reasoning happens in the agents.

System layers

┌─────────────────────────────────────────────────────┐
│  Agents (any machine, any tool)                     │
│  Claude Code · Codex · Cursor · Copilot · scripts   │
└──┬──────────────────────────────────┬──────────────┘
   │                                  │
   │ cpk scan / cpk code              │ cpk task / cpk docs
   │ (offline — direct SQLite)        │ (HTTP to daemon)
   │                                  ▼
   │              ┌────────────────────────────────┐
   │              │  codepakt server (Hono)        │
   │              │  ├── REST API /api/* :41920    │
   │              │  └── Dashboard /   (same port) │
   │              └────────────┬───────────────────┘
   ▼                           ▼
┌──────────────────────────────────────────────────────┐
│  <project>/.codepakt/data.db  (SQLite, WAL mode)     │
│  tables: tasks, agents, events, docs,                │
│          symbols, imports                            │
└──────────────────────────────────────────────────────┘

Two execution paths reach the same local SQLite file:

  • Code intelligence (cpk scan, cpk code *): opens the DB directly via better-sqlite3. No HTTP, no daemon. Fast and offline-capable.
  • Task coordination (cpk task *, cpk board *, cpk agent *, cpk docs *): goes through the Hono server because atomic pickup needs BEGIN IMMEDIATE across concurrent agent processes.

Technology stack

LayerChoiceReason
HTTP frameworkHonoLightweight, fast, TypeScript-native
Databasebetter-sqlite3Synchronous API, WAL mode, zero config, embeds in npm package
CLICommander.jsMature, minimal, well-typed
ValidationZodRuntime type safety at API boundaries
Query layerTyped functions, no ORMSQL is readable; ORMs add abstraction without benefit here
Code parsingtree-sitter WASM (web-tree-sitter)Works with npx (no native compilation). Grammar WASMs for TS, TSX, JS, Python, Go ship in grammars/

Single npm package

Everything ships in one package: codepakt.

  • CLI binary (cpk)
  • HTTP server (Hono)
  • SQLite driver (better-sqlite3)
  • Dashboard HTML/JS (served from the same process)
  • tree-sitter WASM grammars for 5 languages (~6MB unpacked)

No docker. No separate database service. No config file beyond what cpk init creates. Install and run.

Code intelligence scanner

cpk scan parses every supported source file with tree-sitter WASM:

  1. Collect files — walk the project tree, respect .gitignore and .codepaktignore, filter by extension
  2. Load grammar — lazy-load the right WASM grammar per language (cached after first use)
  3. Parse — produce a concrete syntax tree from the source
  4. Extract — walk the CST to emit symbol and import records (functions, classes, interfaces, types, methods, exported vars, imports with resolved paths)
  5. Persist — replace project data in a single SQLite transaction

Incremental mode (cpk scan --incremental) uses git diff --cached to get staged files, then processes only those. The pre-commit hook runs this automatically, keeping the index fresh with millisecond updates per commit.

Atomic task pickup

Two agents calling cpk task pickup simultaneously must not receive the same task.

Solution: BEGIN IMMEDIATE transaction in SQLite.

BEGIN IMMEDIATE;

SELECT id FROM tasks
WHERE status = 'open'
  AND deps_met = 1
ORDER BY CASE priority WHEN 'P0' THEN 0 WHEN 'P1' THEN 1 ELSE 2 END, created_at ASC
LIMIT 1;

UPDATE tasks SET status = 'in-progress', assignee = :agent WHERE id = :task_id;

COMMIT;

BEGIN IMMEDIATE acquires a write lock at transaction start, before the SELECT. Any concurrent transaction attempting to start waits. The first writer wins; others retry. This is SQLite’s built-in serialization — no application-level locking needed.

This is the reason task coordination still needs the server: multiple CLI processes calling pickup simultaneously all need to coordinate through the same daemon for the lock to work. Code intelligence doesn’t need this because cpk scan runs from a single process (or git hook) and reads are inherently lock-free in WAL mode.

Audit trail

Every mutation (task creation, status change, pickup, completion) writes to an events table:

events
  id          TEXT PRIMARY KEY
  task_id     TEXT
  agent       TEXT
  action      TEXT  -- 'task_created', 'task_pickup', 'task_complete', etc.
  detail      TEXT  -- JSON payload
  created_at  TEXT

This is an append-only log. Nothing is ever deleted from events. It’s the source of truth for “what happened and when.”

Dependency resolution

When a task reaches review (agent calls cpk task done), the server runs a dependency check:

  1. Find all tasks whose depends_on includes the completed task’s ID
  2. For each, check whether all other dependencies are in review or done
  3. If yes, set deps_met = true and transition from backlog to open

Dependencies resolve on review, not on done. This means the pipeline keeps moving without waiting for human approval. The check runs synchronously inside the same transaction.

Database schema

TablePurpose
projectsProject metadata (id, name, created_at)
tasksTask records with status, priority, deps, verify, notes
agentsAgent records (auto-created on first interaction)
eventsAppend-only audit log
docsKnowledge base documents
symbolsCode intelligence: functions, classes, interfaces, methods, etc.
importsCode intelligence: file-to-file import relationships
metadataKey/value settings
schema_versionMigration tracking

Schema version is 3 (as of v0.2). Migrations in src/server/db/index.ts handle upgrades automatically when a DB is opened.

Dashboard delivery

The dashboard is a static HTML/JS application served from the Hono server at /. The JS makes fetch calls to /api/* — the same origin, no CORS needed. There is no separate deployment step.

What’s not in the server

  • No authentication or authorization (single-user, localhost)
  • No LLM calls
  • No file watching or filesystem access (scans are on-demand)
  • No webhooks or external integrations
  • No background job queue
  • No Postgres (planned for a later release)

Keeping the server simple is a deliberate choice. Complexity lives in the agents, where it can be reasoned about, tested, and replaced independently.