Full-stack architecture
VELA is a ground-segment, advisory system. This page is the map of the whole stack; the design — architecture doc goes deeper on the reasoning graph, and data & ingest covers the telemetry pipeline.
The stack at a glance
One composition root, two entry points
vela/orchestration.py::prepare_investigation turns a scenario into a PreparedInvestigation — a
triggering AnomalyEvent, a wired in-process MCP client, and a reasoning model. Both the CLI
(vela/cli.py) and the HTTP service (vela/api/) go through it, so they drive an identical graph and
produce identical reports. Store wiring happens here, deliberately outside the agent layer.
The reasoning graph is fixed and deterministic
vela/agents/graph.py is an auditable blueprint — not an agent choosing its own path:
supervisor ─┬─(suppressed)──────────────────────────────► END └─(investigate)─► triage ─► investigator ─► validator ─► drafter ─┬─(procedure)─► approval ─► END └─(none)──────────────────► END- State is a
TypedDict;audit_logis a reducer. Each node returns only its ownAgentTraceStep(annotated withoperator.add) — never a read-modify-write of the accumulated list. - The approval gate is a real
interrupt(). The graph compiles with a checkpointer keyed bythread_id == investigation_id. Resuming withCommand(resume=…)continues the run. On resume the interrupted node re-executes from the top, so the only code beforeinterrupt()inapproval_nodeis deterministic and idempotent.
The MCP boundary is the audit boundary
vela/mcp/client.py defines the MCPClient Protocol — the complete data surface available to
reasoning, spanning three servers:
| Server | Tools |
|---|---|
| knowledge | channels_to_components, get_failure_modes, search_docs, get_doc_section, resolve_citation |
| telemetry | get_window, get_command_history, compare_to_baseline |
| procedure | get_procedure_templates, validate_procedure, format_report |
InProcessMCPClient calls the tool functions directly (the real path today); serving them as real
MCP endpoints over stdio/HTTP is a documented future seam. Every result is stamped with as_of for
deterministic provenance.
Guardrail #4 — enforced by import-linter
vela/agents/ may import vela.mcp.client and nothing else data-shaped — no vela.kg,
vela.adapters, or store/tool modules. To give agents a new capability, add a tool to an MCP
server and expose it on the MCPClient protocol; never widen the import.
Stores sit behind protocols
GraphStore(vela/kg/store.py) →InMemoryGraphStore|Neo4jGraphStoreDocSearch(vela/kg/doc_search.py) →GraphKeywordDocSearch|QdrantDocSearch|HybridDocSearch
Chosen via store_backend / doc_search_backend in Settings, overridable by env. Nothing above the
MCP layer changes when you swap them (ADR 0007).
Contracts are the single source of truth
Pydantic v2 models in vela/contracts.py (VelaModel, extra="forbid") define every shared shape.
apps/console/src/api/types.ts mirrors them by hand — a contract change updates both in the same
change. See ADR 0002.
Reasoning: deterministic by default, real Claude available
get_model returns DeterministicModel by default — required in airgap, used by the golden/CI
suite for reproducibility. AnthropicModel (vela/agents/models/anthropic.py) is implemented and
live-verified, key-gated and config-selected; its release-blocking grounding gates fail closed to
INSUFFICIENT. Prompts live in versioned .md files under vela/agents/prompts/, never inline
f-strings.
Frontend
One frontend: apps/console/ — the operator / production surface, contract-aligned, RBAC-gated,
wired to the live API, tested (vitest + Playwright). See Frontend console.
