Skip to main content

ADR-006: Tech Stack

Status: Accepted | Date: 2026-02-10

Context

Fascia has a split architecture: a Control Plane (Fascia-hosted) for design-time operations and a Data Plane (customer's GCP project) for runtime execution. The tech stack must serve two distinct environments with different requirements:

  • Control Plane -- JSON Schema-heavy spec management, LLM API integration for the Safety Agent, deployment orchestration, and a visual flow builder UI.
  • Data Plane (Executor) -- Runs on Cloud Run in the customer's GCP project. Cold start time is critical because it directly impacts every API call made by end users of customer products.
  • Frontend -- Flow Studio requires a mature visual graph/canvas library for building flow graphs.

Decision

LayerChoiceVersion Target
FrontendReact + TypeScript + ReactFlow (xyflow)React 19+, ReactFlow 12+
Platform BackendNode.js + TypeScript + FastifyNode 22 LTS, Fastify 5+
Executor (Customer Cloud Run)GoGo 1.23+
Platform DatabasePostgreSQLPostgres 16+
LLM StrategyMulti-model cross-check (Claude + GPT-4 + Gemini fallback)Provider abstraction layer

Why Fastify

Fastify was chosen over NestJS and Hono primarily for its native JSON Schema validation via Ajv. Fascia's entire domain model is defined as JSON Schema -- Entity specs, Tool input/output schemas, and Policy conditions are all JSON Schema documents. Fastify's route-level schema declaration allows specs to flow naturally from domain definitions to API validation to auto-generated Swagger documentation.

  • NestJS -- Too much boilerplate (dependency injection, decorators, modules) for a spec management platform. Its class-validator approach is less natural than JSON Schema for Fascia's domain.
  • Hono -- Faster benchmarks and a cleaner API, but uses Zod validators rather than JSON Schema natively. Its multi-runtime advantage (Bun, Deno, Cloudflare Workers) is irrelevant since the Control Plane runs on GCP Cloud Run with Node.js.

Why Go for the Executor

The Executor is a well-bounded, rarely-changing component. Once the flow graph engine and node handlers are implemented, changes are infrequent. The cold start advantage of Go (~50ms vs ~300-500ms for Node.js) directly impacts every API call made by end users.

Go also produces small, statically-linked binaries that are ideal for container deployment on Cloud Run, and its built-in concurrency primitives (goroutines, channels) are well-suited for parallel node execution within flow graphs.

Why React + ReactFlow

ReactFlow (xyflow) is the de facto standard for building visual graph editors in the browser. Flow Studio -- where users design Tool execution logic by connecting nodes -- is a critical product surface. ReactFlow provides production-ready pan, zoom, node dragging, edge routing, and minimap capabilities that would take months to build from scratch.

Alternatives Considered

Frontend

OptionProsCons
React + TypeScriptLargest ecosystem, ReactFlow is the standard for flow buildersBundle size, ecosystem complexity
SvelteKitSmaller bundle, simpler state managementFewer graph/flow libraries; Flow Studio is on the critical path
Vue + TypeScriptGood balance of simplicity and powerFewer flow editor options than React ecosystem

Platform Backend

OptionProsCons
Fastify (Node.js + TS)Native JSON Schema via Ajv, fast, type sharing with frontendSingle-threaded (mitigatable with clustering)
Hono (Node.js + TS)Ultra-lightweight, modern APIZod-based validation, smaller plugin ecosystem
NestJS (Node.js + TS)Strong structure (DI, modules)Heavy boilerplate, class-validator less aligned with JSON Schema
GoHigh performance, good concurrencyNo type sharing with frontend, less ergonomic for JSON-heavy spec work
Python (FastAPI)Good LLM library ecosystemDifferent language from frontend, GIL limitations, LLM calls are just HTTP

Executor

OptionProsCons
Go~50ms cold start, small binary, excellent concurrency, low memoryDual-language codebase
Node.js + TypeScriptSame language as platform, shared types~300-500ms cold start, higher memory footprint
RustFastest cold start, smallest binarySteep learning curve, slower development velocity

Database

OptionProsCons
PostgreSQLJSONB for spec storage, rich query features, relational integrity for metadataRequires management (mitigated by Cloud SQL)
MongoDBFlexible schema for specsWeaker transaction support, specs have known structure that benefits from relational model

Consequences

Positive

  • JSON Schema alignment -- Schemas flow seamlessly from spec definitions to Fastify route validation to auto-generated Swagger documentation. No translation layer needed.
  • Shared TypeScript types -- Frontend and platform backend share TypeScript type definitions for spec interfaces, creating a single source of truth for the Control Plane.
  • Sub-100ms cold start -- The Go Executor delivers fast cold starts on Cloud Run, providing excellent end-user experience for customer products.
  • Production-ready flow canvas -- ReactFlow provides a mature visual editor for Flow Studio, saving months of custom development.
  • Efficient spec storage -- PostgreSQL JSONB stores spec documents efficiently while maintaining relational integrity for workspace and version metadata.

Negative

  • Dual-language codebase -- TypeScript for the Control Plane and Go for the Executor requires proficiency in both languages. Developer onboarding takes longer.
  • Type synchronization -- The Go Executor cannot import TypeScript type definitions. Spec types must be kept in sync through JSON Schema as the shared contract, with code generation to reduce manual duplication.
  • Smaller community -- Fastify has a smaller community than Express or NestJS. Some patterns require custom implementation rather than off-the-shelf plugins.

Risks

  • Type drift between TypeScript and Go -- Mitigated by using JSON Schema as the shared contract and generating Go structs from the same schemas.
  • Go Executor maintenance burden -- Mitigated by keeping the Executor's scope minimal. It only executes compiled spec bundles; business logic lives in specs, not in Executor code.
  • Fastify plugin gaps -- Mitigated by leveraging the broader Node.js ecosystem. Most Express middleware is compatible via @fastify/middie.