AgentCanvas / Pages / Developer Guide / Core / Codebase Map β€” agentcanvas
Files are grouped by package; descriptions explain the role, not the implementation. As of 2026-07-03 the backend (app/) contains ~22,800 lines of Python (tests excluded) across 10 packages + root files. The frontend (src/) is React 18 + TypeScript + React Flow + Zustand. Line counts in tables below are approximate snapshots and drift between updates β€” trust the structure (which file holds what role), not the digits.
Packages added since the initial v1 map agent_loop/ (executor, scope analysis, loop runner β€” the largest package, Β§3) Β· services/ (job scheduler, watchers, resource sampler β€” Β§9) Β· standard/ (wire types, actions, node I/O β€” Β§8) Β· replay/ (episode replay backend β€” Β§9) Β· logging/ (execution log layer β€” Β§7) Β· tools/ (standalone graph validator β€” Β§9). Every package now has its own section below; tests (test_*.py) are out of scope throughout.

Β§1Backend: Root Files

agentcanvas/backend/app/

Entry points, core singletons, and domain modules that live at the package root. Imported by nearly every sub-package β€” they stay at the top level to avoid circular imports.

1.1 Entry Points

FileLinesRole
__init__.py0Package marker
__main__.py3python -m app entry point β€” calls llm.cli.main()
main.py276FastAPI application factory β€” creates the app, registers CORS middleware, mounts 15 API routers across 5 domain sub-packages (canvas, execution, platform, registry, replay), defines lifespan (scans workspace/ on startup via WorkspaceComponentRegistry, starts the JobScheduler tick loop + file watchers + ResourceSampler, shuts down nodesets on exit)

1.2 Core Singletons

Small files imported everywhere β€” config, state, models, and the core data model.

FileLinesRole
config.py42Settings singleton β€” Pydantic BaseSettings loaded from .env: host, port, workspace_dir, Habitat-specific settings. Immutable after init
state.py110ProcessServices + ExecutionGuard + broadcast() β€” ProcessServices holds WorkspaceComponentRegistry; ExecutionGuard is a thread-safe mutex preventing concurrent canvas + eval (ExecutionMode: idle/canvas/eval); broadcast() sends WSMessage to all connected WebSocket clients
models.py17WSMessage β€” single Pydantic model for all WebSocket messages (type, data, timestamp, execution_id, source)
graph_def.py388The core data model β€” pure dataclasses mirroring frontend types.ts: GraphDefinition, NodeDef, EdgeDef, ContainerDef, StateDef, AccessGrantDef, HookDef. JSON-serializable, no live state

1.3 Domain Modules

Single-file modules with one consumer each β€” candidates for future sub-packages as their domains grow.

FileLinesRole
layout.py~750Auto-layout algorithm — two-pass: topological layering (left→right X assignment) + semantic post-processing (viewer stacking, container placement). Also works as CLI: python -m app.layout graph.json
errors.py~300ErrorBus singleton + ErrorEnvelope dataclass (ADR-observability-004). 200-entry ring buffer + 500-deep async dispatch queue, broadcasts error_event WS frames
eval_subprocess_main.py~310Subprocess-per-run entry (ADR-eval-003) β€” python -m app.eval_subprocess_main --run-dir ...: reads spec.json + shared_urls.json, attaches to parent backend's shared singletons, runs BatchEvalRunner.execute() unmodified, writes _DONE marker on clean exit
Removed since v1 env_controller.py + its HabitatEnvController were superseded by the generic BaseEnvPanel contract in app/components/env_panel.py (ADR-server-002). Episode control is now a nodeset-side concern, not a framework module.

Β§2Backend: API Layer

agentcanvas/backend/app/api/

Grouped into 5 domain sub-packages. Each module exports a router = APIRouter() that main.py mounts.

2.1 Canvas β€” graph editing + environment control

FileLinesRoute prefixRole
graphs.py165/api/graphsGraph CRUD β€” list, load, save, delete graph JSON files in workspace/graphs/ and workspace/graph_nodes/. Also POST /layout for auto-layout
env_panel.py175/api/env-panelsEpisode control β€” generic env-panel REST API: list panels, state, dynamic options, field change, action invoke (POSTs guarded by ExecutionGuard). Serves any nodeset's registered BaseEnvPanel

2.2 Execution β€” run, eval, logs, websocket

