Architecture
Goal
Build a MACP UI that behaves like an execution operations console:
- orchestration launchpad
- live run monitor
- agent debugger
- logs and traces explorer
- historical analytics surface
High-level architecture
flowchart LR
User[User Browser] --> UI[Next.js App Router UI]
UI --> BFF[Route Handler Proxy /api/proxy/...]
UI --> JP[Jaeger Proxy /api/jaeger/...]
BFF --> ES[Examples Service]
BFF --> CP[Control Plane]
CP --> RT[Runtime]
JP --> JG[Jaeger]
Agents[Agents via macp-sdk] --> RTUnder the observer-only Control Plane model, agents emit envelopes (messages, signals, context updates) directly to the runtime via macp-sdk-python / macp-sdk-typescript rather than through CP HTTP endpoints. The UI reads lifecycle, state, and events from CP; it never originates agent traffic.
Upstream services
This document describes the UI architecture. The upstream services have their own architecture docs, referenced rather than duplicated here:
| Service | Canonical architecture doc |
|---|---|
| Examples Service | examples-service/docs/architecture.md |
| Control Plane | control-plane/docs/ARCHITECTURE.md |
| Runtime | runtime/docs/architecture.md |
| Python SDK (agent framework) | python-sdk/docs/architecture.md · guides/agent-framework.md |
| TypeScript SDK (agent framework) | typescript-sdk/docs/index.md · guides/agent-framework.md |
For the endpoint surface the UI actually consumes, see api-integration.md
and backend-repo-notes.md.
Front-end layers
1. App Router pages
The app/ directory contains route-driven product surfaces:
- dashboard
- scenarios
- runs (history, live workbench, new-run launch)
- agents
- logs
- traces
- observability
- modes (runtime mode registry browser)
- policies (runtime policy registry browser, RFC-MACP-0012)
- settings
2. Data access layer
Located in lib/api/.
fetcher.tswraps proxy requests and exposes the typedApiErrorclass (withisNotFoundgetter for 404 discrimination).client.tsexposes typed UI-facing functions (~1180 lines) covering packs, scenarios, launch, runs, state, events, streaming, metrics, traces, artifacts, audit, webhooks, runtime metadata, runtime policies, batch ops, and admin. Seeapi-integration.md § Client-side integration functionsfor the full inventory.- Functions switch between demo-mode mocks and real proxy-backed requests.
- Includes response normalization layers that reconcile backend shapes with UI types:
normalizeRun()— unwraps paginated responses and nests flatsourceKind/sourceRefintosource: { kind, ref }.archivedAtis passed through from CP directly (the legacy tag-synthesis bridge is gone).normalizeEvent()— nests flatsourceKind/sourceName/subjectKind/subjectId/rawTypefields intosourceandsubjectobjects. Applied bygetRunEvents,listEvents, and the SSEcanonical_eventhandler.validateRunmapsvalid && errors.length === 0→okfor the UI.cancelRunandarchiveRunextract{ ok, runId, status | archived }envelopes from CP's full run record responses.- Cross-run
listEventsfalls back to per-run fan-out when CP lacks the/eventsendpoint and caches the decision for the browser session.
3. Demo-mode data layer
Located in lib/data/mock-data.ts (~2000 lines).
Contains:
- packs
- scenarios
- launch schemas
- compiled execution requests
- runs
- run states
- events
- metrics
- traces
- artifacts
- audit logs
- webhook subscriptions
- chart series
4. Real-time state layer
Located in lib/hooks/use-live-run.ts.
Responsibilities:
- subscribe to live SSE stream
- track connection state
- merge incoming canonical events
- update run-state projection
- simulate stream progression in demo mode
5. Preferences layer
Located in lib/stores/preferences-store.ts.
Persisted UI settings:
- theme
- demo mode toggle
- auto-follow active node
- critical-path animation
- parallel branch visibility
- replay speed
- log density
Key UI components
Layout
AppShellSidebarTopbarCommandPalette
Run experience
RunWorkbenchExecutionGraphNodeInspectorLiveEventFeedSignalRailDecisionPanelRunOverviewCard
Catalog / analytics
ScenarioCardAgentCardRunsTableLineChartCardBarChartCard
Proxy / BFF model
The browser only calls:
/api/proxy/example/...
/api/proxy/control-plane/...
/api/jaeger/... # optional, trace-detail deep-dives only
The generic proxy handler (app/api/proxy/[service]/[...path]/route.ts):
- maps
exampleandcontrol-planeto upstream base URLs - forwards method, headers (minus
host/connection/content-length), query string, and body - injects auth when configured (
x-api-keyfor Examples Service,authorization: Bearerfor Control Plane) - strips
content-encodingon the response and streams the body unchanged - appends
x-macp-ui-proxy: <service>to every response for observability
The Jaeger proxy (app/api/jaeger/[...path]/route.ts) forwards GETs to ${JAEGER_BASE_URL}/api/* and returns 502 when Jaeger is unreachable, so the UI degrades gracefully.
lib/server/integrations.ts resolves the base URLs and throws in production when they are missing; empty auth tokens log a warning but do not block the request.
Product flows
Scenario launch flow
- list packs
- list scenarios for selected pack
- load launch schema
- edit defaults
- compile launch request
- optionally validate request
- submit request to Control Plane
- redirect to live run workbench
Live execution flow
- fetch initial run record and state
- open SSE stream
- render graph
- append canonical events
- update node inspector and signal rail
- show final decision once emitted
Historical analysis flow
- list runs
- open run detail
- inspect traces/artifacts/logs
- compare with another run
- review observability surfaces for regression analysis
Extension points
Easy additions later:
- RBAC-aware route guards
- annotation/comments on runs
- prompt/policy version diffing (payload structural diff already ships; this would layer on top)
- alerting / threshold-based notifications on metric surfaces