Little documentation
Guides and APIInternals

Architecture

The layers, module layout, and dependency graph for the alpha SDK — now built around the Harness primitive.

Little Workflow: published on npm as little-workflow@0.2.0-alpha.1 (2026-10-06). Sections labeled UNRELEASED describe source-only changes. Release status

The alpha SDK ships as a single @little-stack/workflow package. This milestone introduces the Harness as the agent-runtime primitive that unifies Planner, Orchestrator, Worker, and Fixer roles under one contract. The four architectural layers below reflect the new design.

The Four Layers

┌─────────────────────────────────────────────────────┐
│  Control Plane                                       │
│  defineWorkflow()  runWorkflow()                     │
│  WorkflowDefinition  WorkflowDefinitionHash          │
└────────────────────┬────────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────────┐
│  Harness Layer                                       │
│  Harness contract  workflowHarness (default)             │
│  Roles: Planner · Orchestrator · Worker · Fixer      │
│  harness.session.* / harness.model.* / harness.tool_call.* │
└────────────────────┬────────────────────────────────┘
                     │ spawns
┌────────────────────▼────────────────────────────────┐
│  Sub-Run Layer                                       │
│  Orchestrator → plan_workflow / run_workflow          │
│  Sub-workflows: own WorkflowVersion + event log      │
│  Per-step LWIR scheduler (unchanged)       │
└────────────────────┬────────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────────┐
│  Storage Layer                                       │
│  LocalWorld (events/ artifacts/ locks/)              │
│  Memory Stores (/mnt/memory/workflow|pipeline|org/)  │
│  Run Scratch (/mnt/scratch/own|from-*/peer-steps/)   │
└─────────────────────────────────────────────────────┘

Control Plane

defineWorkflow() produces a WorkflowDefinition. A WorkflowDefinitionHash (new in this milestone — see Identity + Manifests) is computed once at authoring time: it is the sha256 of the definition's identity-bearing fields (planner config, models, schemas, tools, memory config). This is distinct from WorkflowVersion, which is the per-run LWIR hash (unchanged).