FileLinesRoute prefixRole
run.py139/api/navigateCanvas graph execution β€” POST /run receives GraphDefinition JSON, creates LoopRunner, runs via GraphExecutor. Plus /stop, /pause, /resume, GET /node-schemas
eval.py558/api/eval/v2Batch eval API β€” POST /start (enqueue a run on JobScheduler; the admitted run executes BatchEvalRunner in its own subprocess), GET /status, POST /stop, GET /runs, DELETE /runs/{id}
eval_storage.py106β€”Eval run persistence β€” saves/loads/deletes eval run results as JSON in outputs/eval_runs/
logs.py270/api/logsExecution log retrieval β€” list runs, full run, per-node, summary. Reads JSONL from outputs/runs/ and outputs/eval_runs/
websocket.py47/wsWebSocket endpoint β€” accepts connections, registers/unregisters clients with state.py. Broadcast logic lives in state.broadcast()
internal_containers.py136/api/internalCross-process container broker (not a public API) β€” lets server-mode subprocess nodes read/write state containers regardless of which process homes them; called by server/remote_container.py::RemoteContainerProxy, msgpack payload path
internal_events.py72/api/internalSubprocess event push β€” receives structured log/error events POSTed by server-mode subprocesses (server/event_push.py) and republishes them on the ErrorBus β†’ error_event WS frame

2.3 Platform β€” config, components, LLM profiles

FileLinesRoute prefixRole
config.py41/api/configSettings β€” GET / returns current Settings + active LLM profile info
components.py238/api/componentsComponent registry β€” nodeset list/load/unload, POST /reload (hot-reload workspace/), /eval-metadata
profiles.py248/api/profilesLLM profile management β€” CRUD, POST /test-key, GET /providers (24 providers), GET /models/{provider}
errors.py32/api/errorsError backfill β€” returns the ErrorBus ring buffer so the frontend can backfill envelopes published while disconnected
system.py53/api/systemSystem usage β€” /usage (latest sample) + /history from the always-on ResourceSampler (Β§9)

2.4 Registry β€” component snapshot

FileLinesRoute prefixRole
snapshot.py43/api/registryShared-nodeset snapshot β€” read-only view of currently-loaded shared nodesets; fallback for run subprocesses that started without a shared_urls.json (the normal path: JobScheduler writes it at submit time), also used by tests

2.5 Replay β€” episode replay API

FileLinesRoute prefixRole
router.py207/api/replayReplay API β€” serves the env-agnostic replay timeline parsed from a run's episode logs (backend package app/replay/, Β§9); consumed by the frontend Replay page (Β§17)

Β§3Backend: Execution Engine

agentcanvas/backend/app/agent_loop/

The core runtime that turns a GraphDefinition JSON into a live agent run. All execution goes through this package. No HTTP/FastAPI imports β€” pure Python.

3.1 Execution Pipeline

When the user presses Play on the canvas, this is the call chain:

api/execution/run.py LoopRunner.run() flatten_graph() build StateContainers GraphExecutor.execute() NODE_HANDLERS.forward() hooks fire around each node firing

3.2 File by File

loop_runner.py216 lines

The outermost lifecycle shell β€” first thing called from the API. Owns the lifecycle: pause, stop, resume.

Does not know how nodes fire β€” sets up the world and hands control to the executor.

flatten.py300 lines

Pre-processing step. Runs before execution starts. The canvas supports nested graphs (composite nodes containing sub-graphs), but the executor needs a flat list of nodes and edges.

graph_executor.py1,987 lines Β· largest file

Where nodes actually execute. Implements a dataflow FIFO queue model:

  1. Build: wraps each NodeDef into a NodeInstance (tracks pending inputs per port)
  2. Seed: finds nodes with no required inputs (environment nodes, constants) and enqueues them
  3. Loop: pull a node from queue β†’ execute β†’ deliver outputs to downstream ports β†’ if downstream has all required inputs, enqueue it
  4. Fan-out: when a node produces output, all target ports are filled before any downstream node fires
  5. Fan-in: a node only fires when all required input ports have data
  6. Cycles: IterOutNode copies its inputs to the paired IterInNode and re-enqueues it
  7. Termination: IterOut's stop input port goes true (Decide phase), step budget reached, stop event set, empty queue, or a node-raised StopExecution

Each node fires by calling NODE_HANDLERS[node_type].forward(inputs, ctx).

scope_analysis.py416 lines

Computes the scope forest before execution starts.

builtin_nodes.py1,369 lines

