Skip to main content

One plan, three ways to run it

A Yam project compiles to one plan.json and one bindings store. What changes between a test, a workflow and a tool is who is asking and what they get back, not the plan, the bindings, or what a step means.

plan.json + bindings/

┌─────────────────────────┼─────────────────────────┐
▼ ▼ ▼
yam run yam workflow run yam tool serve
a pass/fail oracle a typed function an MCP tool an agent calls
and runs/<id>/ inputs → outputs as JSON audited per invocation

As a test

yam run --host playwright

Expectations pass or fail and the exit code says which. Use it from any CI runner; see Run in CI.

As a workflow

yam workflow run "Sign in" --input username=me@example.com | jq .

A story with a signature run as a function. Inputs are validated by type before anything runs, outputs come back on stdout as JSON, checkpoints and audit are always on, and a story that is not marked idempotent is refused against a production configuration unless you say --allow-side-effects. See Run a story from cron.

As a tool

yam tool serve --expose "Sign in"

An MCP server whose tools are the stories. Each tool's input schema is derived from the signature, each call is a workflow run with the agent recorded as the invoker, and the audit line is the only account of why the system changed. See Expose a story as an MCP tool.

Switching behaviour never requires recompiling or re-recording, and the runtime cannot call a model in any of the three: the import boundary is linted and the tool server's tests run with every external connection refused. The longer account is Behaviors.