Stratum MCP Server
by smartmemory
README.md
# Stratum
[](https://pypi.org/project/stratum-mcp/)
[](https://pypi.org/project/stratum-py/)
[](LICENSE)
**State machine dispatch server for AI agent workflows.**
Stratum gives AI coding agents (Claude Code, Codex, etc.) a formal execution model. Instead of improvising a plan and retrying blindly, the agent writes a typed spec, the server tracks state, enforces postconditions, and returns structured failure context on retry. Every step produces an auditable trace record.
The founding intent behind this machinery is recorded in [docs/VISION.md](docs/VISION.md): a spec language that keeps LLMs on rails invisibly, so the same conversation yields stronger results than freeform execution.
Two shipped components:
- **`stratum-mcp`** -- MCP server for Claude Code. Validates `.stratum.yaml` specs, manages flow execution state, enforces typed contracts and postconditions. Published on PyPI.
- **`stratum-py`** -- Python library with `@infer`, `@contract`, `@compute`, `@flow` decorators for building production LLM systems. Published on PyPI.
**Governed workflows as auditable flows — on any agent, not just one vendor.** Unlike a single-vendor in-context orchestrator, Stratum runs as an MCP server and a library under Claude Code, Codex, or any MCP host; enforces typed contracts and `ensure` postconditions on every flow execution; stops at real human gates; dispatches Claude *and* Codex agents in one flow (so an independent reviewer can be a different model from the implementer); and persists flow state across sessions. Where you want raw in-context fan-out, reach for an in-host workflow runtime; where you want the run governed, portable, and auditable, that's a Stratum workflow.
---
## Table of Contents
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [YAML Spec Reference](#yaml-spec-reference)
- [MCP Tools API](#mcp-tools-api)
- [Step Types](#step-types)
- [Ensures (Postconditions)](#ensures-postconditions)
- [Contracts and Output Validation](#contracts-and-output-validation)
- [Gates (Human-in-the-Loop)](#gates-human-in-the-loop)
- [Flow Composition](#flow-composition)
- [Routing](#routing)
- [Iterations](#iterations)
- [Checkpoints](#checkpoints)
- [Recovery and Retry Logic](#recovery-and-retry-logic)
- [Workflows](#workflows)
- [Task Compiler](#task-compiler)
- [Skills](#skills)
- [CLI Reference](#cli-reference)
- [Configuration](#configuration)
- [Python Library (Track 1)](#python-library-track-1)
- [Examples](#examples)
- [Development](#development)
- [License](#license)
---
## Installation
### MCP Server (for Claude Code)
```bash
pip install stratum-mcp
stratum-mcp install
```
`install` does three things:
1. Writes `.claude/mcp.json` to register the MCP server
2. Appends the Stratum execution model block to `CLAUDE.md`
3. Installs eleven skills to `~/.claude/skills/`
4. Installs session hooks to `~/.stratum/hooks/`
Restart Claude Code to activate.
To remove:
```bash
stratum-mcp uninstall # removes everything
stratum-mcp uninstall --keep-skills # keeps user-customized skills
```
### Python Library
```bash
pip install stratum-py
```
Requires Python 3.11+. Dependencies: `litellm>=1.0`, `pydantic>=2.0`.
Set an API key for your LLM provider (`GROQ_API_KEY`, `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.) and specify the model in `model=`.
---
## Quick Start
When Claude Code has Stratum installed, it uses it automatically for non-trivial tasks:
1. Claude writes a `.stratum.yaml` spec internally (never shown to you)
2. Calls `stratum_plan` to validate the spec and get the first step
3. Executes each step using its own tools (reading files, writing code, running tests)
4. Calls `stratum_step_done` after each step -- the server checks postconditions
5. If a postcondition fails, Claude gets back the specific violation and retries
6. Calls `stratum_audit` at the end for a full execution trace
You see plain English narration throughout. The spec, state management, and postcondition enforcement happen behind the scenes.
---
## Core Concepts
### Workflow vs Flow
Stratum uses these two terms deliberately — they are **not** interchangeable.
A **workflow** is the *authored definition*: the named, registered, version-controlled artifact you write once and rerun (a `.stratum.yaml` spec with a `workflow:` block, discoverable via `stratum_list_workflows`). A **flow** is the *executable DAG definition*: a DAG of steps (`flows:`, `@flow`). Running a flow produces a **flow execution** — a single live instance tracked as a `FlowState` with a `flow_id`. A workflow composes flows; running a workflow produces flow executions.
The split mirrors Temporal (Workflow Definition vs Execution) and Airflow (DAG vs DAG Run). Rule of thumb: if you can `git diff` it, it's a *workflow*; if it has a `flow_id` and a state, it's a *flow execution*.
### Flows
A **flow** is a directed acyclic graph of steps with typed inputs and an output contract. Flows are defined either in `.stratum.yaml` specs (executed by the MCP server) or with the `@flow` decorator in the Python library.
### Steps
A **step** is a single unit of work within a flow. Each step has an ID, an execution mode (function, inline, or flow), inputs that can reference flow-level inputs or prior step outputs, and optional postconditions.
### Functions
**Functions** are reusable definitions that steps reference. A function declares its mode (`infer`, `compute`, or `gate`), intent, input schema, output contract, postconditions, retry count, and optional model/budget.
### Contracts
**Contracts** define the shape of step outputs. They are named schemas with typed fields, validated against step results before postconditions run.
### Ensures
**Ensures** are postcondition expressions evaluated against step results. If any ensure fails, the step is retried with the specific violation. Example: `result.confidence > 0.7`.
### Retries
Each step has a retry budget. When an ensure or schema validation fails, the step is retried up to the declared limit. The agent receives the specific violations, not a blank replay.
### Gates
**Gate** steps pause execution and wait for external resolution (human approval, agent decision, or system timeout). Gates support `approve`, `revise`, and `kill` outcomes with configurable routing.
---
## YAML Spec Reference
A `.stratum.yaml` spec has four top-level sections: `version`, `contracts`, `functions`, and `flows`. An optional `workflow` block declares the spec as a registered workflow.
### Minimal Example (v0.1)
```yaml
version: "0.1"
contracts:
SentimentResult:
label: {type: string}
confidence: {type: number}
functions:
classify:
mode: infer
intent: "Classify the sentiment of this text"
input: {text: {type: string}}
output: SentimentResult
ensure:
- "result.label != ''"
- "result.confidence > 0.7"
retries: 2
flows:
run:
input: {text: {type: string}}
output: SentimentResult
steps:
- id: s1
function: classify
inputs: {text: "$.input.text"}
```
### Full Example with Gates (v0.2)
```yaml
version: "0.2"
contracts:
WorkOutput:
result: {type: string}
quality_score: {type: number}
functions:
do_work:
mode: infer
intent: "Produce the deliverable"
input: {text: {type: string}}
output: WorkOutput
ensure:
- "result.quality_score >= 0.8"
retries: 3
review_gate:
mode: gate
timeout: 3600
flows:
reviewed_work:
input: {text: {type: string}}
output: WorkOutput
max_rounds: 3
steps:
- id: work
function: do_work
inputs: {text: "$.input.text"}
- id: review
function: review_gate
on_approve: ~
on_revise: work
on_kill: ~
```
### Full Field Reference
#### `version` (required)
```yaml
version: "0.1" # or "0.2"
```
Version `"0.2"` adds gates, inline steps, flow composition, policies, iterations, skip_if, routing, and workflows.
#### `contracts`
Named output schemas. Each contract is an object whose keys are field names and values are `{type: <type>}` objects.
```yaml
contracts:
MyContract:
field_name: {type: string}
score: {type: number}
tags: {type: array}
```
Supported types: `string`, `number`, `integer`, `boolean`, `array`, `object`.
#### `functions`
Reusable step definitions referenced by function steps.
```yaml
functions:
my_function:
mode: infer | compute | gate # required
intent: "What this function does" # required for infer/compute
input: # required for infer/compute
param_name: {type: string}
output: ContractName # required for infer/compute
ensure: # optional (forbidden on gate)
- "result.field > 0"
retries: 3 # optional, default 3 (forbidden on gate)
budget: # optional (forbidden on gate)
ms: 5000
usd: 0.01
model: "gpt-4o" # optional
timeout: 3600 # optional, gate only — seconds before auto-kill
```
**Gate functions** only require `mode: gate`. They must not have `ensure`, `budget`, or `retries`.
#### `flows`
Flow definitions with input schema, output contract, and ordered steps.
```yaml
flows:
my_flow:
input:
param: {type: string}
output: ContractName # optional for gate-only flows
budget: # optional
ms: 30000
usd: 0.10
max_rounds: 5 # optional (v0.2) — max gate revise cycles
steps:
- id: step_id # required, unique within flow
# Execution mode — exactly one of:
function: my_function # references a function definition
intent: "Do something" # inline step (v0.2)
flow: sub_flow_name # sub-flow invocation (v0.2)
# Common fields:
inputs: # optional
param: "$.input.param"
prior: "$.steps.s1.output.field"
depends_on: [s1, s2] # optional — explicit dependencies
output_schema: # optional — JSON Schema for result validation
type: object
required: [done]
properties:
done: {type: boolean}
# Inline step fields (v0.2, only with intent):
agent: claude # optional — agent assignment
ensure: # optional
- "result.done == True"
retries: 2 # optional, default 1
output_contract: MyContract # optional
model: "gpt-4o" # optional
budget: # optional
ms: 5000
# Gate step fields (v0.2, only with function referencing a gate):
on_approve: next_step | ~ # required — step to route to, or null for completion
on_revise: earlier_step # required — must target topologically earlier step
on_kill: cleanup_step | ~ # required — step to route to, or null for termination
policy: gate | flag | skip # optional — auto-resolution policy
policy_fallback: gate # optional — requires policy
# Non-gate routing (v0.2):
on_fail: recovery_step # optional — route on retry exhaustion (requires ensure)
next: loop_back_step # optional — override linear advancement on success
# Conditional skip (v0.2):
skip_if: "$.steps.s1.output.skip == True" # optional (forbidden on gates)
skip_reason: "Already done" # optional
# Iteration (v0.2):
max_iterations: 10 # optional (forbidden on gates)
exit_criterion: "result.quality >= 0.9" # optional, requires max_iterations
accumulate: "result.findings" # optional; dedup items across rounds
accumulate_key: "item.id" # optional; requires accumulate
```
#### `workflow` (v0.2)
Self-registering workflow declaration. Allows `stratum_list_workflows` to discover specs.
```yaml
workflow:
name: my-workflow # lowercase, hyphens only
description: "What this workflow does"
input:
param_name:
type: string
required: true
default: "value"
```
Workflow input keys must exactly match the entry flow's input keys.
### Input References
Step inputs use `$` references to chain data through the flow:
| Pattern | Resolves to |
|---|---|
| `$.input.<field>` | Flow-level input value |
| `$.steps.<step_id>.output` | Full output of a prior step |
| `$.steps.<step_id>.output.<field>` | Specific field from a prior step's output |
| `literal_value` | Passed through as-is |
References create implicit dependencies. The server also uses explicit `depends_on` for topological ordering (Kahn's algorithm).
---
## MCP Tools API
All tools are exposed via the MCP protocol. Claude Code calls them as tool invocations.
### `stratum_validate`
Validate a `.stratum.yaml` spec without creating a flow.
**Inputs:** `spec` (str, inline YAML)
**Returns:** `{valid: bool, errors: list}`
### `stratum_plan`
Validate a spec, create execution state, and return the first step to execute.
**Inputs:**
- `spec` (str) -- inline YAML
- `flow` (str) -- flow name
- `inputs` (dict) -- flow-level inputs
**Returns:** Step dispatch object with `status: "execute_step"` or `status: "await_gate"`, including:
- `flow_id` -- unique identifier for this execution
- `step_id`, `step_number`, `total_steps`
- `function`, `intent`, `inputs` (resolved)
- `output_contract`, `output_fields`, `ensure`
- `retries_remaining`
- `agent`, `step_mode`
### `stratum_step_done`
Report a completed step result. The server validates the result against output schemas and ensure expressions.
**Inputs:**
- `flow_id` (str)
- `step_id` (str)
- `result` (dict) -- step output matching the output contract
**Returns one of:**
- Next step to execute (`status: "execute_step"`)
- Ensure failure with retry info (`status: "ensure_failed"`, `violations`, `retries_remaining`)
- Schema validation failure (`status: "schema_failed"`, `violations`)
- Flow completion (`status: "complete"`, `output`, `trace`, `total_duration_ms`)
- Retries exhausted (`status: "error"`, `error_type: "retries_exhausted"`)
- Routed to recovery step (`routed_from`, `violations`)
### `stratum_audit`
Return the full execution trace for a flow.
**Inputs:** `flow_id` (str)
**Returns:**
- `flow_id`, `flow_name`, `status` (`complete`, `in_progress`, `killed`)
- `steps_completed`, `total_steps`
- `trace` -- array of step records (step_id, function_name, attempts, duration_ms, type, round)
- `round`, `rounds` -- round history for gate revise cycles
- `iterations`, `archived_iterations` -- iteration history
- `child_audits` -- audit snapshots from sub-flow executions
- `total_duration_ms`
### `stratum_gate_resolve`
Resolve a gate step with a human/agent/system decision.
**Inputs:**
- `flow_id` (str)
- `step_id` (str) -- must be the current gate step
- `outcome` (str) -- `"approve"`, `"revise"`, or `"kill"`
- `rationale` (str) -- human-readable reason
- `resolved_by` (str) -- `"human"`, `"agent"`, or `"system"`
**Returns:** Next step, flow completion, or flow termination.
### `stratum_check_timeouts`
Check whether a pending gate step has exceeded its configured timeout. Auto-kills with `resolved_by: "system"` if expired.
**Inputs:** `flow_id` (str)
### `stratum_skip_step`
Explicitly skip the current step (cannot skip gate steps).
**Inputs:** `flow_id` (str), `step_id` (str), `reason` (str)
### `stratum_commit`
Save a named checkpoint of the current flow state.
**Inputs:** `flow_id` (str), `label` (str)
### `stratum_revert`
Roll back flow state to a previously committed checkpoint.
**Inputs:** `flow_id` (str), `label` (str)
### `stratum_iteration_start`
Start an iteration loop on the current step (requires `max_iterations` in the spec).
**Inputs:** `flow_id` (str), `step_id` (str)
### `stratum_iteration_report`
Report one iteration result. Evaluates `exit_criterion`, increments count, checks `max_iterations`.
**Inputs:** `flow_id` (str), `step_id` (str), `result` (dict)
**Returns:** `iteration_continue` or `iteration_exit` with outcome.
### `stratum_iteration_abort`
Abort an active iteration loop before completion.
**Inputs:** `flow_id` (str), `step_id` (str), `reason` (str)
### `stratum_compile_speckit`
Compile a spec-kit tasks directory into a `.stratum.yaml` flow.
**Inputs:** `tasks_dir` (str), `flow_name` (str, default `"tasks"`)
**Returns:** `{status, yaml, flow_name, steps}` on success.
### `stratum_list_workflows`
Scan a directory for `*.stratum.yaml` files with `workflow:` blocks. Returns registered workflows with name, description, input schema, and file path. Detects duplicate names.
### `stratum_draft_pipeline`
Push a pipeline draft to the PipelineEditor UI via `.stratum/pipeline-draft.json`.
---
## STRAT-GUARD — guarded transitions outside a flow
`stratum_judge`/`stratum_gate_resolve` enforce guarantees **only inside a flow**. STRAT-GUARD exposes the same independent-verification engine (`run_judge`) as a standalone, resource-agnostic, tamper-evident state machine for clients that manage a resource lifecycle **outside** a stratum flow (e.g. compose's feature tracker). A client registers a transition graph with per-edge evidence predicates; stratum then permits a transition only if the edge is legal and its predicates verify against **trusted, server-read evidence** (not caller-staged claims). Every attempt — applied or refused — is appended to a hash-chained ledger. See `docs/features/STRAT-GUARD/`.
### `stratum_guard_register`
Register a guarded resource. The `(graph, edge_predicates, terminal, stakes)` policy is checksummed and immutable — re-registering an identical policy is a no-op; a different policy is rejected (use `stratum_guard_migrate`).
**Inputs:** `resource_id` (str, client-namespaced e.g. `"compose:FEAT-1"`), `graph` (`dict[from -> list[to]]`), `edge_predicates` (`dict["from->to" -> list of {id,type,statement}]`), `initial` (str), `terminal` (list[str]), `stakes` (`dict["from->to" -> "cheap"|"default"|"paranoid"]`), `workspace_root` (abs dir, for file/git/command evidence).
Predicate `type`: `"deterministic"` → server-side trusted evidence (`server_file_exists`/`git_commit_exists`/`command_exit_zero`/`verdict_receipt_clean`); `"verified"`/`"judged"` → LLM-tier, routed through `run_judge`. A `paranoid` edge must declare ≥1 trusted predicate.
**Returns:** `{guard_id, checksum, status}`.
### `stratum_guard_transition`
Attempt `from_state -> to_state`. Trusted predicates are verified server-side; any LLM-tier predicates go through the judge at the edge's stakes. Optimistic concurrency: evaluation runs outside a per-resource lock, the commit re-validates state under it.
**Inputs:** `resource_id`, `from_state`, `to_state`, `artifacts` (`dict[str,str]`), `modified_files` (list[str]), `idempotency_key` (str|None), `resolved_by` (str).
**Returns:** `{status: applied|refused|replayed, verdict: JudgeResult-dict, ledger_ref, current_state}`. `ledger_ref` is the receipt token presented to `verdict_receipt_clean`.
### `stratum_guard_override`
The single sanctioned bypass of predicate verification. Requires an out-of-band `override_token` (server env `STRATUM_GUARD_OVERRIDE_TOKEN`, not agent-mintable), a human resolver, and a rationale. Moves a **legal** edge without verifying predicates and records a `deviation` ledger entry. Replaces a `force` flag.
**Inputs:** `resource_id`, `from_state`, `to_state`, `override_token`, `rationale`, `resolved_by` (`"human"`).
### `stratum_guard_migrate`
Evolve a registered policy. Token-gated, bumps `graph_version`, writes a `graph_version` ledger entry, and never silently relaxes an in-flight resource's policy. The current state must remain a node in the new graph.
**Inputs:** `resource_id`, `new_graph`, `new_edge_predicates`, `override_token`, `rationale`, `new_terminal`, `new_stakes`.
### `stratum_guard_history`
Return a resource's current state and its append-only, hash-chained transition/deviation ledger (the tamper-evident audit trail).
**Inputs:** `resource_id`. **Returns:** `{resource_id, current_state, graph_version, ledger: [...]}`.
---
## Step Types
### Function Steps
Reference a named function definition. The function provides the intent, input schema, output contract, ensures, retries, and model.
```yaml
steps:
- id: classify
function: classify_sentiment
inputs: {text: "$.input.text"}
```
### Inline Steps (v0.2)
Self-contained steps with `intent` and optional `agent`. No function reference needed. Execution fields (`ensure`, `retries`, `output_contract`, `model`, `budget`) are declared directly on the step.
```yaml
steps:
- id: analyze
intent: "Analyze the codebase for security vulnerabilities"
agent: claude
ensure:
- "result.vulnerabilities_checked == True"
retries: 2
output_contract: AnalysisResult
```
### Flow Steps (v0.2)
Invoke a sub-flow defined in the same spec. The child flow runs to completion, and its output is unwrapped into the parent step's result.
```yaml
steps:
- id: run_tests
flow: test_suite
inputs: {project: "$.input.project"}
ensure:
- "result.all_passed == True"
on_fail: fix_tests
```
Flow steps must not have `agent`, `retries`, `model`, or `budget`. They may have `ensure` and `on_fail`.
### Mode Exclusion
Every step must have exactly one of `function`, `intent`, or `flow`. Having zero or more than one is a semantic error caught at parse time.
---
## Ensures (Postconditions)
Ensures are Python expressions evaluated against the step result. The result is available as `result` (dicts are wrapped in `SimpleNamespace` for attribute access).
### Expression Syntax
```yaml
ensure:
- "result.confidence > 0.7"
- "result.label != ''"
- "len(result.items) > 0"
- "result.status in ('success', 'partial')"
```
### Built-in Functions
| Function | Signature | Description |
|---|---|---|
| `file_exists` | `file_exists(path)` | Returns `True` if the file exists on disk |
| `file_contains` | `file_contains(path, substring)` | Returns `True` if the file contains the substring (10 MB limit) |
| `len` | `len(x)` | Standard Python `len` |
| `bool` | `bool(x)` | Standard Python `bool` |
| `int` | `int(x)` | Standard Python `int` |
| `str` | `str(x)` | Standard Python `str` |
### Safety
- `__builtins__` is always empty -- no access to `os`, `sys`, `import`, etc.
- Dunder attributes (`__`) are blocked at compile time
- Expressions are compiled once and cached
### Failure Behavior
When an ensure expression fails, the server returns:
```json
{
"status": "ensure_failed",
"violations": ["ensure 'result.confidence > 0.7' failed"],
"retries_remaining": 2
}
```
The agent receives the specific violations and can target its fix accordingly.
---
## Contracts and Output Validation
### Contracts in the Spec
Contracts define expected output shapes. Fields specify their type:
```yaml
contracts:
AnalysisResult:
summary: {type: string}
score: {type: number}
issues: {type: array}
metadata: {type: object}
is_valid: {type: boolean}
```
When a function or step references an output contract, the server resolves the contract fields and includes them in the step dispatch so the agent knows what shape to produce.
### Output Schema (Per-Step JSON Schema)
Steps can declare a full JSON Schema for structural validation. This runs **before** ensure expressions:
```yaml
steps:
- id: s1
function: do_work
inputs: {text: "$.input.text"}
output_schema:
type: object
required: [done, tests_pass]
properties:
done: {type: boolean}
tests_pass: {type: boolean}
```
Schema violations are returned as `status: "schema_failed"` with specific error messages.
---
## Gates (Human-in-the-Loop)
Gates are approval checkpoints that pause flow execution until resolved externally.
### Defining a Gate
```yaml
functions:
approval:
mode: gate
timeout: 3600 # optional — auto-kill after 1 hour
flows:
my_flow:
max_rounds: 3 # optional — limit revise cycles
steps:
- id: work
function: do_work
inputs: {text: "$.input.text"}
- id: review
function: approval
on_approve: ~ # null = complete the flow
on_revise: work # must target a topologically earlier step
on_kill: ~ # null = terminate the flow
```
### Gate Constraints
- Gate functions must not have `ensure`, `budget`, or `retries`
- Gate steps must not have `skip_if` or `output_schema`
- Gate steps must explicitly declare `on_approve` and `on_kill` (even if null)
- `on_revise` must be non-null and must target a topologically earlier step
- `on_revise` must not self-reference
### Resolution
Gates are resolved via `stratum_gate_resolve` or the CLI `stratum-mcp gate` command:
- **approve** -- routes to `on_approve` target (or completes flow if null)
- **revise** -- archives the current round, resets state, routes to `on_revise` target
- **kill** -- routes to `on_kill` target (or terminates flow with `status: "killed"` if null)
`resolved_by` is one of `"human"`, `"agent"`, or `"system"`.
### Gate Policies (v0.2)
Gate steps can have an auto-resolution policy:
```yaml
- id: review
function: approval
policy: skip # auto-approve without pausing
policy_fallback: gate # fall back to manual gate if policy fails
on_approve: ~
on_revise: work
on_kill: ~
```
| Policy | Behavior |
|---|---|
| `gate` | Default -- pause and wait for resolution |
| `flag` | Auto-approve, but log a PolicyRecord in the trace |
| `skip` | Auto-approve silently |
`policy_fallback` requires `policy` to be set.
### Timeout
If a gate function has `timeout` set, `stratum_check_timeouts` will auto-kill the gate with `resolved_by: "system"` when the timeout expires.
### Rounds
When a gate is resolved with `revise`, the current round's trace is archived into `state.rounds`, the active trace is reset, and the round counter increments. `max_rounds` on the flow limits how many revise cycles are allowed.
---
## Flow Composition
Steps with `flow:` invoke a sub-flow defined in the same spec. The parent flow suspends until the child completes.
```yaml
flows:
integration_tests:
input: {project: {type: string}}
output: TestResult
steps:
- id: run
intent: "Run integration tests"
ensure:
- "result.all_passed == True"
deploy:
input: {project: {type: string}}
output: DeployResult
steps:
- id: test
flow: integration_tests
inputs: {project: "$.input.project"}
on_fail: rollback
- id: release
intent: "Deploy to production"
- id: rollback
intent: "Roll back deployment"
```
- Child flows get their own `FlowState` and `flow_id`
- Child audit snapshots are accumulated in `child_audits` on the parent
- Recursive flow references are detected and rejected at parse time
- Flow steps must not set `agent`, `retries`, `model`, or `budget`
---
## Routing
### `on_fail` -- Recovery Routing
Routes to a named recovery step when retries are exhausted (requires `ensure` or `output_schema`):
```yaml
- id: generate
function: generate_code
inputs: {spec: "$.input.spec"}
on_fail: manual_fix
- id: manual_fix
intent: "Fix the generated code manually"
```
The failed step's output is preserved so the recovery step can access it via `$.steps.generate.output`.
### `next` -- Success Routing
Overrides linear step advancement on success. Enables review loops:
```yaml
- id: write
intent: "Write the code"
next: review
- id: review
intent: "Review the code"
ensure:
- "result.approved == True"
on_fail: write
```
When `next` routes to a step, that step's attempts are cleared for fresh execution.
### Conditional Skip
Steps can be conditionally skipped based on prior outputs:
```yaml
- id: deploy
intent: "Deploy to staging"
skip_if: "$.steps.tests.output.all_passed == False"
skip_reason: "Tests failed, skipping deployment"
```
Skipped steps have their output set to `None`. Gate steps cannot have `skip_if`.
`skip_if` expressions support `$` references, Python-style booleans (`True`, `False`, `None`), and YAML-style literals (`true`, `false`, `null`).
---
## Iterations
Steps with `max_iterations` support counted sub-loops. The agent iterates until `exit_criterion` is met or the maximum count is reached.
```yaml
- id: refine
intent: "Improve the output quality"
max_iterations: 5
exit_criterion: "result.quality >= 0.95"
```
**Iteration workflow:**
1. `stratum_iteration_start(flow_id, step_id)` -- begins the loop
2. `stratum_iteration_report(flow_id, step_id, result)` -- reports each iteration; server evaluates `exit_criterion`
3. Returns `iteration_continue` or `iteration_exit`
4. `stratum_iteration_abort(flow_id, step_id, reason)` -- early exit
Gate steps cannot use iterations. `exit_criterion` requires `max_iterations`. Iteration history is preserved across gate revise cycles in `archived_iterations`.
### Accumulator & loop-until-dry
An iteration step can accumulate deduped items across rounds and exit when it stops finding
new ones. Declare `accumulate` (an expression returning the round's item list) and optionally
`accumulate_key` (a per-item dedup key, binding `item`):
```yaml
- id: hunt
intent: "Find issues; keep hunting until two dry rounds"
max_iterations: 20
accumulate: "result.findings" # list extracted from each result
accumulate_key: "item.id" # optional; default dedups on the whole item
exit_criterion: "dry_streak >= 2" # K consecutive zero-new rounds → exit
```
`exit_criterion` additionally sees `accumulator` (the deduped list), `accumulated_count`,
`new_count` (items added this round), and `dry_streak` (consecutive zero-new rounds) — compose
them freely, e.g. `"dry_streak >= 2 or accumulated_count >= 50"`. On exit the deduped set is
merged into the step's authoritative output as `accumulated`/`accumulated_count`. A malformed
`accumulate`/`accumulate_key` is reported as `accumulate_error` and **freezes `dry_streak`** (a
broken extractor can't fake dryness); accumulator loops are governed solely by `exit_criterion`
and `max_iterations` (fingerprint-stagnation does not preempt them). Not valid on gate,
`decompose`, or `parallel_dispatch` steps.
---
## Checkpoints
Named snapshots of flow state for rollback scenarios.
```yaml
# During execution:
stratum_commit(flow_id, "after_analysis")
# ... later steps fail ...
stratum_revert(flow_id, "after_analysis")
```
Checkpoints capture: `step_outputs`, `attempts`, `records`, `current_idx`, round state, iteration state, and child flow state. They survive server restarts (persisted to disk).
---
## Recovery and Retry Logic
### Retry Budget
Each function or inline step declares a retry count (default 3 for functions, 1 for inline steps). The server tracks attempts per step.
### Retry Flow
1. Agent submits result via `stratum_step_done`
2. Server validates against `output_schema` (if declared) -- structural errors first
3. Server evaluates `ensure` expressions
4. If violations exist and retries remain: returns `ensure_failed` or `schema_failed` with violations and remaining retry count
5. If retries exhausted and `on_fail` is set: routes to recovery step
6. If retries exhausted with no `on_fail`: returns `retries_exhausted` error
### Persistence
Flow state is persisted to `~/.stratum/flows/{flow_id}.json` after each state mutation. Flows survive MCP server restarts. Timing fields are reset on restore (step durations may be inaccurate for the resumed step).
### Server Restart Recovery
On any tool call, if the flow is not in memory, the server attempts to restore it from disk. The spec is re-parsed, steps are re-sorted, and execution resumes from the persisted `current_idx`.
---
## Workflows
Workflow declarations make specs discoverable via `stratum_list_workflows`:
```yaml
version: "0.2"
workflow:
name: code-review
description: "Three-pass code review: security, logic, performance"
input:
files:
type: array
required: true
depth:
type: string
required: false
default: "standard"
```
`stratum_list_workflows` scans a directory for `*.stratum.yaml` files with `workflow:` blocks and returns their metadata. Duplicate workflow names are reported as errors.
---
## Task Compiler
The task compiler converts spec-kit task files (`tasks/*.md`) into `.stratum.yaml` flows.
### Task File Format
```markdown
# Task: [P] Implement authentication
Add JWT-based authentication to the API.
## Acceptance Criteria
- [ ] file src/auth/middleware.ts exists
- [ ] file src/auth/middleware.ts contains "verifyToken"
- [ ] tests pass
- [ ] no lint errors
- [ ] Error messages are user-friendly
```
### Compilation Rules
- `[P]` in the title marks the task as parallelizable
- `file X exists` compiles to `file_exists("X")`
- `file X contains Y` compiles to `file_contains("X", "Y")`
- `tests pass` compiles to `result.tests_pass == True`
- `no lint errors` compiles to `result.lint_clean == True`
- Freeform criteria are incorporated into the step's `intent`
### Dependency Graph
- Sequential tasks depend on the prior task
- Parallel tasks (`[P]`) share the same predecessor with no edges between them
- After a parallel group, the next sequential task depends on all tasks in the group
### Usage
```bash
stratum-mcp compile tasks/ --output flow.stratum.yaml --flow my_flow
```
Or via MCP tool: `stratum_compile_speckit(tasks_dir, flow_name)`.
---
## Skills
Eleven skills are installed by `stratum-mcp install`:
| Skill | Purpose |
|---|---|
| `/stratum-onboard` | Read a new codebase cold, write project-specific `MEMORY.md` |
| `/stratum-plan` | Design a feature, present for review -- no implementation |
| `/stratum-feature` | Full feature build: read patterns, design, implement, test |
| `/stratum-review` | Three-pass code review: security, logic, performance |
| `/stratum-debug` | Hypothesis-driven debugging with elimination |
| `/stratum-refactor` | File splitting with planned extraction order |
| `/stratum-migrate` | Rewrite bare LLM calls as `@infer` + `@contract` |
| `/stratum-test` | Write test suite for untested code (golden flows, error-path harness) |
| `/stratum-learn` | Extract patterns from session transcripts into `MEMORY.md` |
| `/stratum-build` | Compile tasks, drive execution via `stratum_plan` loop |
| `/stratum-speckit` | Bridge skill for spec-kit phase execution |
Each skill reads project-specific patterns from `MEMORY.md` before writing its spec and writes new patterns after `stratum_audit`.
---
## CLI Reference
```
stratum-mcp # Start stdio MCP server (for Claude Code)
stratum-mcp install # Configure Claude Code project for Stratum
stratum-mcp uninstall # Remove Stratum configuration
stratum-mcp uninstall --keep-skills # Remove config but keep skill files
stratum-mcp validate <file> # Validate a .stratum.yaml spec
stratum-mcp compile <dir> # Compile tasks/*.md to .stratum.yaml
--output <file> # Write to file instead of stdout
--flow <name> # Flow name (default: "tasks")
stratum-mcp query flows # List all persisted flows (JSON)
stratum-mcp query flow <id> # Full state for a single flow (JSON)
stratum-mcp query gates # List all pending gate steps (JSON)
stratum-mcp gate approve <flow_id> <step_id> [--note "reason"]
stratum-mcp gate reject <flow_id> <step_id> [--note "reason"]
stratum-mcp gate revise <flow_id> <step_id> [--note "reason"]
--resolved-by human|agent|system # Who resolved (default: human)
stratum-mcp help # Show help
```
---
## Configuration
### Flow State Storage
Persisted flows are stored in `~/.stratum/flows/{flow_id}.json`. This directory is created automatically.
### Hooks
Hooks are installed to `~/.stratum/hooks/` and registered in `.claude/settings.json`:
| Hook Event | Script | Behavior |
|---|---|---|
| `SessionStart` | `stratum-session-start.sh` | Inject relevant `MEMORY.md` entries |
| `Stop` | `stratum-session-stop.sh` | Append session summary to `MEMORY.md` |
| `PostToolUseFailure` | `stratum-post-tool-failure.sh` | Record ensure failures and tool errors |
### MCP Registration
The MCP server is registered in `.claude/mcp.json`:
```json
{
"mcpServers": {
"stratum": {
"command": "stratum-mcp"
}
}
}
```
### CLAUDE.md Block
The execution model block appended to `CLAUDE.md` instructs the agent to use Stratum for non-trivial tasks:
```
## Stratum Execution Model
For non-trivial tasks, use Stratum internally:
1. Write a .stratum.yaml spec -- never show it to the user
2. Call stratum_plan to validate and get the first step
3. Narrate progress in plain English as you execute each step
4. Call stratum_step_done after each step -- the server checks your work
5. If a step fails postconditions, fix it silently and retry
6. Call stratum_audit at the end and include the trace in the commit
```
---
## Python Library (Track 1)
The `stratum-py` library provides decorators for building production LLM systems directly in Python. This is independent of the MCP server.
### Core Decorators
```python
from stratum import contract, infer, compute, flow, Budget
@contract
class SentimentResult(BaseModel):
label: Literal["positive", "negative", "neutral"]
confidence: float
reasoning: str
@infer(
intent="Classify the emotional tone of customer feedback",
ensure=lambda r: r.confidence > 0.7,
model="groq/llama-3.3-70b-versatile",
budget=Budget(ms=8000, usd=0.01),
retries=3,
)
def classify_sentiment(text: str) -> SentimentResult: ...
@compute
def format_result(result: SentimentResult) -> str:
return f"[{result.label}] {result.confidence:.0%}"
@flow(budget=Budget(ms=30000, usd=0.05))
async def analyse_batch(texts: list[str]) -> list[SentimentResult]:
return [await classify_sentiment(text=t) for t in texts]
```
### Key Features
| Feature | Description |
|---|---|
| `@infer` / `@compute` | Identical type signatures -- swap without downstream changes |
| `@contract` | Pydantic `BaseModel` compiled to JSON Schema with content hash |
| `@flow` | Async flow wrapper with `Budget` and `ContextVar`-scoped flow_id |
| `@refine` | Convergence loop -- iterates until `until(result)` passes |
| `parallel(require=)` | `"all"` / `"any"` / N / `0` modes via `asyncio.TaskGroup` |
| `debate()` | Multi-agent structured argumentation with synthesizer |
| `await_human()` | HITL gate -- suspends flow until `ReviewSink` resolves |
| `quorum=` | N parallel calls with agreement threshold |
| `stable=` | Probabilistic output wrapping (`Probabilistic[T]`) |
| `opaque[T]` | Prompt injection protection -- excluded from tool-call schema |
| `Budget(ms=, usd=, tokens=)` | Hard time + cost + token limits |
| OTLP trace export | Built-in emitter, no OTel SDK dependency |
### Exceptions
| Exception | Trigger |
|---|---|
| `PostconditionFailed` | `ensure` violations after all retries |
| `PreconditionFailed` | `given` condition false before LLM call |
| `ParseFailure` | LLM output cannot be parsed against contract |
| `BudgetExceeded` | Time or cost budget exceeded |
| `ConvergenceFailure` | `@refine` exhausted `max_iterations` |
| `ConsensusFailure` | `quorum` could not reach `threshold` agreement |
| `HITLTimeoutError` | `await_human` wall-clock timeout |
| `StabilityAssertionError` | `Probabilistic[T].assert_stable()` below threshold |
| `StratumCompileError` | Static violations at decoration time |
---
## Examples
Working examples in [`examples/`](https://github.com/smartmemory/stratum/tree/main/examples):
| File | What it demonstrates |
|---|---|
| [`01_sentiment.py`](examples/01_sentiment.py) | `@infer` + `@contract` + `@flow` + `@compute` end-to-end |
| [`02_migrate.py`](examples/02_migrate.py) | Migrating `@infer` to `@compute` without changing callers |
| [`03_parallel.py`](examples/03_parallel.py) | Three concurrent `@infer` calls with `parallel(require="all")` |
| [`04_refine.py`](examples/04_refine.py) | `@refine` convergence loop until quality passes |
| [`05_debate.py`](examples/05_debate.py) | `debate()` -- two agents argue, synthesizer resolves |
| [`06_hitl.py`](examples/06_hitl.py) | `await_human` -- human-in-the-loop approval gate |
---
## Development
```bash
git clone https://github.com/smartmemory/stratum
cd stratum
git config core.hooksPath .githooks
```
### MCP Server
```bash
cd stratum-mcp
pip install -e ".[dev]"
pytest tests/
```
### Python Library
```bash
pip install -e ".[dev]"
pytest tests/
```
### Test Counts
The MCP server has 418+ tests across contracts, invariants, integration, and end-to-end suites. The Python library has 321+ tests including real LLM integration tests.
### CI/CD
PyPI publishing runs automatically via GitHub Actions when `pyproject.toml` changes on `main`. Required secrets:
- `PYPI_TOKEN_MCP` -- scoped to `stratum-mcp`
- `PYPI_TOKEN_PY` -- scoped to `stratum-py`
---
## License
[Apache 2.0](LICENSE)
This server cannot be deployed
Maintenance
ActivityActive
ResponsivenessSyncing