Files
forge/TECH_STACK.md
T
nihalashetty ae67bff5a3 feat: deep-agent canvas, live observability, and multi-environment tooling
Self-hosted platform for building, testing, and shipping LangChain/LangGraph agents. Deep-agent sub-agents on the canvas, a live tracing/observability timeline, auto-provisioned built-in tools with import/export, per-environment tool variables, streamed evaluations, and per-user auth token forwarding.
2026-07-28 01:49:19 +05:30

14 KiB
Raw Blame History

Forge — Technology Stack

Every technology used in Forge, its purpose, and where it lives. Sourced from apps/web/package.json, apps/api/pyproject.toml, both Dockerfiles, docker-compose.yml, and apps/api/forge/config.py. Backend deps grouped [in brackets] are optional extras (installed on demand / prod); everything else is core.

Layer Technology Use case Where it lies
Frontend Next.js 14.2 React framework; standalone server + build-time API proxy rewrites to the backend apps/web (web console); next.config.mjs
Frontend React 18.3 + React DOM UI component rendering apps/web
Frontend TypeScript 5.6 Typed frontend language apps/web (tsconfig.json)
Frontend @xyflow/react (React Flow) 12 Visual drag-and-drop node-graph editor — the workflow builder canvas apps/web (builder/canvas components)
Frontend react-markdown 9 + remark-gfm 4 Render agent/chat responses as GitHub-flavored Markdown apps/web (chat UI)
Frontend mustache 4 Client-side {{...}} template rendering apps/web
Frontend jmespath 0.16 JSON projection/query in the browser apps/web
Frontend / Build Node.js 22 JS runtime for building and serving the console apps/web/Dockerfile (node:22-alpine)
Build / Monorepo pnpm (workspace) Package manager + monorepo workspaces (apps/web, packages/*) repo root (pnpm-workspace.yaml, corepack)
Backend / API Python 3.113.13 Backend language (runtime image: python:3.12-slim) apps/api
Backend / API FastAPI 0.115 HTTP/REST API framework, routing, dependency injection, middleware apps/api/forge/main.py, forge/routers/*
Backend / API Uvicorn[standard] 0.32 ASGI server that runs the app serve command (uvicorn forge.main:app)
Backend / API sse-starlette 2.1 Server-Sent Events streaming of run event frames (run/node_start/messages/done) forge/routers/runs.py
Backend / API python-multipart Multipart form / file upload parsing apps/api
Backend / API hatchling Python package build backend apps/api/pyproject.toml
Config / Data Pydantic 2.9 Request/response DTOs, data validation forge/schemas/dto.py, models
Config / Data pydantic-settings 2.6 Environment-driven application settings forge/config.py
Config / Data email-validator 2.2 Validate email fields (invites, auth) apps/api
Agent / Engine LangChain 1.3 + langchain-core LLM orchestration primitives (messages, tools, model bindings) forge/engine/*
Agent / Engine LangGraph 1.2 Stateful agent/workflow graph engine — compiles nodes into a runnable graph; the execution core forge/engine/compiler.py, forge/services/runs.py
Agent / Engine langgraph-checkpoint 4 Checkpointer interface for durable / resumable run + HITL state forge/engine, forge/services/runs.py
Agent / Engine langgraph-checkpoint-sqlite 3 SQLite-backed checkpointer (dev default) .data/checkpoints.sqlite
Agent / Engine deepagents 0.6 Deep Agents harness (planning, subagents, virtual filesystem, sandbox) — always-registered deep_agent node agent node palette
Model Providers langchain-openai 1.x OpenAI model access [providers] extra
Model Providers langchain-anthropic 1.x Anthropic Claude models + prompt-caching middleware [providers] extra; default_anthropic_prompt_caching
Model Providers langchain-google-genai 4.2+ Google Gemini models [providers] extra
Model Providers tiktoken 0.7 Accurate token counting for the cost meter / budgets (falls back to len/4) forge/tracing/pricing.py
Tooling Primitives httpx 0.27 Outbound HTTP for REST/GraphQL tools, webhooks, web_fetch, OAuth/token fetches tool runtime + egress/SSRF guard
Tooling Primitives jsonschema 4.23 Validate node/tool config against the shared JSON Schemas packages/schemas + engine
Tooling Primitives jmespath 1.0 (py) JSON projection of tool outputs tool runtime
Tooling Primitives RestrictedPython 7.4 AST-sandboxed execution of code tools (opt-in; hardening layer, not OS isolation) code tool runtime (enable_code_tools)
MCP langchain-mcp-adapters 0.2 Consume external MCP servers as tools [mcp] extra
MCP mcp 1.9 Model Context Protocol SDK [mcp] extra
MCP fastmcp 3 Expose Forge projects as MCP servers [mcp] extra
Knowledge / RAG chromadb 1.5 Embedded persistent vector store (zero infra) [vectors] extra; .data/chroma
Knowledge / RAG fastembed 0.3+ Default embedder — local open-source ONNX model (no API cost / no PyTorch); also powers the local cross-encoder reranker (TextCrossEncoder) [knowledge] extra
Knowledge / RAG langchain-text-splitters 1.x Chunk/split documents for ingestion [knowledge] extra
Knowledge / RAG pypdf 5 Extract text from PDF documents [knowledge] extra
Knowledge / RAG beautifulsoup4 4.12 Parse HTML for URL ingestion [knowledge] extra
Knowledge / RAG rank-bm25 0.2 Lexical (BM25) ranking for hybrid vector + keyword search [knowledge] extra
Persistence SQLAlchemy 2.0 [asyncio] Async ORM / database access layer forge/models/entities.py
Persistence aiosqlite 0.20 Async SQLite driver (dev default) dev DB .data/forge.db
Persistence Alembic 1.14 Schema migrations (controlled prod path) apps/api/migrations, alembic.ini
Persistence greenlet 3.1 Async/sync bridge required by SQLAlchemy asyncio runtime dependency
Persistence SQLite Default dev database + checkpointer store .data/*.db (dev only)
Persistence PostgreSQL 16 Production application database + shared durable checkpointer docker-compose.yml (prod)
Persistence asyncpg 0.30 / psycopg[binary,pool] 3.2 Async Postgres drivers [postgres] extra (prod)
Persistence langgraph-checkpoint-postgres 2.x Durable Postgres checkpointer shared across workers (prod/HITL) [postgres] extra (prod)
Secrets / Auth cryptography 43 (Fernet) Encrypt stored secrets/credentials with a master key .data/master.key; secrets service
Secrets / Auth python-jose[cryptography] 3.3 Mint/verify platform JWT access + refresh tokens (with kid rotation) auth layer (forge/config.py JWT settings)
Secrets / Auth bcrypt 4 Password hashing for local accounts auth layer
Background / Workers Redis 7 Shared rate-limit / idempotency store + worker queue backend docker-compose.yml (prod); [workers] extra
Background / Workers arq 0.26 Async task queue + worker for offloaded run execution forge/worker.py, forge/queue.py; [workers]
Background / Workers croniter 26 Evaluate cron schedule triggers scheduler; [workers]
Observability opentelemetry-sdk 1.20 Emit run traces/spans (GenAI semantic conventions) forge/tracing/otel.py; [observability]
Observability opentelemetry-exporter-otlp-proto-http 1.20 Export spans to an OTLP collector / Langfuse forge/tracing/otel.py; [observability] (opt-in via otel_enabled)
Infra / Deploy Docker + Docker Compose Production-shaped container stack (postgres + redis + api + worker + web) repo root (docker-compose.yml, apps/*/Dockerfile)
Dev Tooling pytest 8.3 + pytest-asyncio 0.24 Backend test suite (async mode auto) apps/api/tests; [dev] extra
Dev Tooling anyio 4.6 Async test/runtime utilities [dev] extra
Dev Tooling ruff 0.15 (pinned >=0.15,<0.16) Linting + import sorting/formatting; range-pinned so CI lint is reproducible pyproject.toml [tool.ruff]; [dev] extra
Dev Tooling mypy 1.13+ Static type-checking (advisory in CI; gradual adoption) pyproject.toml [tool.mypy]; [dev] extra
Dev Tooling Vitest 2 + Testing Library Frontend unit/component tests (pnpm --filter web test) apps/web (devDependencies)

