Documentation source
Command Center
The four-tab operator surface for AI transformation — Operations, My Role, Delegate, Transformation — backed by typed system entities (Task, Workstream, Role) and a unified data pipeline.
## Overview
The Command Center is the consolidated operator surface for visualizing AI-human work distribution, delegation pipelines, and transformation outcomes. It groups four purpose-built pages under `/command-center/` with a shared tab strip, all reading from the same typed entity graph:
- **Operations** (`/command-center/operations`) — Sankey flow of work across workstreams, roles, and delegation states
- **My Role** (`/command-center/my-role`) — the current user's workstreams, delegation queue, and readiness trend
- **Delegate** (`/command-center/delegate`) — the delegation wizard + candidate queue, scored by readiness
- **Transformation** (`/command-center/transformation`) — before/after snapshots, KPI strips, milestone timelines for shareable reporting
This feature unifies four previously-separate top-level routes into a cohesive workspace backed by **three system entity types**, **named platform data sources**, and a **typed query layer** — replacing the previous mix of `public.actions`-metadata parsing and tenant-local entity types.
## Key Concepts
### System entity types (platform-wide, `tenant_id IS NULL`)
Registered in `features/entities/system-types/catalog.ts`:
| Slug | Purpose | Core fields |
| ------------ | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `task` | User-facing work items | `description`, `status`, `delegation_state`, `task_type`, `work_category`, measurement fields, `readiness_score`, `guardrails`, `reversible_until`, `trial` |
| `workstream` | Organizational unit / swimlane | `name`, `status`, `health`, `target_agent_share`, `loaded_hourly_cost`, `coverage_note`, `owner_user_id` |
| `role` | Human capacity unit | `name`, `seniority`, `weekly_capacity_hours`, `description` |
Task relations point at Workstream (`in_workstream`, one) and Role (`owned_by_role`, one). Per [ADR-0004](/docs/adrs/0004-task-entity-action-registry-split), Task entities are the product surface; `public.actions` (the action registry) is a parallel primitive and **no Action entity type is registered**.
### Delegation states
The four-state lifecycle that drives the Sankey flow and the delegation wizard:
| State | Meaning |
| ------------------ | ----------------------------------------------------------------- |
| `human_only` | No AI participation — baseline |
| `trialing` | AI is running alongside a human reviewer — gathering confidence |
| `agent_supervised` | AI runs first-draft, human approves — hours saved but still gated |
| `agent_autonomous` | AI operates unattended within guardrails — full productivity gain |
### Work category — the AI-transition lens
Orthogonal to `task_type` (which is the issue-tracker classification: `task / bug / feature / milestone / question`), `work_category` segments work by its AI-transition profile:
- `strategic` — high-judgment, low-volume (board prep, market theses)
- `operational` — recurring, measurable (pipeline reviews, monthly closes)
- `administrative` — transactional, high-volume (invoice routing, CRM hygiene)
### Measurement — flat fields, one contract
All measurement fields live flat on the Task entity's `content` (not nested) so the standard entity form builder, filter UI, and CSV I/O work without jsonSchema overrides:
- `measurement_cadence` (`daily / weekly / monthly / ad_hoc`)
- `measurement_volume_per_period` (nonneg)
- `measurement_human_time_per_run` (hours, nonneg)
- `measurement_agent_time_per_run` (hours, nonneg)
- `measurement_annual_dollar_value` (currency, nonneg)
A thin adapter — `extractMeasurement()` in `features/custom/server/work-model/measurement-adapter.ts` — bridges flat fields to the nested `TaskMeasurement` shape that `computeAnnualDollarValue()` / `computeWeeklyHumanHours()` / `computeWeeklyAgentHours()` in `features/actions/lib/measurement.ts` expect. The arithmetic stays DRY; only the wire format translates.
### Readiness score — 0..100 scale
Normalized for UI readability. Computed via `computeReadinessScore()` in `features/custom/server/work-model/readiness.ts` from `work_category` + `measurement_cadence` + `measurement_volume_per_period` + `measurement_human_time_per_run`. The Phase 5 migration converts any legacy 0..1 values to the new scale on write.
## How It Works
### Data flow
```
User opens /command-center/operations
→ server component creates Command Center named data sources
→ resolves the workstreams + roles EntityDataSource contracts
→ listWorkstreamsWithCoverage() reads entities[type=workstream]
→ joins task relations for coverage metrics
→ projects typed WorkstreamWithCoverage[] + aggregate counts
→ passes as props to client Sankey component
```
Every Command Center page follows the same pattern: server-component SSR creates the named sources from `features/custom/server/work-model/data-sources.ts`, passes validated `EntityDataSource` contracts into `features/custom/server/work-model/*.ts`, then passes typed props to the client block. No client-side work-model fetches — the data is all SSR-hydrated.
### Route structure
```
app/(app)/command-center/
├── layout.tsx # wraps all four tabs with <CommandCenterTabs />
├── operations/
│ ├── page.tsx
│ └── operations-client.tsx
├── my-role/
│ ├── page.tsx
│ └── my-role-content.tsx
├── delegate/
│ ├── page.tsx
│ ├── delegate-client.tsx
│ └── loading.tsx
└── transformation/
├── page.tsx
├── sections.tsx
├── data.ts
└── print-styles.tsx
```
The tab strip (`features/custom/components/command-center/command-center-tabs.tsx`) uses `usePathname()` to handle both bare paths and tenant-scoped URLs (`/t/<slug>/command-center/...`). The navigation config (`features/navigation/defaults.ts`) exposes Command Center as an expanded sidebar group (`defaultOpen: true`, `collapsible: true`) so the four tabs appear together.
### Query layer
All work-model queries live in `features/custom/server/work-model/`:
| File | Exports |
| ----------------------------- | ----------------------------------------------------------------------------------------------------- |
| `data-sources.ts` | `createCommandCenterDataSources()`, `commandCenterEntitySource()`, canonical source IDs |
| `task-queries.ts` | `DelegationCandidate`, `getDelegationQueue()` |
| `workstream-queries.ts` | `WorkstreamCoverage`, `WorkstreamWithCoverage`, `getWorkstreamsWithCoverage()` |
| `role-queries.ts` | `RoleRecord`, `RoleInventory`, `getRoleInventory()`, `listRolesForUser()`, `getRoleDelegationShare()` |
| `transformation-queries.ts` | `TransformationSnapshot`, `getTransformationSnapshot()` |
| `overnight-queries.ts` | `OvernightSummary`, `getOvernightSummary()` |
| `promote.ts` | `promoteTask()` — advances `delegation_state` and logs the promotion to a session |
| `readiness.ts` | `computeReadinessScore()`, `buildReadinessContext()` |
| `agent-share.ts` | `TaskDelegationState`, `DELEGATION_STATE_LABELS` |
| `measurement-adapter.ts` | `extractMeasurement()`, `extractDelegationState()` — flat→nested bridge |
| `view-data-source-presets.ts` | Registers the canonical sources as reusable view-editor source presets |
The canonical Command Center sources are all entity-native: `task`, `workstream`, `role`, `milestone`, and `contest`. Task-oriented queries read from `entities` with `entity_type_slug = 'task'` (plus joins to `entity_relations` for workstream + role). `public.actions` metadata is no longer parsed at read time.
Those same source definitions are also registered in the workspace-editor
library as Command Center source presets. Standalone views can import `tasks`,
`delegationQueue`, `workstreams`, `roles`, `milestones`, and `contests` from the
Library → Sources tab without copying route-local loader code into platform
views.
## API Reference
### `getDelegationQueue(tenantId, options?) → DelegationCandidate[]`
Returns Task entities ranked by readiness score, filtered to `delegation_state ∈ {human_only, trialing}` by default or by the supplied task `EntityDataSource`. Each candidate has: `taskEntityId`, `description`, `work_category`, `readiness_score` (0..100), `delegation_state`, and `measurement` (nested `TaskMeasurement` if all three core fields are present).
### `promoteTask(supabase, { taskEntityId, targetState }) → { taskEntityId, fromState, toState }`
Advances a Task entity's `delegation_state` forward (`human_only → trialing → agent_supervised → agent_autonomous`). Writes the state change to `entity.content.delegation_state` and logs a session event. Idempotent on `fromState === toState`.
### `getWorkstreamsWithCoverage(tenantId, options?) → WorkstreamWithCoverage[]`
Returns Workstream entities with rolled-up coverage metrics (weekly human hours, weekly agent hours, target agent share, actual agent share). Accepts a workstream `EntityDataSource` and joins `entity_relations` for `in_workstream` task references.
### `getTransformationSnapshot(tenantId) → TransformationSnapshot`
The full Transformation page payload — before/after human hours per workstream, annual dollar value delta, delegation-state distribution, milestone timeline. Used by `transformation-kpi-strip`, `transformation-before-after`, `transformation-human-agent-chart`, `transformation-milestone-timeline`, `transformation-workstream-rollup`, `transformation-narrative-quote`.
## Migration
The Phase 5 migration (`features/inngest/functions/migrate-todos-to-task-entities.ts`) carries the full work-model metadata from `public.actions.metadata` to the Task entity's `content`:
- Flat measurement fields map 1:1 from nested `TaskMeasurement`
- `readiness_score` converts 0..1 → 0..100 when detected as legacy scale
- Legacy `content.category` lifts to `task_type`
- Measurement helpers are re-applied via `extractMeasurement()` when queried
Index support: `supabase/migrations/20260423000000_task_entity_work_model_indexes.sql` adds 4 partial expression indexes on `entities.content ->> 'field'` scoped to `entity_type_slug='task'` for fast filtering on `delegation_state`, `work_category`, `measurement_cadence`, and `readiness_score`.
## For Agents
Agents can read and write Task entities via the standard entity tools — no command-center-specific tools required:
- `searchEntities({ typeSlug: "task", filter: { content: { delegation_state: "trialing" } } })`
- `updateEntity(id, { content: { delegation_state: "agent_supervised", readiness_score: 85 } })`
- `createRelation({ fromEntityId, toEntityId, relationshipType: "in_workstream" })`
Heartbeat agents that promote tasks across delegation states should call `promoteTask()` from `features/custom/server/work-model/promote.ts` rather than writing `content.delegation_state` directly — `promoteTask` logs a session event for the pulse + audit trail.
## Design Decisions
**Why a unified `/command-center/` namespace?** Previously, the four pages lived at top-level (`/operations`, `/my-role`, `/delegate`, `/transformation`) with no visible grouping in the sidebar. Users didn't know they were part of the same workflow. Nesting under `/command-center/` with a persistent tab strip makes the pipeline explicit: see the flow (Operations), find your work (My Role), promote candidates (Delegate), review outcomes (Transformation).
**Why system entity types for Workstream and Role?** Both existed as tenant-local entity types from the PR #829 seed. Making them system types gives platform TypeScript modules (like the work-model queries) a stable type import without requiring every tenant to register their own. The `entity-type-access.ts` resolver already prefers tenant-local over global, so existing seeded entities keep their `entity_type_id` intact — the system types are additive, not destructive.
**Why flat measurement fields instead of nested?** Nested `measurement: { cadence, volume_per_period, ... }` requires custom form builders, filter UIs, and CSV mappers to understand the jsonSchema nesting. Flat fields work with the stock entity surface for free — and the arithmetic helpers still consume the nested shape via a thin adapter, keeping the math DRY.
**Why 0..100 readiness and not 0..1?** Users read "85%" more naturally than "0.85". The data migration converts legacy values; all new code uses the 0..100 scale.
**Why no Action entity type?** Per ADR-0004, `public.actions` is the action registry (a parallel primitive to entities) — it stores trigger configs, cron schedules, and output contracts. Representing both the work item and the action config as entities would conflate two semantically different primitives. Task entities are for humans to see and manage; `public.actions` rows are internal automation infrastructure.
## Related Modules
- [Tasks](/docs/features/tasks) — the action registry primitive and its trigger/dispatch system
- [Sessions](/docs/features/sessions) — execution records for promoted tasks
- [Entity System](/docs/features/entity-system) — how system entity types register and resolve
- [Block System](/docs/features/block-system) — the 12 block components this feature hosts
- [ADR-0004](/docs/adrs/0004-task-entity-action-registry-split) — why Task entity and Action registry are separate