TypeScript · Python

One plan. Named steps. Nothing copied by the model.

A model emits a declarative plan of named steps; your application validates and executes it; results flow between steps by name and never travel through the model.

npm install weftai pip install weftai Any model provider MIT licensed
{ "steps": [
  { "id": "drone",      "op": "parts.find",
    "input": { "filters": [{ "field": "label", "op": "fuzzy", "value": "Aurora Drone" }] } },
  { "id": "components", "op": "parts.components",
    "input": { "from": "$drone", "depth": "all" } },
  { "id": "taiwan",     "op": "parts.filter",
    "input": { "from": "$components",
               "filters": [{ "field": "origin", "op": "eq", "value": "Taiwan" }] } }
] }

One tool call, three dependent lookups, zero identifiers copied by the model.
Errors that name the fixAn unknown operation suggests the nearest name; a wrong reference says what it expected.
Typed referencesA step reads an earlier result as $stepId, and the handler receives the collection.
Nothing truncates silentlyExact counts, “showing 30 of 35”, and a notice for every cap that bit.
Results stay in the sessionThe next tool call can build on this one, by name.

How it works

Define an operation once. Everything else is derived.

The same library, with the same plan format, is published for TypeScript on npm and for Python 3.12+ on PyPI.

01

You define operations

A name, a description for the model, an input schema, an output type and a handler. Standard operations per collection come for nothing: filter, count, countBy, distinct, mostCommon, first, pick, details.

02

The model writes a plan

Named steps over your operations. A later step refers to an earlier result as $stepId or $stepId[2]; positions always index the full set, not the preview the model was shown.

03

Your application runs it

If a plan has any issue, no step runs, and the model reads every issue at once. Independent reads run together, a write runs on its own in the order it was written, a failed step skips only its dependents, and every step is traced.

Packages

Five packages, released together under one version.

On npm as weftai and @weftai/*, and on PyPI as weftai with matching extras. Release tags and each package's changelog are in the repository.

weftai

Core runtime

Operations, plans, validation, execution, the result store, the formatter and traces.

@weftai/providers

Wire-format adapters

OpenAI, Anthropic, Gemini, Bedrock, Ollama and more, so a plan tool reads the same to each.

@weftai/mcp

MCP server

Expose a runtime as an MCP server.

@weftai/testing

Testing

A test runtime, matchers and fixture helpers.

@weftai/cli

Command line

weftai run | validate | describe | trace | mcp | init: run saved plans against fixtures with no model in the loop.

pip install weftai

Python

The same library and plan format for Python 3.12+, on PyPI.

Run it

One install, in either language.

The README walks through a bill-of-materials example end to end: the operations, the plan a model writes, and the text the model reads back.

  1. TypeScript, on Node 22.12 or newer.
    npm install weftai
  2. Python 3.12 or newer.
    pip install weftai

Start with concepts, then writing operations.

Rules it keeps

Rules that are part of the product.

Does my data travel through the model?

No. Results flow between steps by reference and stay in a session-scoped store. The model reads a formatted, token-budgeted view with exact counts, and refers to the full set by name.

What happens when the model asks a wrong question?

It is an error, never an empty result. An unknown field lists the available fields, an unknown step suggests the nearest id, and a reference written into a field that does not take one is refused before anything runs.

Is it tied to one model provider?

No. The plan tool is described once and adapted to each provider's wire format. A model that cannot call tools is an error naming the fix, and an unknown model name is not assumed to speak Chat Completions.

Does it force the model to call it?

No. No adapter sets a forced tool choice by default; the newest OpenAI, Anthropic and Gemini models reject one.

Docs

Written for the person who has to run it.