Skip to content
erwinmsmithPublic

About

An agent-native development node framework for on-demand scaling and low-cost evolution of agent structures.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Ditto logo

Ditto

A lightweight Node-native Runtime for composable Agent systems.
Scale Workers without changing Graphs or Node Contracts.

English | 简体中文

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/ditto

Save 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-retrieval

Import 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 cache LOOKUP, WRITE, and INVALIDATE;
  • 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 folders

The 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 check

Install 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

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.

About

An agent-native development node framework for on-demand scaling and low-cost evolution of agent structures.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages