Dashboard and editing
The same teamapi serve-api process serves a live dashboard at /dashboard. Its static HTML/CSS/JS fetches the running REST API, with no separate process or build step. It shows every team with its type and focus, free-text search, and a tabbed diagram viewer (topology / org-hierarchy / context-map) rendered client-side with Mermaid. Each team has a cognitive-load bar coded by color and icon, plus a separate 🤖 chip for supervision load. Keeping supervision out of the bar gives its width the same meaning for every team. Sections load independently, so a blocked CDN only disables the diagram tab; the team list, cognitive load, and search keep working.
The Live digital twin turns that graph into an original pixel-art operations floor. Teams occupy rooms, people and agents appear as attributed sprites, and declared interactions, dependencies, services, and agent states play through an event rail. Playback can be paused, stepped, or sped up, and selecting a room opens its normal team detail panel. This is deliberately a declared-state replay, not fake activity telemetry: GET /digital-twin derives every cue from the resolved TeamAPI documents and never claims that an agent or team is executing work right now.

A Health section runs all three graph-only checks at once — gaps, policy, and topology — as counts plus a combined finding list sorted most-serious-first, so a blocking finding is never buried under twenty warnings. These are served by GET /gaps, /policy and /topology, all pure functions of the resolved graph, and each is fetched independently: a server built before /policy and /topology existed shows those two as unavailable rather than blanking the section.
Clicking a team opens a detail panel: its roles (with vacancies marked, since a vacancy is what gaps escalates when another team reports into it), members and contacts, services, declared agents and who owns each, and its interactions and dependencies. Cards are keyboard-operable, not mouse-only.

teamapi serve-api examples/acme-org --port 3000
open http://127.0.0.1:3000/dashboard

Four more sections expose data that was already available through the API:
- AI agents — the whole fleet, with counts by status and provider. Every agent with no human owner is marked in red. Without that mark, downstream consumers such as
AGENTS.md, context bundles, and generated crews present an unowned agent exactly like one with a real owner. - Sessions — what was actually built with an assistant, newest first.
- Context map — as a list beside the diagram, because the diagram can show the relationships but not the conflicts: two teams describing one relationship differently. Conflicts are listed first, unconditionally; a disagreement buried under thirty healthy relationships is a disagreement nobody acts on.
- Knowledge graph — a node picker and depth control for walking the graph. Drawing the entire graph would be unreadable; selecting a node answers questions such as "what's connected to this ADR?"
Every section fetches independently and says so when it fails, so a server built before one of these routes existed degrades to a message rather than to a box that stays on "Loading…".
Editing a team without opening an editor#
Everything else in the API is read-only because the YAML documents in git are the source of truth. Requiring direct git edits, however, has often limited corrections to engineers. Other team members may know a document is wrong but lack a way to fix it.
teamapi serve-api ./org --propose-to acme/org-repo # needs GITHUB_TOKEN with write access
The dashboard's team panel gains a small form for focus, the four cognitive-load scores, and notes. Its button says Open a pull request. The served graph does not change:
POST /teams/stream-checkout/proposals
{ "patch": { "cognitiveLoad": { "intrinsic": 6, "extraneous": 4, "germane": 4, "supervision": 3 } },
"author": "aoife@example.com" }
201 → { "url": "https://github.com/acme/org-repo/pull/128", "summary": ["cognitiveLoad.extraneous: 8 → 4", …] }
The change remains reviewed, attributable, CI-checked, and declinable. More people can start the conversation without weakening git as the source of truth. Committing directly to the default branch would turn the documents into a database with a YAML export.
Four things make it safe to hand to somebody who has never seen the schema:
- The patch is a closed list:
info.name,info.focus,cognitiveLoad,channels,searchTerms. Nothing that changes what other documents resolve to — no$ref, no id rename, no team removal. An unknown field is rejected, not dropped, so a client sendinginteractionsgets told no rather than a pull request that did nothing. - Comments survive. The YAML is edited in place rather than re-serialized from the resolved object, because these files carry the reasons things are the way they are — the
notes:explaining a load score, the comment above an interaction saying when it should end. A write path that deleted those would make the format worse for having a UI. - The result is re-validated and re-formatted before it's pushed, so the pull request can't fail
teamapi validateorteamapi fmt --check. Somebody who used a web form should never be handed a red build they have no way to fix. - Proposing the same change twice updates one pull request instead of accumulating near-identical ones: the branch name is derived from the resulting content.
GET /health reports which optional surfaces this server has, so the dashboard knows whether to offer the form at all — an edit button that 404s is worse than no edit button. Add "dryRun": true to get the proposed file and change summary back without writing anything.