Two entry points start a run:

  • runWorkflow(workflow, input, options?) → spawns a Planner harness session that drafts LWIR (a fixed-plan workflow's planner returns its plan without a model call), then executes steps via Worker harness sessions.
  • runOrchestration({ workflows: [w1, w2, ...], orchestrator, input }) (deprecated) → spawns an Orchestrator harness session that composes sub-workflow runs via tool calls (plan_workflow, run_workflow, start_workflow).

Harness Layer

Every agent role — Planner, Orchestrator, Worker, Fixer — lives in a Harness session. A harness is an agent runtime: model + bash + tools + memory/scratch mounts + skills + event recording. The SDK ships workflowHarness as the default; devs substitute via the same Harness contract.

type Harness = {
  run(task: HarnessTask, ctx: HarnessContext): Promise<HarnessResult>;
};

type HarnessTask =
  | { kind: "plan";         workflowSnapshot; input }
  | { kind: "orchestrate";  available; input }
  | { kind: "execute_step"; step; stepInput }
  | { kind: "fix_step";     step; error; attempts };

One contract, four task kinds. See Harness for the full spec.

Worker unification: the prior alpha had separate RuntimeAiAdapter, RuntimeCodeRunner, and tool-dispatch paths. All three are now unified through the worker harness. ai.generate steps make one model call with no tools; code.run steps validate and compile, but the default worker harness refuses to execute them (it checks the step's sandbox policy and then throws), so running one needs a custom worker.harness that implements execute_step; tool.call steps invoke the named tool directly. The harness emits the same event vocabulary for all.

Sub-Run Layer

When the Orchestrator calls run_workflow, the SDK creates a sub-run with its own runId, its own event log, and its own per-step LWIR scheduler (unchanged). Sub-runs are fully independent for durability: a sub-run crash resumes via its own event log without touching the orchestrator's session.

The orchestrator's harness records harness.tool_call.started with args.subRunId and harness.tool_call.succeeded with result.runId. Two levels of memoization compose cleanly — the orchestrator's session memoizes completed tool calls; each sub-run memoizes completed LWIR steps.

See Runtime Execution and Replay for how both levels interact.

Storage Layer

Three storage surfaces, all filesystem-backed in alpha (localWorld):

SurfacePathScopeNotes
Event log<dataDir>/events/events.dbPer run idSQLite/WAL, append-only event rows
Artifacts<dataDir>/artifacts/Per runContent-addressed blobs
Memory Stores<dataDir>/memory/workflows/<id>/, pipelines/<key>/, org/Cross-runMounted at /mnt/memory/ inside harness sessions
Run Scratch<dataDir>/runs/<runId>/scratch/ (hierarchical)Per runMounted at /mnt/scratch/ inside harness sessions

Memory Stores persist across runs and accumulate lessons. Run Scratch is ephemeral — orchestrator writes for planners, planners write for workers — and expires with the run. See Memory + Scratch.


Package Layout

packages/little-workflow/src/
  authoring.ts          defineWorkflow(), runWorkflow(), runOrchestration(), model(), skill(), types
  fixed-plan.ts         defineWorkflow({ plan }) — the fixed-plan planner harness
  tool-registry.ts      createToolRegistry() — AI SDK tool({...}) as the primitive
  schema.ts             schema + output-mode normalization (JSON Schema / Zod)
  canonical.ts          canonical JSON + sha256 digest
  lwir.ts               LWIR types, Ajv validator, structural + expression checks
  expressions.ts        deterministic {{ ... }} expression language
  compiler.ts           compile pipeline (WorkflowDefinitionSnapshot → LWIR → WorkflowVersion)
  compiler-lock.ts      WorkflowVersion + validation hashes
  harness/
    types.ts            re-exports Harness, HarnessTask, HarnessContext, HarnessResult, event types (defined in little-harness)
    event-recorder.ts   HarnessEventRecorder — writes harness.session.*, harness.model.*, harness.tool_call.* events
  memory.ts             Memory Store mount construction + path enforcement
  scratch.ts            Run Scratch mount construction + hierarchy
  skills.ts             Skill loading + system-prompt injection
  orchestrator.ts       plan_workflow / run_workflow / start_workflow tool implementations
  run-state.ts          materializeRunStateFromEvents — folds event stream into MaterializedRunState
  world.ts              Local World: events, artifacts, manifests, locks
  outer-loop.ts         outer-loop scheduler + manifest read/write (Layer B)
  runtime.ts            LWIR step scheduler + worker harness dispatch
  replay.ts             replayRun() helper
  model-registry.ts     bundled model registry (pricing, capabilities, context window)
  pricing.ts            the single tokens -> USD site (priceModelCall, UsageTotals)
  run-report.ts         per-step tokens / cache-hit share / dollars from run state
  cli-core.ts           little validate | run | events | replay | report
  cli.ts                process.argv entry
  index.ts              the root entry: an explicit export list (src/index.test.ts pins it)
  entries/              the /lwir, /eval and /internals entry points

Module Dependency Graph

Arrows point from importer to importee. The Harness layer sits between the control plane and the runtime/world layers.

100%

Responsibility Boundaries

ModuleOwnsDoes Not Own
authoring.tsWorkflowDefinition, WorkflowDefinitionHash computation, model() factory + slot-id derivation, skill() loader, public re-exportsValidation, hashing, execution
tool-registry.tscreateToolRegistry(), AI SDK tool({...}) storage, ToolRegistrySnapshot for manifests, MCP attachmentSchema validation, hashing
schema.tsSchema + output-mode normalization. Zod / JSON Schema → NormalizedSchemaDescriptorHashing (uses canonical.ts), validation
canonical.tsCanonical JSON encoding + sha256: digestsAnything semantic
lwir.tsLWIR types, structural validator, expression presence checks, alpha JSON Schema subsetRequest-binding (compiler), execution (runtime)
expressions.tsParses and evaluates {{ ... }} expressionsSide effects, schema validation, async
compiler.tsCompile pipeline: normalize → snapshot → planner harness session → validate → registerStep execution, persistence
compiler-lock.tsWorkflowVersion + validationHash over a stable lock seedCalling Ajv
harness/types.tsAll Harness types: Harness, HarnessTask, HarnessContext, HarnessResult, all event typesImplementation
packages/little-harness/src/workflow-harness/workflow-harness.tsDefault harness: AI SDK streamText / generateText agent loop, bash tool, cwd-scopingWorkflow domain knowledge; it only sees ctx.*
harness/event-recorder.tsHarnessEventRecorder — emits harness.session.*, harness.model.*, harness.tool_call.* eventsMaterializing state
memory.tsMemory Store mount construction, path allowlist enforcement, lazy dir creationCross-run memory consolidation (dreaming)
scratch.tsRun Scratch mount hierarchy construction, cleanup policy stubMemory (different lifecycle)
skills.tsSkill loading from directory + single-file forms, system-prompt frontmatter injectionSkill authoring, discovery
orchestrator.tsplan_workflow / run_workflow / start_workflow tool implementations; sub-run spawning + concurrency capHarness agent loop (delegates to workflowHarness)
world.tsOn-disk store: SQLite/WAL event rows, artifact manifests + blobs, run locks, outer-loop manifests, sha256 verification, durable writesKnowing what a step means
outer-loop.tsOuter-loop scheduler: per-cycle compile-and-run, supervise() dispatch, OuterLoopCycleCompletedIndividual step execution
run-state.tsFolding an event stream into MaterializedRunState. Pure and replay-safeI/O
runtime.tsLWIR step scheduler: registering work, dispatching worker harness, retries, cancellation, parallel branches, run finalizationPersistence (world.ts), planning (compiler.ts), orchestration (orchestrator.ts)
replay.tsThin replay wrapper returning ReplayPlanStep[] + hydrated artifactsNew computation
model-registry.tsBundled model registry JSON + getModelInfoFor(providerId, modelId). Pricing + capabilities for known modelsLive sync (post-alpha)
pricing.tsTurning recorded tokens + model identity into USD, and folding calls into UsageTotals. The only place cost is computedReading a cost off an event — events are token-only
run-report.tsPer-step and run-total tokens, cache-hit share, and dollars; JSON and table renderingPre-run cost estimation (not implemented)
cli-core.tsThe little command surface — init, add, validate, run, test, events, replay, reportLoading TS modules; no harness is wired here

Identity At Each Scope

Three hashes give auditable identity at three different scopes:

HashComputed whenCovers
WorkflowDefinitionHashdefineWorkflow()id, schemas, planner config, models, tools, memory config
WorkflowVersion (= LWIR hash)Planner emits LWIRThe complete LWIR graph — per run
ManifestHashEach harness session startsThe session's capability surface (model, harness, skills, tools, memory stores)

See Identity + Manifests for drift detection details.

Why One Package

Alpha keeps modules in one package: the wiring is dense and the surface is small. Splitting points are already drawn — harness/ could become @little-workflow/harness, world.ts could become @little-workflow/world-postgres. Those splits happen post-alpha when the boundaries are validated against production load.

On this page