Verified composition platform for AI agents.
Named after Emmy Noether: type signature symmetry guarantees composition correctness.
cargo build --release -p noether-cli
export PATH="$PWD/target/release:$PATH"
noether compose "parse CSV data and count the number of rows"
# → { "ok": true, "data": { "output": 3.0 } }See the demos → — type safety, parallel execution, stage reuse, and the full agent flow.
Noether is a platform where AI agents decompose problems into typed, composable stages and execute them with reproducibility guarantees.
A stage is an immutable, content-addressed unit of computation:
stage: { input: T } → { output: U }
identity: SHA-256(signature) ← not a name, not a version, a hash
Two stages with the same hash are provably the same computation — forever, across machines, across repos.
The composition engine type-checks stage graphs before executing them. If stage_a outputs Record { url: Text, score: Number } and stage_b expects Record { url: Text }, the checker verifies compatibility using structural subtyping — no runtime surprises.
Noether is not a workflow orchestrator, AI agent framework, or pipeline runner. It is infrastructure for agents that need to compose and verify computation.
┌─────────────────────────────────────────────────────────┐
│ L4 — Agent Interface │
│ ACLI-compliant CLI · Composition Agent · Semantic Index │
├─────────────────────────────────────────────────────────┤
│ L3 — Composition Engine │
│ Type checker · DAG planner · Executor · Trace store │
├─────────────────────────────────────────────────────────┤
│ L2 — Stage Store │
│ Immutable SHA-256 registry · Lifecycle · 80+ stdlib │
├─────────────────────────────────────────────────────────┤
│ L1 — Execution Layer │
│ Nix hermetic sandboxing · Python/JS/Bash runtimes │
└─────────────────────────────────────────────────────────┘
| Crate | Purpose |
|---|---|
noether-core |
Type system (NType), effects, stage schema, stdlib (80+ stages), Ed25519 signing |
noether-store |
StageStore trait, MemoryStore, JsonFileStore, lifecycle validation |
noether-engine |
Lagrange graph format, type checker, planner, executor, semantic index, LLM agent |
noether-cli |
ACLI-compliant CLI — stage, store, run, build, compose, trace |
- Rust 1.75+
- Nix (optional, required for Python/JS stage execution)
git clone https://github.com/alpibrusl/noether
cd noether
cargo build --release -p noether-cli
export PATH="$PWD/target/release:$PATH"
noether version# List all stdlib stages
noether stage list
# Search by capability
noether stage search "format HTML report"
# Dry-run a composition graph (type-check only)
noether run --dry-run graph.json
# Execute with input
noether run graph.json --input '{"query": "rust async", "limit": 10}'
# Build a standalone binary
noether build graph.json --output ./my-app
# Run as HTTP microservice
./my-app --serve :8080# Set one LLM provider:
export VERTEX_AI_PROJECT=your-project VERTEX_AI_MODEL=gemini-2.5-flash
# or: export OPENAI_API_KEY=sk-...
# or: export ANTHROPIC_API_KEY=sk-ant-...
noether compose "parse CSV data and count the number of rows"
noether compose --dry-run "convert text to uppercase and get its length"
noether compose --verbose "sort a list and take the top 3" # show reasoningStages are defined as JSON and added to the store:
{
"name": "my_transform",
"description": "Transforms a search query into structured results",
"input": { "kind": "Record", "value": { "query": {"kind": "Text"}, "limit": {"kind": "Number"} } },
"output": { "kind": "List", "value": { "kind": "Record", "value": { "title": {"kind": "Text"}, "url": {"kind": "Text"} } } },
"language": "python",
"examples": [
{ "input": {"query": "rust async", "limit": 5}, "output": [] }
],
"implementation": "def execute(input_value):\n ..."
}noether stage add my-stage.json
# → { "id": "a4f9bc3e..." } ← SHA-256 of the signatureThe ID never changes unless the type signature or implementation changes.
Why "Lagrange"? The project is named after Emmy Noether, whose theorem connects symmetries to conservation laws via the Lagrangian (named after Joseph-Louis Lagrange). A Lagrange graph is what you write down to describe a computation; Noether's type system guarantees its correctness — the same relationship as Lagrangian ↔ conservation law.
{
"description": "Research digest pipeline",
"version": "0.1.0",
"root": {
"op": "Sequential",
"stages": [
{
"op": "Parallel",
"branches": {
"results": { "op": "Stage", "id": "a4f9bc3e..." },
"topic": { "op": "Const", "value": "rust async" }
}
},
{ "op": "Stage", "id": "b7d2e1a4..." }
]
}
}Operators: Stage · Sequential · Parallel · Branch · Fanout · Merge · Retry · Const
The type checker validates every edge before execution.
noether build compiles a composition graph into a self-contained binary with all custom stages bundled:
noether build graph.json --output ./fleet-briefing
# CLI mode
./fleet-briefing --input '{"fleet_name": "Nordic GmbH", "routes": [...]}'
# HTTP microservice (browser dashboard included)
./fleet-briefing --serve :8080
# GET / → browser dashboard (auto-populated with example input)
# POST / → execute graph, returns HTML or JSON
# GET /health → liveness checkNoether uses structural subtyping — no class hierarchy, no nominal types:
NType := Text | Number | Bool | Bytes | Null
| List<T>
| Record { field: T, ... } -- width subtyping
| Union<T1 | T2 | ...> -- normalized, deduplicated
| Stream<T>
| Any -- bidirectional escape hatch
Record { name: Text, score: Number, url: Text } is a subtype of Record { name: Text } — width subtyping means a stage that needs a subset of fields always accepts a richer record.
Stages follow a lifecycle: Draft → Active → Deprecated → Tombstone
noether store stats # store statistics
noether store health # audit: signatures, missing examples, orphans
noether store dedup # find near-duplicate stages (cosine similarity)
noether store dedup --apply # deprecate confirmed duplicates (with successor pointer)
noether stage activate <id> # promote Draft → ActiveEvery stage is indexed across three vectors (signature, description, examples). Search uses cosine similarity with weighted fusion:
noether stage search "convert temperature units"
noether stage search "parse and validate JSON schema"The same index powers noether compose — the LLM agent receives the top-20 candidates and constructs a composition graph.
Stages that need to persist state across runs use the built-in KV store (SQLite, ~/.noether/kv.db):
def execute(input_value):
import sqlite3, pathlib
db = sqlite3.connect(str(pathlib.Path.home() / '.noether' / 'kv.db'))
db.execute('CREATE TABLE IF NOT EXISTS kv (namespace TEXT, key TEXT, value TEXT, PRIMARY KEY(namespace,key))')
# read/write state across invocationsOr via the stdlib stages: kv_get, kv_set, kv_delete, kv_exists, kv_list.
| Category | Stages |
|---|---|
| Scalar | parse_number, parse_bool, to_string, parse_json, to_json |
| Collections | list_map, list_filter, list_reduce, list_sort, list_take, list_flatten, zip, group_by |
| Control | identity, const, branch_if, retry, validate_schema, coerce_type |
| I/O | http_get, http_post, read_file, write_file, kv_get, kv_set, kv_delete, kv_exists |
| LLM | llm_complete, llm_classify, llm_extract, llm_embed |
| Data | json_merge, json_path, csv_parse, csv_format, json_schema_validate, diff_objects, template_render |
| Text | regex_match, regex_replace, text_split, text_join, text_trim, text_contains |
| Noether | stage_get, stage_search, compose_graph, kv_list, list_length, format_trace |
Noether is designed to be called by agents, not to contain them:
# An AI agent calls Noether to execute a sub-problem
noether compose "extract key entities from these documents" --input '...'
# The agent receives structured ACLI output and continues
{
"ok": true,
"command": "noether",
"data": { "output": [...] },
"meta": { "version": "0.1.0" }
}Token efficiency: the composition graph travels as compact JSON (not prompt text). Only the final LLM stages consume tokens. Benchmarks show 60-80% token reduction vs. naïve chaining.
| Phase | Status |
|---|---|
| 0 — Foundation (type system, hashing, stage schema) | ✅ Done |
| 1 — Store + Stdlib (80+ stages, test harness) | ✅ Done |
| 2 — Composition Engine (DAG executor, traces) | ✅ Done |
| 3 — Agent Interface (Composition Agent, semantic index) | ✅ Done |
| 4 — Hardening (signatures, dedup, store health) | ✅ Done |
5 — Effects v2 (inference & enforcement, --allow-effects) |
✅ Done |
| 6 — NixExecutor hardening (timeout, error classification, warmup) | ✅ Done |
| 7 — Cloud Registry hardening (DELETE, paginated refresh, scheduler) | ✅ Done |
8 — Runtime budget enforcement (--budget-cents, BudgetedExecutor) |
✅ Done |
See roadmap.md for near-term improvements and future directions.
See CONTRIBUTING.md.
Areas where contributions are especially welcome:
- New stdlib stages (any domain)
- Language runtimes beyond Python (JS, Ruby, Go)
- LLM provider integrations (OpenAI, Anthropic, Mistral, Vertex AI supported; Ollama via OpenAI-compatible API)
- Type system extensions (generic types, row polymorphism)
European Union Public Licence v1.2 (EUPL-1.2) — see LICENSE.
The EUPL is a copyleft licence compatible with GPL, LGPL, AGPL, MPL, EUPL, and others. It was designed specifically for public sector and open-source software within the EU.