
Research
/Security News
OpenAPI React Query Codegen Compromised in Mini Shai-Hulud npm Supply Chain Attack
Ten malicious OpenAPI React Query Codegen versions were published to npm in the Mini Shai-Hulud attack, all with valid provenance.
Embeddable workbench for LangGraph Agent apps — edit, run, debug, and monitor in one place
Embeddable workbench for LangGraph Agent applications — design topology on canvas, implement logic in code, compile into executable graphs.
| 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 |
npm install linforge
# or
pnpm add linforge
Peer dependencies (install only what you need):
# Server
pnpm add koa @koa/router
# UI
pnpm add react react-dom @xyflow/react
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.
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)
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]
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,
},
});
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 |
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.
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,
},
}));
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 |
For full control over component creation. See examples/full-stack/ for a complete example.
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.
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 |
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.
MIT
FAQs
Embeddable workbench for LangGraph Agent apps — edit, run, debug, and monitor in one place
The npm package linforge receives a total of 0 weekly downloads. As such, linforge popularity was classified as not popular.
We found that linforge demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Research
/Security News
Ten malicious OpenAPI React Query Codegen versions were published to npm in the Mini Shai-Hulud attack, all with valid provenance.

Security News
Socket joins more than 100 technology, cybersecurity, and financial organizations calling for a global surge in cyber defense.

Product
Enterprise security teams can now detect malware, credential theft, suspicious network activity, and risky updates across Microsoft Edge extensions.