Operations overview (Command Center)
The overview answers what is in motion and who is waiting on whom. It is available by URL at /command-center and /o/[orgSlug]/command-center only after the platform commandCenter.enabled switch is enabled. It ships off at platform level. The org entitlement defaults on; an explicit org false override omits that org and returns 404 for its drill-down. No rollout or settings write is part of this change. Operations appears beside My Work in the account sidebar and navigation search; the org drill-down remains available from the overview.
GET /v1/me/command-center returns one snapshot across permitted memberships. GET /v1/orgs/{orgId}/command-center returns one org. Project visibility, category ACLs, token org allow-lists, API-key org scope and suspended orgs apply. With a connected event bus, project/category permission predicates are cached for five seconds per viewer and credential scope; snapshots and DM membership are never cached. A disconnected bus uses fresh permission resolution. Org membership, suspension and entitlement changes apply before any cache hit. Task-only callers with no visible projects receive 403 on drill-down. Runner and user-scoped keys are refused. Keys need command_center; user tokens need read and issues. Per-project item counts supply the system view even without epics or Coordinator lanes.
Lanes are Coordinator lane issues; features are epics with visible child progress. Waiting-on names come from current org membership (departed users read “Former member”). Persona assigned and touchedOpen counts overlap; last activity includes touched issues. Requests combine Coordinator requests and heartbeat records joined to readable follow-up tickets. Lists are capped (200 lanes, 100 other rows); truncated warns that not all rows are shown.
Each org carries an agents section while platform switch commandCenter.agentsPanel is on (default on; off omits the field). Every member sees the persona directory they already see (enabled, unretired personas with a live key): name, handle, last outbound agent-chat message, last request and a status derived from the newest of those two (active under 15 minutes, idle under 24 hours, silent at 24 hours or more, never with neither). Owners and admins also see disabled personas, active agent daemons (host label, harnesses, last heartbeat, same status rule) and the agent-chat backlog (pending queued and deadLettered message deliveries); for other roles those two fields are null. qaCommander reports whether the @qa-commander persona is present, on, and its status. The section costs a fixed three statements per snapshot for an admin (one for a member), whatever the persona count. It reads existing tables only; empty orgs show “No agents connected yet” with a link to a project's Agents page.
GET /v1/me/command-center/events streams identifier-only invalidations, ready and 15-second heartbeats. It coalesces org events for 500ms, re-authenticates through the read route before sending and refreshes time-derived values every 60 seconds. Null bus pings do not invalidate all orgs; issue_burst does. Limits: 3 streams/user, 500/instance, 30 connects/minute, 30-minute lifetime and 1MB queued output. Unavailable streams return 404 and polling takes over. Polling is 4 seconds foreground, 30 seconds hidden, error backoff up to 60 seconds; SSE retry is 60 seconds. Reconnect reloads the full snapshot. STALE retains data after max(2×poll interval,15s); Live tolerates 35 seconds between beats. Drill-down refreshes are at most once per 2 seconds. Metadata never includes message text; private DMs still require existing membership. Wider org-owner bot-DM visibility awaits a privacy ruling.
Follow-up heartbeats use org agent-memory keys followups.graph.lastSync.reconcile and .sync. Counts are explicitly dataTrust: untrusted; freshness uses the row's server updatedAt. No readable heartbeat means “not configured”, never zero. Foreign projects and summaries containing hidden category work are discarded. Heartbeat writers cap records by both 500 and the 16KiB memory API limit. A write failure exits the reconciler non-zero. The dark switch leaves the previous reconciler write effects unchanged. Dry runs write only the heartbeat, describing the current ledger rather than hypothetical transitions.
Data feeds and rollout (orchestrator only)
- Review, migrate on staging, enable the staging platform switch and run the fixture. No worker deploys or reads production.
- Canonical lane source: a JSON export of the workflow
lanes.pyrun.tools/command-center/lane-feed.mjsexportspublishLaneSnapshot; wire it to the bridge's existing API adapter with one org/project, a stablerunId, and stable laneids. It replays deterministic issue-operation ids and PATCHes mutable progress, worker and Coordinator fields.pm:laneknowledge and agent-chat assignments are projections of those same identities. The publisher does not install services or read local journals itself. - Refresh the deployed copy of
tools/followup-graphused by the reconciler timer (repo changes alone do not refresh that copy). Its org persona key must havecommand_center,agent_memoryand existing reconcile families plusorg:manage_agents. The sync producer useswriteFollowupHeartbeatwith sourcesync. Installation and copy refresh are outside this lane. - New nav entry and expanded bot-DM access remain owner decisions. Production enable is performed by the orchestrator after review.
The TypeScript SDK can call the documented routes after enable; the default served OpenAPI/SDK inventory remains dark. The all-features document includes the additive contracts. Feature card: draft only, no publish or user notification; title “Operations overview”, scope web/desktop, prerequisite platform enable, target /command-center, copy “See visible work, named blockers and bot activity across your organizations.” Screenshot and placement approval are required before publishing.
- API reference:
/v1/openapi.jsonafter enable; the all-features contract includes dark routes. - MCP tools:
get_command_centerreturns the same authorized snapshot when the platform switch and org entitlement are enabled; see the tool reference. - Runbook: operations and tests.
Local lane publisher (operator installs the schedule, no timer is installed by this feature):
node tools/command-center/dash-lanefeed.mjs \
--org ORG_ID --project COORDINATOR_PROJECT_ID \
--snapshot SESSION_SCRATCH/lanes-export.json \
--api-url API_ORIGIN --key-file KEY_FILE
The canonical export is { "runId": "session-id", "lanes": [{ "id": "stable-lane-id", "title": "Dashboard", "status": "in_progress", "branch": "claude/dashboard", "head_sha": "40-hex", "harness": "codex", "pushed_verified_at": "ISO instant" }] }. Status is todo, in_progress, in_review or done. A lane can carry assigneeId, assignedAgentApiKeyId, and subject_ref (a readable issue ID in the Coordinator project). Relation fields are IDs at write time; the snapshot resolves them to visible ticket links. Missing/hidden relation targets never expose IDs or titles. pm:lane and agent-chat assignment describe the same exported lane identity rather than creating another lane. A failed publish exits nonzero without printing credentials.
Person-facing issue, lane, epic, request, blocker and bot-work counts exclude
follow-up shadow records using followupRecordsForProjects, shared with My Work.
Registration excludes the entire org follow-up project. Without registration,
the fallback uses an immutable server-only creation stamp derived from API origin and the graph writer's exact first-line description
Follow-up fu-<12 lowercase hex> (docs/design/FOLLOWUP-STATE-GRAPH.md)., followed
by a newline or the end of the description. Titles and FOLLOWUP-STATE mirror
comments never classify a ticket; adopted real tickets retain their normal counts.
For existing edited tickets, migration uses the server-written first revision,
so a later marker edit cannot hide ordinary API-created work. Missing original
history leaves a ticket in ordinary counts.
rollup.followupRecords.open and each project's followupRecords report visible
open, unarchived shadow records separately from Open items. These counts reuse
the same org/project/category access predicate as real-ticket counts. The UI
labels them “Follow-up records”; they are not heartbeat totals. The new fields
are optional in the shared decoder for compatibility with an older server
during a paired promotion, while the current API always supplies them.