linforge
v0.3.2
Published
Embeddable workbench for LangGraph Agent apps — edit, run, debug, and monitor in one place
Downloads
498
Maintainers
Readme
Linforge
Embeddable workbench for LangGraph Agent applications — design topology on canvas, implement logic in code, compile into executable graphs.
Why Linforge
| LangGraph Studio (official) | Linforge | |------------------------------|----------| | Desktop app | Web-embeddable — integrates into your product UI | | Code-only graph definition | Visual canvas + code hybrid | | Dev-only tool | Product managers sketch graphs, developers implement nodes | | Dev-time debugging | Built-in run history, step replay, and prompt versioning |
Install
npm install linforge
# or
pnpm add linforgePeer dependencies (install only what you need):
# Server
pnpm add koa @koa/router
# UI
pnpm add react react-dom @xyflow/reactQuick Start
A minimal server in ~30 lines:
import Koa from 'koa';
import cors from '@koa/cors';
import bodyParser from 'koa-bodyparser';
import { StateSchema } from '@langchain/langgraph';
import { z } from 'zod/v4';
import { defineNodeFor } from 'linforge/core';
import { linforgeMiddleware } from 'linforge/server';
// 1. Define state
const MyState = new StateSchema({
messages: z.array(z.string()).default([]),
result: z.string().default(''),
});
// 2. Define nodes — state type is inferred automatically
const defineMyNode = defineNodeFor(MyState);
const greeter = defineMyNode({
key: 'greeter',
label: 'Greeter',
run: async (state) => ({
messages: [...state.messages, '[greeter] Hello!'],
result: 'Greeting complete.',
}),
});
// 3. Start server
const app = new Koa();
app.use(cors());
app.use(bodyParser());
app.use(linforgeMiddleware({
stateSchema: MyState,
nodes: [greeter],
}));
app.listen(3001);This gives you a full REST API at http://localhost:3001/linforge — graph CRUD, run execution, step recording, and prompt management.
Package Exports
linforge Re-exports all submodules
linforge/core defineNode, Registry, Compiler, RunManager, PromptLoader, Store interfaces
linforge/server Koa HTTP API (linforgeMiddleware + mountRoutes)
linforge/react React hooks for graph editing, runs, and prompts
linforge/testing In-memory Store implementations (dev/test)Core Concepts
Three-Layer Architecture
Code Layer: Node implementations + StateSchema + Route predicates [developer]
Visual Layer: Topology + Wiring + Prompts + Parameters [canvas / DB]
Auto Layer: Compiler merges both into a LangGraph Runnable [linforge]defineNode
Create node implementations with defineNode() or the type-safe defineNodeFor():
import { defineNodeFor } from 'linforge/core';
const defineMyNode = defineNodeFor(MyState);
const planner = defineMyNode({
key: 'planner',
run: async (state) => ({
plan: 'Step 1: fetch. Step 2: analyze.',
}),
// Optional: conditional routing
routes: {
has_plan: (state) => !!state.plan,
},
});Store Interfaces
Four database-agnostic interfaces — implement them to plug in any database:
| Interface | Purpose |
|-----------|---------|
| GraphStore | Graph definition CRUD |
| RunStore | Run record lifecycle |
| StepPersister | Step data write/query |
| PromptStore | Prompt version management |
PromptLoader
createPromptLoader(store) creates a cached prompt loader with Mustache template rendering:
import { createPromptLoader, renderPrompt } from 'linforge/core';
const loader = createPromptLoader(promptStore);
// Load active prompt, render variables, with fallback
const result = await loader.render('node-id', { name: 'World' }, {
template: 'Hello {{name}}!',
temperature: 0.7,
});
// result.text → "Hello World!"
// result.source → 'store' | 'fallback'
// Pure function (no store needed)
const text = renderPrompt('Hello {{name}}!', { name: 'World' });Linforge ships in-memory implementations (linforge/testing) for development. For production, use an adapter package (e.g., linforge-adapter-prisma) or implement the interfaces yourself.
Server API
linforgeMiddleware (recommended)
One-line setup that auto-creates all internal components:
// Single-agent mode (backward compatible)
app.use(linforgeMiddleware({
stateSchema: MyState, // required — LangGraph StateSchema
nodes: [planner, tools], // required — node definitions
prefix: '/linforge', // default: "/linforge"
stores: { // optional — custom Store implementations
graphStore,
runStore,
stepPersister,
promptStore,
},
}));Multi-Agent mode
Bind distinct stateSchema and nodes per Agent:
app.use(linforgeMiddleware({
agents: [
{ slug: 'qa-bot', name: 'QA Bot', stateSchema: QAState, nodes: [retriever, answerer] },
{ slug: 'coder', name: 'Coder', stateSchema: CoderState, nodes: [planner, coder] },
],
sharedNodes: [logger], // available to all agents
}));Each agent gets its own NodeRegistry and GraphCompiler. Routes resolve context by slug. The middleware auto-creates empty graph definitions in GraphStore on first request (code-first mode).
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| agents | AgentConfig[] | — | Multi-agent config (mutually exclusive with stateSchema/nodes) |
| sharedNodes | NodeDefinition[] | [] | Common nodes registered to every agent |
| stateSchema | StateSchema | — | Single-agent: LangGraph StateSchema |
| nodes | NodeDefinition[] | — | Single-agent: node definitions |
mountRoutes (low-level)
For full control over component creation. See examples/full-stack/ for a complete example.
REST Endpoints
All endpoints are prefixed with /linforge by default.
| Method | Path | Description |
|--------|------|-------------|
| GET | /graphs | List graphs |
| POST | /graphs | Create graph |
| PATCH | /graphs/:slug | Update graph metadata |
| GET | /graph/:slug | Get graph definition |
| PUT | /graph/:slug | Save graph definition |
| POST | /graph/:slug/run | Trigger a run |
| GET | /graph/:slug/runs | List run history |
| GET | /runs/:runId | Get run details |
| GET | /runs/:runId/steps | List steps for a run |
| GET | /registry/nodes | List registered nodes |
| GET | /prompts/:nodeId | List prompt versions |
| GET | /prompts/:nodeId/active | Get active prompt |
| POST | /prompts/:nodeId | Create prompt version |
| POST | /prompts/:nodeId/versions/:id/activate | Activate prompt version |
| GET | /templates | List available templates |
| POST | /graph/:slug/apply-template | Apply template to graph |
Optional stores (runStore, stepPersister, promptStore) return 501 when not provided.
React Hooks
import {
useLinforgeGraph,
useLinforgeGraphList,
useLinforgeRuns,
useLinforgePrompt,
} from 'linforge/react';| Hook | Purpose |
|------|---------|
| useLinforgeGraph | Graph editing (nodes, edges, save) |
| useLinforgeGraphList | Graph list CRUD |
| useLinforgeRuns | Run management (trigger, list, detail) |
| useLinforgePrompt | Prompt version management |
Custom Store Adapter
Implement the four interfaces to connect any database:
import type { GraphStore, RunStore, StepPersister, PromptStore } from 'linforge/core';
class MyGraphStore implements GraphStore {
async getGraph(slug: string) { /* ... */ }
async saveGraph(graph) { /* ... */ }
async listGraphs() { /* ... */ }
}
// Pass to middleware
app.use(linforgeMiddleware({
stateSchema: MyState,
nodes: [planner],
stores: { graphStore: new MyGraphStore() },
}));See linforge-adapter-prisma for a reference implementation.
Examples
- Quick Start — Minimal server (~30 lines)
- Full Stack — Manual assembly with all components
License
MIT
