A TypeScript library ecosystem for building inspectable transformation graphs.
input → transforms → output → evaluation → feedback
A model graph makes transformation chains explicit, records intermediate state, evaluates outputs, and can return feedback actions.
- typed transforms
- inspectable graph runs
- traceable intermediate states
- evaluation-first outputs
- optional feedback actions
- not an ML framework
- not an agent framework
- not a workflow engine
- not a harness
- not LangChain
- not a graph database
| Concept | Definition |
|---|---|
| Transform | the thing that maps input to output |
| Data | the thing that flows through transforms |
| Graph | the composition of transforms |
| Trace | recorded intermediate state |
| Evaluation | judgment of output quality |
| Feedback | next action suggested by evaluation |
pnpm install
pnpm build
pnpm testimport {
createModelGraph,
createTransform,
} from "@composable-model-graph/core";
const normalize = createTransform<string, string>({
id: "normalize",
name: "Normalize",
run: (input) => input.trim().toLowerCase(),
});
const tokenize = createTransform<string, string[]>({
id: "tokenize",
name: "Tokenize",
run: (input) => (input === "" ? [] : input.split(/\s+/)),
});
const graph = createModelGraph<string, string[]>({
id: "tokens",
name: "Tokenizer",
transforms: [normalize, tokenize],
});
const run = await graph.run(" Hello World ");
console.log(run.output); // ["hello", "world"]
console.log(run.trace); // every intermediate stateThe library is a single package: the model graph.
@composable-model-graph/core—Transform,ModelGraph,Evaluator,FeedbackResolver,TraceStep,RunContext,GraphRun, and thecreateTransform/createEvaluator/createFeedbackResolver/createModelGraph/createRunContextfactories.
It has no dependencies and depends on no harness package. Domain-specific building blocks (numeric layers, data transforms, evaluator/feedback libraries) are intentionally out of scope — they belong in consumers built on top of these primitives.
Input ───▶ Transform ───▶ Output
Input
↓
Transform A
↓
State A
↓
Transform B
↓
State B
↓
Transform C
↓
Output
Input
↓
Transform Chain
↓
Output
↓
Evaluator
↓
EvaluationResult
Input
↓
Transform Chain
↓
Output
↓
Evaluation
↓
Feedback
├── accept
├── retry
├── adjust
├── reject
└── custom
Raw Input Data
↓
Normalize
↓
Data State 1
↓
Extract / Pick
↓
Data State 2
↓
Structure
↓
Built Data
↓
Evaluate
↓
Feedback
- Data is not the transform.
- Data flows through transforms.
- The graph turns raw state into useful state.
- The trace exposes each intermediate state.
D0 = raw input data
D1 = normalize(D0)
D2 = extract(D1)
D3 = structure(D2)
BuiltData = D3
Evaluation = E(D3)
Input Data Building is the process of turning raw state into useful state through explicit, inspectable transforms.
See docs/03-input-data-building-model.md.
Input Vector
↓
Dense Layer
↓
Activation
↓
Hidden Representation
↓
Dense Layer
↓
Prediction
↓
Loss / Error
This is only one illustrative instance of the generic graph. The library does not ship numeric layers; you implement transforms like these in a consumer.
Input
├──▶ Transform B ──┐
│ ├──▶ Merge ──▶ Output
└──▶ Transform C ──┘
Pass connections to createModelGraph to run transforms as a branch/merge
graph instead of a straight line. A transform runs once every transform feeding
it has produced output; transforms whose inputs are all ready run together. A
transform fed by several others (a merge) receives the list of their outputs,
in connection order.
const graph = createModelGraph({
id: "branch-merge",
name: "Branch then merge",
transforms: [start, draftB, draftC, merge],
connections: [
{ from: "start", to: "draftB" },
{ from: "start", to: "draftC" },
{ from: "draftB", to: "merge" },
{ from: "draftC", to: "merge" },
],
});The graph input goes to the single transform with no incoming connection, and
the graph output is the single transform with no outgoing connection (set
start / end explicitly when there is more than one). Cycles are not allowed:
repeating a run belongs to the layer above the graph.
Input
├──▶ Graph A ──▶ Output A ──┐
│ ├──▶ Compare / Evaluate
└──▶ Graph B ──▶ Output B ──┘
Raw Run Data
↓
Extract Signals
↓
Measured State
↓
Evaluate
↓
Evaluation Result
↓
Resolve Feedback
↓
Next Action
Composable Model Graph does not define what a "run" is. It only provides the primitives to model a lifecycle. Some systems improve not by generating better outputs directly, but by making the lifecycle visible: what happened, what signals were measured, what violated expectations, and what should update next.
Runnable examples live in examples/. Build first, then run any
example:
pnpm build
pnpm --filter @composable-model-graph/example-05-input-data-building startSee docs/06-examples.md for the full list.
- 00 - Overview
- 01 - Core Primitives
- 02 - Model Shapes
- 03 - Input Data Building Model
- 04 - Evaluation and Feedback
- 05 - The Library
- 06 - Examples
composable-model-graph/
package.json
pnpm-workspace.yaml
tsconfig.base.json
vitest.config.ts
README.md
packages/
core/
examples/
01-linear-transform/ ... 08-comparison-model/
docs/
00-overview.md ... 06-examples.md
pnpm install # install dependencies
pnpm build # build all packages (tsc -b project references)
pnpm test # run the full vitest suite
pnpm lint # eslint
pnpm format # prettier --writeMIT