Deep multi-agent orchestration with a great visual UX — author, run, and self-host the whole thing from one repo.
Construct is an open-source platform for building complex AI agents. You chain LLM nodes — orchestrators, routers, tools, retrievers, and sub-agents — into runnable flows on a visual canvas, then run them locally or self-host the engine behind an API.
The DSL is the spine: a flow is data — a versioned, validated document
(@construct/dsl). The editor authors it, validateFlow checks it, the engine
executes it, and every extension point (providers, tools, vector stores) is
referenced from it by name. No part of the stack is privileged.
Design principles
- One stable contract. Editor, engine, and tooling all target the same schema.
- Open graph, typed built-ins. Any node
typeis allowed for plugins; the built-in catalog ships typed config schemas and declared output handles. - Control flow is intrinsic; leaves are pluggable. Branching, loops, fan-out, joins, sub-flows, and human pauses live in the runtime. Models, tools, and retrieval are leaves resolved through a registry.
- Safety is first-class. Tools carry a tier (read → dangerous); write-class actions can be routed through a human-approval node before they run.
- The editor is an embeddable library.
@construct/editorexports a single<ConstructEditor>you drop into any React host; it extends by composition (props, slots, a DSL facade) so downstream apps never fork it.
@construct/editor ── <ConstructEditor> embeddable React canvas
(host: apps/editor) │ serializes to
▼
@construct/dsl ── the versioned Flow contract
│
┌──────────────────┼───────────────┐
▼ ▼ ▼
validate engine nodes
(runtime) (agent / classifier / tool / retrieve)
│ uses
┌─────────────┼──────────────┐
▼ ▼ ▼
providers tools rag
(LLMs) (+ MCP) (vector stores)
@construct/sdk — fluent SDK to author flows in code (compiles to the contract)
@construct/server — host the engine behind a REST API + SSE + persistence
@construct/client — programmatic client for that API
- Node >= 22 (
.nvmrcpins the version —nvm useif you have nvm) - Yarn 1.x (the repo is a Yarn + Turborepo monorepo)
git clone https://github.com/Darginec05/construct.git
cd construct
yarn install
yarn dev # turbo run dev — starts the editor playground + watch buildsyarn dev brings up the visual editor at http://localhost:5173 (the
apps/editor playground host). The canvas, inspector, live validation, and reader
view work out of the box against a simulated model provider — no API keys required
to explore.
Common workspace tasks (all via Turborepo):
yarn build # build every package
yarn typecheck # type-check the whole monorepo
yarn test # run the test suites
yarn lintBy default the editor is a pure sandbox: runs use a simulated provider and
Publish is disabled. To target a real self-hosted engine
(@construct/server), point the playground at it via build-time env. Copy
apps/editor/.env.example to apps/editor/.env and set:
VITE_CONSTRUCT_SERVER_URL=http://localhost:8787
VITE_CONSTRUCT_API_KEY=secret # only if the server requires a tokenThe host (
apps/editor) is the only place that reads env. The@construct/editorlibrary never touchesVITE_*— the host constructs aConstructClientand injects it, so no key is ever baked into the library bundle.
@construct/editor exports one component plus a DSL facade. The host owns the
server connection, the initial flows, and persistence; the editor owns the canvas.
import { ConstructEditor, type WorkspaceFlowInput, type WorkspaceFlow } from "@construct/editor";
import "@construct/editor/styles.css";
const initialFlows: WorkspaceFlowInput[] = [
{
kind: "main",
flow: {
id: "main",
name: "Assistant",
nodes: [
{ id: "in", type: "input", config: { schema: { message: "text" } }, position: { x: 0, y: 120 } },
{ id: "ag", type: "agent", config: { model: { provider: "anthropic", model: "claude-sonnet-4-6" }, prompt: "{{ $.message }}", writeTo: "reply" }, position: { x: 320, y: 120 } },
{ id: "out", type: "output", config: { from: "$.reply" }, position: { x: 640, y: 120 } },
],
edges: [
{ id: "e1", source: "in", target: "ag" },
{ id: "e2", source: "ag", target: "out" },
],
},
},
];
export function App() {
return (
<ConstructEditor
client={null} // null = sandbox; inject a ConstructClient to enable Publish
initialFlows={initialFlows} // uncontrolled seed, read once at mount
onFlowChange={(flow: WorkspaceFlow) => save(flow)} // debounced, per changed flow, speaks DSL
/>
);
}Tailwind hosts can pull in the editor's design tokens via the shipped preset:
// tailwind.config.js
import editorPreset from "@construct/editor/tailwind-preset";
export default {
presets: [editorPreset],
content: ["./index.html", "./src/**/*.{ts,tsx}"],
};Extension seams. The editor exposes two stable seams so downstream apps (e.g. a hosted copilot) extend it without forking:
slots.copilot— a mount point that replaces the right-dock "Copilot" tab. Pass your own panel; it renders inside the editor's provider tree, so it can drive the canvas.useEditorApi()— a facade over the canvas that speaks the canonical DSL:getActiveFlow(): Flow,applyFlow(flow)(one undo commit + revalidate),focusNode(id),getIssues().
import { ConstructEditor, useEditorApi } from "@construct/editor";
function MyCopilot() {
const api = useEditorApi();
// read the active flow as DSL, send it somewhere, then apply a patched flow back
const flow = api.getActiveFlow();
// ...
// api.applyFlow(patchedFlow);
}
<ConstructEditor client={null} slots={{ copilot: <MyCopilot /> }} />;Flows are usually drawn on the canvas, but @construct/sdk lets you build the same
contract fluently in TypeScript. Declare channels, chain nodes (edges auto-wire),
then toJSON() to the document the editor stores — or run() it locally.
import { anthropic, defineFlow } from "@construct/sdk";
export const echo = defineFlow("echo", "Echo agent", (f) => {
const message = f.text("message");
const reply = f.text("reply");
f.input({ channel: message })
.agent({ model: anthropic("claude-sonnet-4-6"), prompt: message, writeTo: reply })
.to(f.output(reply));
});
const doc = echo.toJSON(); // canonical Flow — round-trips to the canvas
const result = await echo.run({ message: "hi" }); // execute locallySee packages/sdk/examples for production-shaped flows
(CRM agent, code agent, website builder) authored this way.
packages/
dsl/ @construct/dsl flow schema, types, validation (the contract)
engine/ @construct/engine stateful-graph runtime
nodes/ @construct/nodes built-in node library (executors)
providers/ @construct/providers model-provider abstraction
tools/ @construct/tools tool contract + registry + built-ins
rag/ @construct/rag ingestion, chunking, vector adapters
mcp/ @construct/mcp MCP client + tool adapter
sdk/ @construct/sdk fluent SDK to author flows in code
client/ @construct/client programmatic client for the server API
server/ @construct/server REST API + SSE streaming + persistence
editor/ @construct/editor embeddable <ConstructEditor> React library
apps/
editor/ @construct/playground thin host that mounts <ConstructEditor> (owns env, demo flows)
docs/ @construct/docs documentation site
Dependencies point inward toward the DSL. engine depends only on dsl;
nodes depends on dsl + engine + providers + tools + rag. The
@construct/editor library is consumed from source inside the monorepo
(exports["."] = "./src/index.ts"); nothing the runtime depends on imports the
editor.
An honest snapshot — ✅ implemented · 🚧 partial · 📋 planned. See roadmap.md for detail.
| Package | Status |
|---|---|
@construct/dsl |
✅ Full Flow/node schemas, validateFlow, resolveNodeOutputs |
@construct/engine |
✅ Worklist runner: channels, branch/switch, joins, loops, fan-out, sub-flows, human pauses |
@construct/nodes |
✅ agent, classifier, tool, retrieve executors |
@construct/providers |
✅ Anthropic, OpenAI, Gemini, Fake |
@construct/tools |
✅ Tool contract with tiers + approval, registry, built-ins |
@construct/rag |
🚧 Vector-store contract + in-memory store; no embeddings yet |
@construct/sdk |
✅ Fluent authoring SDK: defineFlow/defineTool/defineNode, compiles to the contract, runs locally |
@construct/client |
✅ Thin client: run, runStream (SSE), saveFlow |
@construct/server |
✅ Hono REST API (flows CRUD, runs, SSE streaming), bearer auth, in-memory + SQLite stores |
@construct/mcp |
✅ MCP client + adapter mounting MCP server tools into the registry |
@construct/editor |
🚧 Embeddable <ConstructEditor>: canvas, inspector, live validation, reader view; slots.copilot + useEditorApi() seams; initialFlows/onFlowChange |
apps/editor (@construct/playground) |
✅ Reference host that mounts the editor and owns env + demo flows |
| Doc | What it covers |
|---|---|
| architecture.md | Big picture, scope, data & control flow |
| dsl.md | The Flow contract: schema, node catalog, validation |
| engine.md | Runtime semantics: channels, joins, loops, pauses |
| nodes-and-providers.md | Built-in executors and the provider abstraction |
| tools-integrations-mcp.md | Tool contract, tier safety model, MCP |
| rag.md | Retrieval: documents, chunking, vector stores |
| sdk-and-server.md | Programmatic client and the self-hosted API |
| editor.md | The visual flow editor (React) |
Contributions are welcome. Start with the Contributing Guide for local setup, the project conventions, and how to add a node, provider, or tool.
Apache License 2.0 — the engine, DSL, nodes, providers, tools, RAG, SDK,
server, and editor are all permissively licensed. A few things (an AI copilot that
generates/edits flows, and multi-tenant managed hosting) are intentionally out of
scope for this repo; see architecture.md. The editor
ships the seams for that copilot (slots.copilot, useEditorApi()), but the
copilot implementation itself lives downstream.