Content
# Modular Agents Control Plane
A local-first control plane for AI coding-agent harnesses. It reads agent transcripts off your own
disk, normalizes them into canonical domain types, and gives you three ways to interrogate them:
a dashboard, a CLI, and an MCP server — all sharing the same analytics code.
It is read-only. Nothing is written back to your transcripts, nothing leaves the machine, and there
is no seeded or demo data anywhere in the repo: when a source is unconfigured or empty, the UI
renders an empty state rather than inventing numbers.

Pointed at a real `~/.claude/projects` tree, the numbers above are all derived at request time from
JSONL on disk. The `Live Activity` panel is a genuine SSE endpoint backed by `fs.watch` — those
`APPENDED` rows appear as sessions are written.
Session detail is a full turn-by-turn transcript replay with per-turn token and cost attribution:

Costs are estimated per model from a point-in-time pricing table, with prompt-cache savings broken
out separately:

## Run it
> **Use the production build.** At real data scale (900+ sessions, 1600+ JSONL files) `next dev` is
> not usable — cold route compiles take 5–7 minutes and the dev server has been OOM-killed
> outright. `next build` + `next start` serves the same routes in milliseconds.
```bash
pnpm install
pnpm build
pnpm --filter @control-plane/web start # http://127.0.0.1:3000
```
Requires Node 22+ and pnpm 10.
### Where the data comes from
| What | Env var | Fallback |
|---|---|---|
| Agent transcripts | `CLAUDE_CONTROL_PLANE_DATA_ROOT` | `~/.claude/projects` |
| Skill manifests (`SKILL.md`) | `CONTROL_PLANE_SKILLS_ROOTS` | `~/.claude/skills` |
| Webhook subscriptions | `CLAUDE_CONTROL_PLANE_WEBHOOKS_FILE` | unconfigured |
| Kanban tickets | `CLAUDE_CONTROL_PLANE_TICKETS_FILE` | unconfigured |
If neither the env var nor the fallback resolves, the module says so instead of showing zeros.
## Design: capability-based, not vendor-branched
Everything the UI renders is a canonical type from `packages/core` — `SessionDescriptor`,
`SessionTurn`, `ToolCall`, `CostBreakdown`, `SkillManifest`, and friends. Vendor-shaped data never
reaches a component.
Adapters sit behind capability contracts in `packages/core/src/contracts/`
(`session-ingest`, `session-analytics`, `replay`, `pricing`, `mcp`, `channel`, `runtime-control`)
and declare which of the capabilities in `packages/core/src/capabilities.ts` they implement. Modules
check `descriptor.runtime` and capability sets — never a vendor name. That rule is
[ADR-0002](docs/architecture/decisions/0002-agent-agnostic-core.md).
**One adapter currently ships**: `packages/adapter-claude-code`, which reads Claude Code JSONL. The
contract layer is what makes a second one additive rather than invasive — but a second one does not
exist in this repo today, and the dashboard is only useful against Claude Code transcripts.
## Modules
| Module | Route | Status |
|---|---|---|
| Control Plane | `/` | Fleet metrics, agent runtime inventory, live event stream |
| Sessions | `/sessions` | Session list, search, export; sub-routes for overview, projects, costs, tools, activity |
| Session detail | `/sessions/[id]` | Turn-by-turn replay, per-turn tokens/cost, tool ranking, compaction markers |
| Agents | `/agents` | Runtime inventory grouped from project directories, per-agent session lists |
| Skills | `/skills` | `SKILL.md` registry, invocation telemetry, size-weighted injection cost, efficacy vs baseline |
| Kanban | `/kanban` | Read-only board over a local tickets file |
| Webhooks | `/webhooks` | Live GitHub receiver at `/api/webhooks/github` (header + HMAC signature validation), integration workbench, dry-run routing |
| Token Optimizer | `/token-optimizer` | Optimization tool fleet with per-session attribution |
| Replay | `/replay` | Session picker into the replay trace |
| MCPs | `/mcps` | Deferred — placeholder, no adapter wired |
| Channels | `/channels` | Deferred — placeholder, no adapter wired |
The sidebar dot and the header pill both render each module's `phase` from
[`apps/web/lib/modules.ts`](apps/web/lib/modules.ts) (`active` / `deferred`). The health pills on
the module pages themselves are derived from the actual adapter result, so a page shows `Degraded`
when its source is not configured.
Not built: webhook CRUD persistence, live process control, multi-user auth, persistent storage
backends. `packages/storage` and `packages/events` exist as interfaces plus in-memory
implementations and are not yet imported by the UI.
## `cp` — the CLI
The same analytics folds, without the browser. JSON by default, `--pretty` for humans.
```bash
pnpm build # or: pnpm --filter @control-plane/cli build
pnpm cp health --pretty
```
```
Control plane health
Data root: /Users/you/.claude/projects
Origin: default
Sessions: 949
Skills: 37
```
| Command | What it does |
|---|---|
| `cp health` | Data-root resolution, session count, skill count |
| `cp audit` | One-shot holistic report: cost, waste, skills, per-project |
| `cp sessions top` | Top sessions by tokens, cost, or turns |
| `cp sessions show <id>` | One session's usage summary (`--timeline` adds per-turn rollup + skill attribution) |
| `cp sessions waste` | Sessions ranked by waste score with the triggering flags |
| `cp skills top` | Top skills by invocations, size, bytes/tokens injected |
| `cp skills usage` | Full usage report with totals |
| `cp skills efficacy` | Per-skill outcome delta vs the all-sessions baseline |
| `cp skills housekeep` | Dead-weight skills; dry-run by default, `--apply` archives |
| `cp agents list` | Agents grouped from project directories |
`pnpm cp <command>` runs `node packages/cli/dist/cli.js`. Run `pnpm link --global` inside
`packages/cli` if you want `cp` on your `$PATH`.
## MCP server
`packages/mcp-server` ships a stdio MCP server exposing **9 read-only tools** over the same
analytics:
`control_plane_health` · `control_plane_audit` · `sessions_top` · `sessions_show` ·
`sessions_waste` · `skills_top` · `skills_usage` · `skills_efficacy` · `agents_list`
```bash
pnpm build # or: pnpm --filter @control-plane/mcp-server build
pnpm mcp-server # stdio; normally launched by an MCP client
```
It is registered for this repo in [`.mcp.json`](.mcp.json), so Claude Code picks it up
automatically. No tool ever throws — failures come back as `{ ok: false, reason, message? }`.
## Layout
```
apps/web Next.js App Router dashboard + local API routes
packages/core Canonical domain types, capability contracts, pricing table
packages/adapter-claude-code Claude Code JSONL -> canonical types; analytics + skills folds
packages/cli `cp` binary
packages/mcp-server stdio MCP server (9 tools)
packages/events Typed event bus + append-only log (interfaces, in-memory)
packages/storage Repository interfaces + in-memory implementation
packages/logger pino-backed structured logger
packages/testing Shared fixtures
e2e Playwright specs
```
## Development
```bash
pnpm typecheck # tsc across all workspaces
pnpm test # Vitest — 577 tests, 79 files
pnpm lint # Biome (errors) + ESLint (type-aware, React, a11y, imports, complexity)
pnpm build # workspace packages, then next build
pnpm test:e2e # Playwright (starts its own dev server)
```
`pnpm lint` builds the workspace packages first — ESLint resolves `@control-plane/*` imports from
the emitted `.d.ts` files, and skipping the build makes it report thousands of phantom type errors.
The Taskfile wraps these into tiered CI gates (`task verify`, `task ci:fast`, `task ci`,
`task ci:nightly`); GitHub Actions calls the same targets.
## Stack
TypeScript (ESM) · Node 22 · pnpm 10 workspaces · Next.js 15 App Router · React 19 · Tailwind 3 ·
Recharts · Vitest · Playwright · Biome + ESLint · pino
## Docs
- [Architecture Overview](docs/architecture/overview.md)
- [Adapter Contracts](docs/architecture/adapter-contracts.md)
- [Data Model](docs/architecture/data-model.md)
- [Security Notes](docs/architecture/security.md)
- [ADR log](docs/architecture/decisions/README.md)
- [Test Strategy](docs/testing/test-strategy.md)
- Per-module specs in [`docs/modules/`](docs/modules)
## License
MIT — see [LICENSE](LICENSE).
Connection Info
You Might Also Like
buddy
Your persistent AI coding companion — the /buddy rescue mission. A...
Vera
Local code search combining BM25, vector similarity, and cross-encoder...
agent-base
Agent Base is a source-level research project on coding agents. It compares...
mitmproxy-mcp
MCP Server that wraps mitmproxy and exposes it as a tool to any MCP client,...
nothumanallowed
NotHumanAllowed — AI Agent Tools, CLI, Documentation & MCP Integration
bouvet
Sandbox for Agents