The node handler registry and the home for all built-in framework nodes (see TODO #37).

Registry:

Built-in node classes (14):

NodeWhat it does
IterInNode / IterOutNodeLoop gates β€” IterOut sends data back to IterIn for next iteration; iterIn's left side captures run-start initPorts, iterOut's final side feeds the after-loop band
LLMCallNodeCalls llm_complete() / vlm_complete() with a prompt, returns text
TextParseNodeGeneric structured parser for LLM response text
HistoryLogNodeAccumulates one TEXT entry per firing, emits the formatted history
GraphInNode / GraphOutNodeComposite boundaries β€” parameter slot / return slot (formerly PortIn/PortOut); graphOut doubles as the sink that buffers _last_inputs for the nav_step broadcast
NullSourceNodeEmits None on a typed output port β€” once, at run-start
NoteNodeMarkdown billboard β€” pure annotation, no inputs/outputs
ImageViewerSinkViewer β€” displays images on canvas via viewer_data WS events (configurable grid, imageGrid layout)
TextViewerSink / TextScrollSink / ActionLogSink / MetricsViewerSinkViewers for latest TEXT / accumulating TEXT / action history / SR/SPL/NE metrics

state_containers.py469 lines

Implements the dual-wire system: nodes communicate via data edges (typed values) and state edges (access grants to shared containers).

eval_batch.py827 lines

Wraps LoopRunner for batch evaluation:

env_worker_pool.py244 lines

Sits between BatchEvalRunner and LoopRunner (ADR-eval-002 PA-3 + PB).

hooks.py307 lines

Fires shell commands at graph/node boundaries β€” like git hooks for graph execution.

__init__.py17 lines

PolicyEntry dataclass: metadata for a loadable neural policy checkpoint (id, name, checkpoint path, config path). Used by WorkspaceComponentRegistry when scanning workspace/policies/.

3.3 Layering

LoopRunner lifecycle shell (pause/stop/resume) GraphExecutor firing engine (FIFO queue) flatten state containers hooks pre-processing + runtime services builtin_nodesNODE_HANDLERS + 14 classes BaseCanvasNode (components/bases.py) BatchEvalRunner multi-episode loop β€” JobScheduler spawns it per-run as a subprocess; fresh LoopRunner per episode
Key design Scheduling is separated from execution. GraphExecutor decides when a node fires (input readiness). NODE_HANDLERS decides what happens (domain logic). Adding a new node type is just subclassing BaseCanvasNode in workspace/ β€” the executor never changes.

Β§4Backend: Component System

agentcanvas/backend/app/components/

The public API for node authors. Files in workspace/ import from this package.

FileLinesRole
bases.py554Base classes for all node authoring β€” PortDef, ConfigField (7 widget types incl. port_list), DisplayField (4 display types: image_viewer, log_list, metric_table, text_viewer), NodeUIConfig, BaseCanvasNode (abstract: forward(), ports, category, icon, _self_log(), batched/batch_dim), BaseNodeSet (loadable tool group; server_python, parallelism, env_panel, default_per_step_budget_sec, replay_parser ClassVars)
registry.py1,955WorkspaceComponentRegistry β€” scans workspace/ subdirectories, imports Python files, discovers BaseCanvasNode/BaseNodeSet subclasses via inspect. Registers in NODE_HANDLERS. Handles load/unload lifecycle, server-mode auto-routing, hook file loading, graph auto-loading, register_remote_nodeset (subprocess attach to shared singletons), container-ownership checks
env_panel.py250BaseEnvPanel β€” control-plane panel base for env-side runtime knobs (suite / split / episode, play / pause / reset); declared via the BaseNodeSet.env_panel ClassVar, served at /api/env-panels/*, bridged into server-mode subprocesses by RemoteEnvPanelProxy. Renamed from "controller" 2026-06-11 (ADR-server-002)
content_hash.py95Content hashing for nodeset source trees β€” lets /api/eval/v2/start detect whether an active_workspace_dir overlay actually changed a shared nodeset's source vs. the frozen baseline
__init__.py24Re-exports: BaseCanvasNode, BaseNodeSet, PortDef, ConfigField, DisplayField, NodeUIConfig β€” the public import path

Β§5Backend: Server SDK

agentcanvas/backend/app/server/

Implements the write-once deploy-anywhere server mode (ADR-server-001). A BaseNodeSet subclass runs identically in-process or as an auto-hosted HTTP server. Three roles: server-side, framework-side, bridge.

FileLinesRole
server_app.py184ServerApp (server-side) β€” builds a FastAPI HTTP service speaking the manifest protocol: GET /manifest, POST /call/{fn}, GET /health
auto_server_app.py204AutoServerApp (server-side) β€” auto-generates a ServerApp from any BaseNodeSet by introspecting port shapes. Owns one BatchedInferenceServer per app; _make_handler dispatches batched=True nodes through the rendezvous
batched_inference.py196Batched-inference rendezvous tier. BatchedInferenceServer owns one _BatchQueue per (function_name, config_hash) β€” collects K parallel callers, calls underlying handler once, scatters K result slices. Pure-functional contract
base_server.py283BaseServer (framework-side) β€” launches subprocess, polls /health, handles start/stop/restart, fetches manifest. Manages process lifecycle and port allocation
nodeset.py192ServerNodeSet β€” combines BaseNodeSet + BaseServer: loadable group of canvas nodes backed by an external server process
proxy.py149Proxy node generator (bridge) β€” reads ServerManifest and auto-generates BaseCanvasNode subclasses whose forward() forwards inputs over HTTP. Canvas can't tell the difference
manifest.py93Manifest protocol types β€” PortSchema, FunctionSchema, ServerManifest
serialization.py300Wire serialization β€” msgpack codec (pack_body/unpack_body, ADR-server-004): one blob ExtType carries raw ndarray/torch.Tensor/PIL.Image bytes with receiver-side degrade; JSON+base64 legacy path kept for the migration window
event_push.py144Subprocess β†’ executor event push β€” forwards structured node logs + first-classed handler exceptions from a server-mode subprocess to the parent's /api/internal/events, so server-node errors surface on the canvas (roadmap #54 Move 3)
remote_container.py75RemoteContainerProxy β€” subprocess-side handle to an executor-home state container; calls the /api/internal broker so access-granted server nodes can read/write home containers
_loopback_proxy.py39Loopback proxy policy β€” keeps httpx calls to localhost auto-host children from being routed through a user's HTTP_PROXY/HTTPS_PROXY shell env
auto_host.py60CLI entry point β€” python -m app.server.auto_host --file nodeset.py --class MyNodeSet --port 9200
examples/habitat_server.py230Reference example β€” hand-written ServerApp for Habitat-Sim (pre-dates AutoServerApp). Kept as documentation
__init__.py30Re-exports: AutoServerApp, BaseServer, ServerApp, ServerFunction, ServerNodeSet, manifest types

Β§6Backend: LLM System

agentcanvas/backend/app/llm/

Self-contained package for the LLM/VLM subsystem. __init__.py re-exports the public API so external imports work unchanged.

FileLinesRole
__init__.py34Re-exports: llm_complete, vlm_complete, get_llm_config, LLMConfig, get_profile_store, LLMProfile, PROVIDER_REGISTRY, resolve_provider_config
call.py251LLM/VLM call client β€” llm_complete() (text-only) and vlm_complete() (multimodal) via litellm. Resolves provider config from active profile using the 3-layer fallback chain (ADR-platform-003). Supports 100+ providers through litellm
profiles.py157ProfileStore β€” persistent storage at ~/.agentcanvas/profiles.json. Thread-safe with RLock. Mtime-based cache invalidation
providers.py117Provider registry β€” static dict mapping 24 provider IDs to ProviderDef (label, base_url, api_type, default_model, litellm_prefix). Covers: OpenAI, Anthropic, Google, Ollama, DeepSeek, Together, OpenRouter, Mistral, xAI, NVIDIA NIM, Hugging Face, Moonshot, and more
key_validator.py94API key validation β€” tests if an API key is valid via a minimal litellm completion request
cli.py478Standalone CLI tool β€” 8 argparse commands: list, add, remove, set-active, test, show, setup, models

6.1 Fallback Chain (ADR-platform-003)

profile config field AGENTCANVAS_* VLM_* (legacy) registry default first match wins β†’

Β§7Backend: Logging

agentcanvas/backend/app/logging/

Two-layer logging system (ADR-observability-003): the executor captures port I/O automatically (exterior); nodes add domain detail via _self_log() (interior).

FileLinesRole
logger.py342ExecutionLogger β€” captures two layers per node firing: automatic exterior (executor records all port inputs, outputs, timing) + voluntary interior (nodes call _self_log(key, value)). log_serialize() truncates large values. Persists as JSONL. Broadcasts exec_log WS events (suppressed in eval batch mode)
models.py39Log data models β€” NodeLogEntry (one node firing) and ExecutionSummary (aggregate)
__init__.py7Re-exports: ExecutionLogger, ExecutionSummary, NodeLogEntry

Β§8Backend: Standard Library

agentcanvas/backend/app/standard/

Shared type definitions and constants used by node implementations and the executor. This is a leaf package with no intra-app dependencies.

FileLinesRole
wire_types.py443Wire type registry β€” 9 current types (IMAGE, DEPTH, POSE, DISCRETE_ACTION, CONTROL, TEXT, BOOL, METRICS, ANY) + 3 deprecated aliases (ACTION, OBSERVATION, STEP_RESULT) kept so legacy graphs load, + LIST[T] modifier, WIRE_FORMAT_SPEC canonical on-wire formats, display colours. Helpers: image_to_base64(), depth_to_base64(), serialize_for_display()
actions.py40Action constants β€” ACTION_STOP, ACTION_FORWARD, ACTION_TURN_LEFT, ACTION_TURN_RIGHT (the VLN Discrete(4) space). parse_action_from_text() extracts an action from LLM output
node_io.py168Auto-generated node I/O schema β€” NODE_IO_SCHEMA + REQUIRED_INPUTS built lazily from the NODE_HANDLERS registry (no handler classes live here). Used by GraphExecutor for fan-in logic
__init__.py9Re-exports everything from all 3 modules

Β§9Backend: Services, Replay & Tools

agentcanvas/backend/app/services/

Long-running backend-process services owned by the FastAPI lifespan: job scheduling, file watching, resource sampling.

FileLinesRole
job_scheduler.py941JobScheduler β€” admission + FIFO queue + Popen pool for eval run subprocesses (ADR-eval-003). Singleton owned by ProcessServices; one tick() per second admits queued jobs through the per-resource measured gate (calibrated estimate vs measured free VRAM/RAM minus reservations; declared fallback) while the canvas ExecutionGuard lock is free; spawns python -m app.eval_subprocess_main with PR_SET_PDEATHSIG
resource_stats.py739ResourceStatsTracker β€” per-owner resource attribution (VRAM per-PID from the sampler, RAM RSS from one /proc walk β†’ shared server | job tree | external), per-run observation windows, calibration store (outputs/system/resource_calibration.json, EWMA + max per resource, source-hash keyed) and the admission estimator behind GET /api/eval/v2/estimate
graph_watcher.py73Graph-directory watcher β€” pushes graphs_changed when graph JSON files change on disk, so coding-agent edits made outside the web UI show up in the Explorer without a manual refresh
nodeset_watcher.py156Nodeset-source watcher β€” hot-reloads nodeset .py edits without a backend restart; companion to graph_watcher
resource_sampler.py346ResourceSampler β€” always-on ~1 Hz machine resource time-series written to outputs/system/system-YYYYMMDD.jsonl; feeds /api/system/usage + /history and the Monitor page (Β§17)
run_state_io.py137Run-dir file protocol β€” the contract between JobScheduler (parent) and eval_subprocess_main (subprocess) under outputs/eval_runs/{run_id}/: spec.json, shared_urls.json, _DONE, …
system_runs.py279System Log per-run aggregation β€” joins a run's execution logs with tee'd resource samples into a per-run performance summary for the Monitor page's Run view
agentcanvas/backend/app/replay/

Episode replay backend β€” turns persisted episode logs into an env-agnostic replay timeline (served by api/replay/, Β§2.5; consumed by the frontend Replay page, Β§17).

FileLinesRole
interface.py292Replay interface β€” env-agnostic timeline schema + BaseReplayParser ABC; an env nodeset opts into replay by pointing its replay_parser ClassVar at a parser module (pure log-walking, no simulator imports)
renderer_client.py144Renderer client β€” lazy-spawns and calls a renderer subprocess for parsers' smooth-mode frame requests; held warm for the FastAPI process lifetime
renderer_host.py61Renderer host β€” launches a renderer class from a file path (mirrors server/auto_host.py but only needs a build_app() method)
agentcanvas/backend/app/tools/
FileLinesRole
validate_graph.py226Standalone graph wire-type validator (dev tool) β€” loads a graph from disk, runs the same app.graph_def.wire_type_report core the backend uses, prints a report, exits non-zero on any mismatch. No backend needed

Β§10Backend: Dependency Flow

How the backend packages depend on each other. Arrows point from importer to imported.

main.py api/canvas/ api/execution/ api/platform/ agent_loop/ state.py components/ broadcast() graph_def.py standard/ logging/ server/ llm/

Β§11Frontend: App Shell

agentcanvas/frontend/src/
FileRole
main.tsxReact entry point β€” mounts <App /> into #root
App.tsxTop-level router β€” switches between CanvasPage, EvalPage, LogViewerPage, NodeSetManager based on appMode
store.tsZustand global store β€” connected, appMode, eval run state, nav status, env context bar data
api.tsREST client β€” fetchJ, postJ, deleteJ, putJ wrappers around fetch with JSON handling
ws.tsWebSocket manager β€” connects to /ws, dispatches typed events to listeners; reconnects on drop
types.tsGlobal TypeScript types β€” NavStatus, EvalRunSummary, AppMode, EnvContextState
errorStore.tsuseErrorStore β€” Zustand store of ErrorEnvelopes fed by error_event WS frames; backfills from GET /api/errors on (re)connect
errors.tsErrorEnvelope types + severity helpers mirroring the backend ErrorBus schema
vite-env.d.tsVite environment type declarations

Shared components (src/components/)

Header.tsxTop app header β€” brand, appMode segmented toggle, execution-mode badge (polls /api/navigate/execution-mode), settings-gear entry
SettingsModal.tsxSettings modal β€” provider API keys, active provider + default model, profiles/favorites; reads/writes /api/config + /api/profiles
ErrorToast.tsxFloating toast layer β€” auto-pops fresh error/warning ErrorEnvelopes (max 3, auto-dismiss); click focuses the Report tab
ViewPanel.tsxStatic placeholder "Views" panel (RGB / Depth tiles) β€” presentational stub, not wired to data

Β§12Frontend: Canvas Editor

agentcanvas/frontend/src/canvas/

Core graph editing workspace. React Flow drives the visual layer; a Zustand store owns all graph state.

FileRole
CanvasPage.tsxCanvas workspace page β€” tab management, error boundary, layout scaffold
UnifiedGraphEditor.tsxCore editor β€” root graph mode + subgraph drill-down mode; React Flow instance owner
useFlowStore.tsZustand canvas store β€” tab-aware graph state, node/edge CRUD, selection, execution status per tab
types.tsGraphDefinition, NodeDef, EdgeDef, ContainerDef, StateDef, AccessGrantDef β€” mirrors backend schema
graphConversion.tsBidirectional conversion between GraphDefinition (backend JSON) and React Flow Node[]/Edge[]
defaultGraph.tsTemplate graphs loaded for new tabs (Straightforward CMA, NavGPT-CE)
portResolution.tsresolveInstancePorts() β€” applies config.ports overrides to class-level port schemas
unifiedNodeTypes.tsNode type registry β€” maps node type strings to React components for React Flow
unifiedCatalog.tsComponent catalog β€” fetches node schemas from backend; drives the node library sidebar
nodesetLoader.tsNodeSet loader β€” fetches loaded nodesets list; triggers nodesets-changed refresh pipeline
groupSelection.tsMulti-select group logic β€” bounding box selection, group move
flowStoreRef.tsBridge β€” holds a stable ref to the React Flow instance accessible outside React tree
runPipeline.tsrunPipeline() β€” builds RunRequest, calls /api/run, manages execution lifecycle from frontend side
edgeTypes.tsCustom edge type registry β€” registers AccessGrantEdge alongside default React Flow edge types
edges/AccessGrantEdge.tsxRenders dashed violet access-grant edges (renamed from StateEdge.tsx with the state-edge β†’ access-grant terminology shift, ADR-dataflow-004)
ShortcutCheatsheet.tsxKeyboard-shortcut cheatsheet overlay
useKeyboardShortcuts.tsGlobal keyboard-shortcut hook β€” Space = play/pause, F = fit view, ? = cheatsheet; ignores typing targets

Β§13Frontend: Canvas Nodes

agentcanvas/frontend/src/canvas/nodes/

The renderer tree that turns Python-defined NodeUIConfig into visible canvas nodes.

Shared primitives

shared/NodeShell.tsxGeneric node wrapper β€” selection ring, drag handle, resize handle
shared/HandleDot.tsxPort handle dot β€” colour-coded by wire type, tooltip with port name/type

Special node types

state/StateContainerNode.tsxRenders a StateContainer on canvas β€” dashed violet border, database icon, reducer badges
composite/CompositeCanvasNode.tsxRenders a composite (nested graph) node β€” port boundaries, drill-down button
composite/CompositeNodeView.tsxEditing view inside a composite node β€” full sub-graph editor in a panel

Generic renderer stack (agentloop/inner/)

All non-special nodes flow through this stack, driven entirely by NodeUIConfig from Python.

GenericBlockRenderer.tsxDispatcher β€” reads ui_config.layout, routes to BlockLayout, StripLayout, ViewerLayout, NoteLayout, or ImageGridViewerLayout
layouts/BlockLayout.tsxStandard block layout β€” title bar + config fields + port handles
layouts/StripLayout.tsxHorizontal strip β€” compact port-only view for pass-through nodes
layouts/ViewerLayout.tsxViewer layout β€” display-only fields for output sink nodes
layouts/ImageGridViewerLayout.tsximageGrid layout β€” rowsΓ—cols image grid for the configurable imageViewer (ADR-components-007)
layouts/NoteLayout.tsxnote layout β€” markdown billboard rendering for the note built-in
layouts/IterInExpandPanel.tsxiterIn expand panel β€” edits the two-sided iterIn's initPorts (with per-port persist checkboxes) and shows the synthesised init/iterOut port surface; edits resync handles live
layouts/ConfigFieldRenderer.tsxRenders a single ConfigField β€” label, slider, text, select, toggle, textarea, port_list
layouts/PortListEditor.tsxport_list config field widget β€” add/remove/rename ports for IterIn/IterOut
layouts/layoutUtils.tsShared layout helpers β€” port position calculation, label truncation
layouts/useNodeOutput.tsReact hook β€” subscribes to viewer_data WS events for a given node_id
layouts/viewerFieldRegistry.tsRegistry of display field renderers keyed by DisplayField.display_type

Display field renderers (layouts/fields/)

ImageViewerField.tsxRenders base64 image frames from viewer_data events
TextViewerField.tsxRenders plain text output (LLM responses, debug strings)
LogListField.tsxScrolling log list with accumulate=true appending behaviour
MetricTableField.tsxKey-value metric table (SR, SPL, NE, etc.)

Β§14Frontend: Canvas Panels

agentcanvas/frontend/src/canvas/panels/

Sidebar and toolbar UI components that surround the React Flow canvas.

TabBar.tsxTab strip for open graphs β€” add, close, switch, unsaved-change indicator
ExplorerPanel.tsxVS Code-style tree β€” Graphs / Graph Nodes / Nodes sections, drag-to-canvas
NodeLibrary.tsxDraggable node palette β€” shows only built-in + currently loaded nodeset nodes
PropertiesPanel.tsxSelected node property editor β€” config field forms, port visibility
StatePanel.tsxState container editor β€” add/remove named state entries, set reducer type
OutputDrawer.tsxBottom drawer β€” Observation / Logs / Step Log / State / Report tabs
LogPanel.tsxExecution log tab β€” lists exec_log events grouped by node
StepLogPanel.tsxPer-step log tab β€” full I/O trace for the current step
ExecutionToolbar.tsxRun / Pause / Stop / Step buttons + execution status badge
EnvPanel.tsxEnv panel strip (renamed from EnvContextBar.tsx; episode browsing folded in) β€” renders any nodeset's BaseEnvPanel from /api/env-panels: suite/split selectors, episode navigator, panel actions
ObservationPanel.tsxCurrent observation display β€” image + instruction text
ReportPanel.tsxReport tab β€” time-ordered ErrorEnvelope list with severity filter chips, click-to-expand (ADR-observability-004)
ResizableBottomPanel.tsxDrag-resizable wrapper for the bottom drawer
GraphStateBanner.tsxBanner shown during/after execution β€” run status, error messages
TemplatePicker.tsxGraph template picker modal β€” choose preset to load into new tab
SaveGraphDialog.tsxSave graph / save-as-node dialog β€” name, group, kind selection

Β§15Frontend: Evaluation Page

agentcanvas/frontend/src/eval/

Batch evaluation launcher and live monitor (ADR-eval-001).

EvalPage.tsxTop-level eval page β€” orchestrates all sub-components, subscribes to WS eval events
evalApi.tsEval API client β€” startEval(), stopEval(), getRunHistory() wrappers
types.tsEval-specific TypeScript types β€” EvalRun, EpisodeResult, EvalStatus
GraphSelector.tsxGraph picker β€” lists saved graphs, shows nodeset requirements
EnvInfoPanel.tsxEnvironment info panel β€” split counts, episode counts, dataset source
EvalProgressBar.tsxProgress bar β€” episodes done / total with estimated completion
MetricCards.tsxLive metric summary cards β€” SR, SPL, NE updated after each episode
EpisodesTable.tsxPer-episode results table β€” instruction, predicted path, metrics
RunHistory.tsxPrevious runs list β€” load past run results for comparison

Β§16Frontend: Log Viewer

agentcanvas/frontend/src/logs/

Dedicated page for post-hoc inspection of execution logs (ADR-observability-003).

LogViewerPage.tsxFull-page log viewer β€” split view: run list + detail pane
logApi.tsLogs API client β€” listRuns(), getRunLog(), getNodeLog()
types.tsLog-specific types β€” RunRecord, NodeLogEntry, PortSnapshot
LogContext.tsxReact context β€” selected run, filter state, pagination
LogCanvasView.tsxCanvas-style log visualisation β€” replay node firing order on a static graph
ExecutionList.tsxLeft panel β€” paginated list of past execution runs
LogEntryList.tsxRight panel β€” paginated log entries for a selected node
LogSummaryBar.tsxSummary bar β€” total nodes fired, duration, error count
LogFilters.tsxFilter controls β€” filter by node type, wire type, has-error
nodeColors.tsNode type to colour mapping for log canvas

Renderers (logs/renderers/)

Each renderer handles one value type in a log entry's port snapshot.

registry.tsRenderer registry β€” maps wire_type strings to renderer components
PrimitiveRenderer.tsxNumbers, booleans, short strings
TextRenderer.tsxLong text β€” LLM responses, prompts
ImageRenderer.tsxBase64 image frames
JsonTreeRenderer.tsxCollapsible JSON tree for dicts/lists
ActionRenderer.tsxACTION wire β€” discrete action label + direction arrow
ErrorRenderer.tsxError traces β€” red-highlighted stack output
TensorRenderer.tsxNumPy array β€” shape, dtype, value preview
MetricsRenderer.tsxMETRICS wire β€” key-value metric table
StepResultRenderer.tsxSTEP_RESULT wire (deprecated type) β€” done flag, info dict, reward
ImageListRenderer.tsxLIST[IMAGE] β€” thumbnail strip for multi-image port values

Β§17Frontend: Replay & Monitor

agentcanvas/frontend/src/replay/ Β· src/pages/monitor/

Two post-hoc inspection pages beyond the log viewer: episode replay and system/run performance monitoring.

replay/ReplayPage.tsxEpisode replay page β€” steps through the env-agnostic replay timeline served by /api/replay (Β§2.5), with frame scrubbing
replay/replayApi.tsReplay API client β€” timeline + frame fetch wrappers
pages/monitor/MonitorPage.tsxMonitor page β€” machine resource sparklines (polls /api/system/usage, Β§2.3) + the JobScheduler queue view (polls /api/eval/v2/queue) + per-run performance view (System Log)
pages/monitor/NodeTimingTable.tsxPer-node timing table β€” where a run's wall-clock went, node by node
pages/monitor/Sparkline.tsxInline sparkline chart for resource/timing series

Β§18Frontend: NodeSet Manager

agentcanvas/frontend/src/pages/
NodeSetManager.tsxFull NodeSet manager page β€” two sections: Local NodeSets (load/unload), Server Mode (start/stop/restart)
manager/NodeSetCard.tsxCard for one local NodeSet β€” name, status badge, load/unload button
manager/ServerCard.tsxCard for one server-mode nodeset β€” URL, health status, start/stop/restart controls

Β§19Config and Build

frontend/package.jsonNPM manifest β€” React 18, @xyflow/react, Zustand, Tailwind, Vite, Three.js
frontend/vite.config.tsVite config β€” API proxy (/api to localhost:8000), React plugin
frontend/tsconfig.jsonTypeScript config β€” strict mode, path aliases
backend/requirements.txtPython deps β€” FastAPI, Uvicorn, Pydantic v2, LiteLLM, httpx, python-multipart
backend/.env.exampleEnvironment variable template β€” AGENTCANVAS_* keys, port config
run_dev.shDev launcher β€” starts backend (uvicorn) on :8000 and frontend (vite) on :5173 in parallel
AgentCanvas docs