Notes

  • Local dev needs no external infra: SQLite + embedded Chroma + in-process (fake) cache/queue. The prod swaps — Postgres, Redis, OTLP, Vault/KMS — are configuration-only (no code changes).
  • Framework is MIT-only: LangChain/LangGraph OSS packages; deliberately not langgraph-api or LangSmith.
  • Optional extras map to pip install -e ".[...]" groups in pyproject.toml: providers, vectors, knowledge, mcp, workers, postgres, observability, dev (and all = vectors+providers+knowledge+mcp).

Architecture

How the pieces above fit together. The browser talks to a same-origin Next.js proxy (/api/forge/*) that rewrites to the FastAPI backend; every run — whatever triggers it — funnels through one RunService, and one ForgeTracer observes it.

flowchart TB
    subgraph Client["Client tier"]
        Browser["Browser — Next.js console<br/>(builder canvas · chat UI)"]
        Embed["Embed widget<br/>(anonymous · publishable key)"]
    end

    subgraph Edge["Edge / web server"]
        Next["Next.js 14 server<br/>same-origin proxy: /api/forge/* to API"]
    end

    subgraph Triggers["Trigger sources"]
        UI["Interactive run (SSE)"]
        WH["Webhook"]
        SCH["Schedule (cron)"]
        CH["Email channel"]
        MCPin["MCP server surface"]
    end

    subgraph API["FastAPI backend — apps/api/forge"]
        MW["Middleware<br/>Audit · TrustedHost · CORS · rate-limit<br/>auth: JWT / service token · X-Forge-Context"]
        Routers["Routers (/v1/...)"]
        Dispatch["Dispatch service"]
        RunSvc["RunService<br/>create_run · stream · run_to_completion · resume"]
        Compile["Engine: build_compile_context to compile_workflow"]
        Graph["LangGraph StateGraph — graph.astream()"]
        Tracer["ForgeTracer callback<br/>spans: tokens · cost · latency"]
    end

    subgraph Nodes["Graph nodes & tools"]
        Models["Model providers<br/>OpenAI · Anthropic · Google GenAI"]
        Tools["Tools: httpx REST/GraphQL · MCP client<br/>code (RestrictedPython) · knowledge/RAG"]
        Deep["deep_agent (Deep Agents)"]
    end

    subgraph DataTier["Persistence & state"]
        DB["SQLAlchemy to SQLite (dev) / Postgres (prod)<br/>Run · Trace · Span · Thread · AuditLog"]
        CP["LangGraph checkpointer<br/>SQLite / Postgres"]
        Vec["Chroma vector store (.data/chroma)"]
        Secrets["Fernet master.key (.data)"]
    end

    subgraph Async["Async / prod tier"]
        Redis["Redis — rate-limit · idempotency · queue"]
        Worker["arq worker (offloaded runs)"]
        OTel["OpenTelemetry to OTLP / Langfuse"]
    end

    Browser --> Next
    Embed --> Next
    Next --> MW
    UI --> MW
    WH --> Dispatch
    SCH --> Dispatch
    CH --> Dispatch
    MCPin --> Dispatch
    MW --> Routers --> RunSvc
    Dispatch --> RunSvc
    RunSvc --> Compile --> Graph
    Graph -. callbacks .-> Tracer
    Graph --> Models
    Graph --> Tools
    Graph --> Deep
    Tools --> Vec
    Tools -. encrypt/decrypt .-> Secrets
    RunSvc --> DB
    Graph --> CP
    Tracer --> DB
    Tracer --> OTel
    RunSvc <--> Redis
    Dispatch --> Worker
    Worker --> RunSvc
    Routers -. SSE frames .-> Next
    Next -. SSE .-> Browser

Example flow — a user sends a chat message

The interactive path (console or embed widget). Sending a message is two HTTP calls: a POST that creates the run row, then a GET that opens the SSE stream carrying tokens and lifecycle events back to the browser.

sequenceDiagram
    autonumber
    actor U as User
    participant B as Browser (chat UI)
    participant N as Next.js proxy
    participant M as FastAPI middleware
    participant R as Runs router
    participant S as RunService
    participant G as LangGraph astream
    participant T as ForgeTracer
    participant P as Model provider
    participant D as DB / checkpointer

    U->>B: type message, hit send
    B->>N: POST /v1/projects/{p}/workflows/{w}/runs
    N->>M: same-origin proxy to api:8000
    M->>R: authenticated request (audit · auth · rate-limit)
    R->>R: idempotency · run rate limit · identity · daily quota
    R->>S: create_run(input, thread_id)
    S->>D: INSERT Run (queued) + Thread
    S-->>R: run
    R-->>B: 201 { run_id, thread_id }

    B->>N: GET /v1/.../runs/{run_id}/stream  (EventSource / SSE)
    N->>R: proxy stream request
    R->>S: stream(run_id)
    S->>D: Run to running
    S->>G: build context to compile_workflow, astream(callbacks=[Tracer])
    activate G
    loop each node / token
        G->>T: on_llm_start / on_tool_start (open span)
        G->>P: LLM call
        P-->>G: tokens
        G-->>B: SSE: node_start · messages (tokens) · custom
        G->>T: on_llm_end (tokens · cost · latency)
    end
    deactivate G
    alt HITL interrupt
        S-->>B: SSE: interrupt (awaiting human input)
        Note over B,S: user approves to resume the same run
    else completed
        S->>D: _write_trace to Trace + Span rows (+ OTel export)
        S-->>B: SSE: done { answer, total_tokens, total_cost_usd }
    end
    B->>U: render streamed answer

Non-interactive triggers (webhook / schedule / email) skip the browser and the SSE stream: they enter through the Dispatch service and call RunService.run_to_completion() instead of stream() — but the compile → LangGraph → ForgeTracer → Trace/Span path is identical, which is what keeps observability consistent across every entry point.