
Ditto
A lightweight Node-native Runtime for composable Agent systems.
Scale Workers without changing Graphs or Node Contracts.
The definition is Node System and API Contract. It contains the final Node tree, semantic boundaries, fixed shared types, and every public Node input/output contract.
Developer handbook · Build the documentation site
Install and use
Requires Node.js 24+ and npm 11+. Ditto ships ESM JavaScript with TypeScript declarations.
npm init -y
npm pkg set type=module
npm install @codesoul-co/dittoSave this as app.mjs and run node app.mjs:
import { createDitto, createContextWorker, graph } from "@codesoul-co/ditto";
const runtime = createDitto({ workers: [createContextWorker()] });
const plan = graph("hello")
.node("loaded", "CONTEXT.LOAD", [], text => ({
sources: [{ role: "user", content: text }],
}))
.node("selected", "CONTEXT.SELECT", ["loaded"], (_input, { loaded }) => ({
context: loaded, purpose: "infer", limit: 1,
}));
try {
const result = await runtime.run(plan, "Hello Ditto");
console.log(result.selected.context.items[0].content);
} finally { await runtime.close(); }This first Graph requires no model, Redis or configuration file. For a complete Agent, use real model configuration, Redis Context and database Memory as shown in the detailed package guide (中文). It covers installation, strict TypeScript, every execution entry, result handling, tools, Loop composition, storage, recovery, output and troubleshooting.
The four runnable beginner examples demonstrate Context, tools, optional retrieval and a persistent multi-turn Agent. The guide includes exact commands to copy application adapters into an independent npm project; example business functions are not package exports.
Install retrieval separately when needed:
npm install @codesoul-co/ditto-retrievalImport its Worker from @codesoul-co/ditto-retrieval. Runtime and built-in Worker entries such as @codesoul-co/ditto/runtime and @codesoul-co/ditto/worker/memory belong to the main package. Do not import private src or dist paths.
Architecture
Ditto separates four concepts:
- Node: the smallest routable semantic operation;
- Worker: the implementation, resource, deployment, and scaling boundary;
- Execution Graph: a location-independent composition of Nodes;
- Runtime: scheduling, routing, communication, and execution.
Changing a model, database, tool Provider, deployment location, or replica count does not create a new Node Type. Runtime communication uses invoke for request/response and emit for asynchronous events; neither is an Interaction Node.
Final Node Domains
INFER.REASONING.*: explicit reasoning organization (TRAJECTORY,REFLECT,DELIBERATE,SAMPLE);INFER.CACHE.*: inference cacheLOOKUP,WRITE, andINVALIDATE;CONTEXT.*: the current invocation/turn working set, including task-local RAG and activated Skills;MEMORY.*: durable storage and search through GET / QUERY / SEARCH / WRITE / UPDATE / DELETE;INTERACTION.ACT.TOOL/INTERACTION.ACT.MCP: external actions;INTERACTION.OBSERVE/INTERACTION.OUTPUT: normalized observations and final output.
INFER/PROVIDERS is an implementation directory, not a Node. INFER/REASONING is also a source directory rather than a REASONING Node. Inject tools through createInteractionWorker({ tools, mcp, output }); individual tools, Linux commands, and web search providers do not create additional Node Types. createReadOnlyCommandTools() offers 14 optional bounded search, reading, text-processing, metadata, disk-usage, and workspace-location commands. createWebSearchTool() accepts an application-injected provider; createBraveWebSearchProvider() is the first native-fetch adapter. Neither helper is registered or permitted by default.
RETRIEVAL is an optional independently deployable search Worker exposing only RETRIEVAL.SEARCH. Import and register @codesoul-co/ditto-retrieval explicitly; Core does not load it by default. Existing direct MEMORY/CONTEXT providers remain available. See the RETRIEVAL API.
Predefined Runtime Flows
Four public compositions live directly in src/runtime/graph.ts and are exported from @codesoul-co/ditto/runtime:
runRagFlow CONTEXT.SELECT (rag strategy)
runSkillFlow CONTEXT.LOAD -> CONTEXT.UPDATE (when context is supplied)
runToolCallFlow INTERACTION.ACT.TOOL -> INTERACTION.OBSERVE -> CONTEXT.UPDATE
runMcpFlow INTERACTION.ACT.MCP -> INTERACTION.OBSERVE -> CONTEXT.UPDATE (invoke)These Runtime functions use explicit Context. RAG is an internal SELECT strategy; applications resolve Skill content for LOAD/UPDATE. See the CONTEXT API for cached calls, Redis and examples.
import { runRagFlow, runToolCallFlow } from "@codesoul-co/ditto/runtime";
const retrieved = await runRagFlow(runtime, {
context: { items: [] },
query: "Find the relevant API definition",
corpus: { uri: "urn:contracts" }, // Resolved by the configured ragStrategy.
});
const toolResult = await runToolCallFlow(runtime, {
context: retrieved.context,
call: { id: "read-1", name: "read_text", arguments: { path: "README.md" } },
});Graphs and Workers
Graphs contain semantic Node Types and data bindings, never Worker IDs or network addresses:
import { randomUUID } from "node:crypto";
import { graph, type Message } from "@codesoul-co/ditto";
const review = graph<Message>("review")
.node("memories", "MEMORY.GET", [], () => ({
keys: ["review-policy"],
}))
.node("context", "CONTEXT.LOAD", ["memories"], (query, { memories }) => {
if (memories.status !== "success" || !memories.output) throw new Error("Memory read failed");
return { sources: [query, ...memories.output.map(memory => ({
id: memory.id, content: typeof memory.content === "string" ? memory.content : JSON.stringify(memory.content),
}))] };
})
.node("reason", "INFER.REASONING.TRAJECTORY", ["context"], (_query, { context }) => ({
messages: [{ role: _query.role, content: typeof _query.content === "string"
? _query.content : JSON.stringify(_query.content) }],
context: context.items.map(item => ({ id: item.id, content: item.content })),
model: { model: "your-model-name" },
strategy: { name: "cot" },
}))
.node("output", "INTERACTION.OUTPUT", ["reason"], (_query, { reason }) => {
if (reason.status !== "success" || reason.output?.status !== "completed") {
throw new Error(reason.error?.message ?? "Trajectory incomplete");
}
return { deliveryId: randomUUID(), message: { role: reason.output.result.role,
content: typeof reason.output.result.content === "string"
? reason.output.result.content : JSON.stringify(reason.output.result.content) } };
});Registering more Worker replicas adds capacity without changing this Graph. The same contracts support local execution, multiple Workers, multiple processes, or custom remote transports.
See the INFER Worker API for setup and all seven leaf contracts.
Define an Agent with Graph → Loop → Worker; run the complete graph-loop-worker.ts example using npm run example:agent. See Interaction setup for Tool and MCP wiring.
Repository Structure
src/
├── contracts/ # shared fixed types and open NodeContractMap
├── runtime/
│ ├── graph.ts # DAG plus four predefined flows
│ ├── runtime.ts # routing and lifecycle
│ └── communication/ # invoke/emit, transports, artifacts
└── worker/
├── infer/
│ ├── reasoning/ # reasoning leaves and node scaffolds
│ ├── cache/ # LOOKUP / WRITE / INVALIDATE
│ └── providers/ # shared provider registry and wire protocols
├── context/
├── memory/
└── interaction/act/tool/ # Tool Node, registry, implementation foldersThe optional SEARCH Worker lives in packages/retrieval/ and is published separately as @codesoul-co/ditto-retrieval.
Core uses only the yaml parser as a third-party runtime dependency. Heavy RPC, event buses, MCP SDKs, database drivers, and model SDKs remain optional application/adapter choices.
Development
Requirements: Node.js 24+ and npm 11+.
npm ci
npm run checkInstall the Runtime with npm install @codesoul-co/ditto. Applications that use the optional search Worker also install @codesoul-co/ditto-retrieval.
See the Runtime API and complete examples for node bindings, independent sandboxes, loops and local IPC / cross-host HTTP.
Documentation
- Examples: control flow, capabilities and execution patterns (run
npm run example:quickstartfor a local introduction) - Node System and API Contract
- Architecture and extension boundaries
- Development and package integration
- Worker communication and deployment
- Providers, Interaction, and predefined flows
Use GitHub Issues for concrete use cases, bugs, and architecture discussions.
Behavior defaults live in root ditto.yaml; credentials and deployment bindings use .env.example. All Workers share Runtime services; see the configuration API. See the INFER example guide for setup and commands.
See the MEMORY API for plugin wiring, six node contracts and configuration.
More public APIs and examples: Worker composition / events / Artifacts, predefined flows, and the local quickstart.
Portable JSON checkpoints, isolated state branches and shared token budgets are available from the root package. See checkpoint and budget contracts for examples and external-resource limitations.