RED-first fixes for the three bugs the source-blind field test caught. Two
are the same failure the whole cycle keeps circling: prose that tells a
caller something the code does not do.
- The chart not-found refusal prescribed `aura exec --tap <NODE.FIELD>=<FOLD>`.
The selector takes a declared tap NAME, so following the refusal verbatim
earns a second refusal ("the tap plan names 'sub.value', but the blueprint
declares no such tap"), and the flag is not needed at all — a plain run
records every declared tap. This sentence predates the cycle and was carried
over verbatim while the rest of the refusal was corrected; the field test
ran it as written, which is exactly why it ran it.
- `aura measure ic <family-handle>` answered "no recorded run '…' under
runs/traces" for a handle that IS recorded — the value the campaign run
printed, that `chart` renders, whose directory exists. A family holds
members rather than one series, so IC is measured over a member. The
refusal now says that and lists the member keys, which also closes the
friction that nothing on any text surface prints them: reaching a member
previously meant the very `ls runs/traces/` this cycle set out to retire.
- C28's #297 note still gave the entry points' signature as the retired
`(_, Vec<String>)` pair in the present tense, contradicting C27 one contract
over. The close commit claimed lockstep; this makes it true.
Both code fixes have tests that fail without them. The field test also found
four friction items and two spec gaps, filed rather than fixed here.
refs #309
aura
aura is a "game engine for traders." It is a Rust framework — and a playground — for authoring trading strategies as composable dataflow nodes, backtesting them deterministically and massively in parallel, validating them (sweep / Monte-Carlo / walk-forward), and freezing a validated strategy into a standalone bot with a broker connection. Its differentiator is the World: a program that constructs and orchestrates whole families of backtests, not the single run.
This README is a discoverability map of the shipped aura CLI — what commands
exist and what they do. It deliberately keeps per-flag detail light; the two
authoritative sources for exact syntax are always:
aura <command> --help— the exhaustive flag grammar.- The design ledger (
docs/design/INDEX.md) and glossary (docs/glossary.md) — the concepts and invariants behind the surface.
Build & run
aura is a Cargo workspace. Build everything and you get one binary named aura:
cargo build --workspace # binary at target/debug/aura
cargo build --workspace --release # optimized, at target/release/aura
Invoke it as aura <command> … (examples below use the plain name).
Concepts in one breath
- Blueprint — a serialized signal graph as data: a param-generic
price → biasnode graph. A blueprint file (blueprint.json) is the unit a downstream consumer loads and executes or varies. - Open vs. closed — a blueprint with unbound (free) numeric knobs is open;
one with every knob bound is closed. A plain
execwants closed; naming a knob as a campaign axis (or anexec --override) is what reopens it. - Family — the set of runs a campaign document's process pipeline produces over a blueprint (a sweep grid, a Monte-Carlo seed set, a walk-forward window sequence). Families are persisted in a content-addressed store and can be listed, ranked, and reproduced.
- Axis — one named, sweepable knob of a blueprint (e.g.
fast.length), bound with a comma-separated value list. A gang fuses several sibling knobs into one axis (one value drives all members).
Executing a document
aura exec <target> (#319) is the one executor verb: <target> is either a
loaded blueprint file (a single synthetic run) or a campaign document
(a .json file or its registered 64-hex content id — instruments × windows ×
strategy × param axes × process, see docs/authoring-guide.md §3). graph
renders a blueprint's structure so a mis-wire is visible before a run.
| Command | Purpose |
|---|---|
aura exec <bp.json> |
Run one backtest of a closed blueprint and print its report. An open (free-knob) blueprint is refused with a clean error — bind it, or vary it as a campaign axis. |
aura exec <bp.json> --override <node.param>=<value> |
Reopen one bound param for this single execution only (the value is recorded raw in the manifest); repeatable. |
aura exec <bp.json> --tap <name>=<fold> |
Subscribe a declared tap to a fold (record/count/sum/mean/min/max/first/last) for this run; repeatable. |
aura graph <bp.json> |
Render the blueprint's structure as an interactive HTML DAG so a mis-wire is visible before a run. Omit the file to render the built-in sample; a named-but-unreadable file is a usage error. |
aura graph introspect --params <bp.json|id> |
Discover a blueprint's open, sweepable knobs. Prints each as <name>:<kind>, bound params trailing default=<value>. Run this first to learn the axis names for a campaign document. |
aura exec <campaign.json|id> [--parallel-instruments <n>] |
Execute a campaign document — a grid family, gates, walk-forward, Monte-Carlo, and cross-instrument generalization per its process document, one cell per (strategy, instrument, window). |
aura runs families |
List every persisted family (id, kind, member count). |
aura runs family <id> [rank <metric>] |
List one family's members, optionally ranked best-first by an R metric (e.g. sqn_normalized, expectancy_r). |
aura reproduce <family-id> |
Re-derive every member of a persisted family from the content-addressed store and check it is bit-identical (the C18/C1 determinism guarantee). |
Important contract — every open knob is mandatory on a campaign axis. A
campaign document's strategies[].axes must name every open knob
graph introspect --params lists: there is no default value — pin a knob you
don't want to vary with a single-value axis ({"kind":"I64","values":[4]}).
Omitting one is a clean error naming the missing knob, not a silent default.
Use aura exec --help for the exact flag grammar, docs/authoring-guide.md
for the campaign document shape (data window, naming, presentation/emit).
Authoring & introspecting topology
A blueprint is authored declaratively from a JSON op-list: an ordered list
of by-identifier construction ops replayed against the standard node vocabulary.
Adding a new node type to that vocabulary is Rust, not JSON — see
docs/authoring-guide.md, §0, "Authoring a new node in Rust".
| Command | Purpose |
|---|---|
aura graph build |
Read an op-list document on stdin, build the blueprint, and print its canonical JSON to stdout (no trailing newline — it is the content-addressed artifact). |
aura graph introspect --vocabulary |
List every node type available to op-lists. |
aura graph introspect --node <T> |
Show one type's input ports, output fields, and param paths (with the bind-value form). |
aura graph introspect --unwired |
Read a partial op-list on stdin and list its still-open interior slots. |
aura graph introspect --content-id |
Read an op-list on stdin and print the SHA-256 content id of the blueprint it would build. |
aura graph introspect --identity-id |
Read an op-list on stdin and print the SHA-256 identity id — the topology's debug-name-blind id, so two documents (or an op-script and a Rust-built blueprint) expressing the same topology print the same id. Combinable with --content-id (both ids, one per line, content id first). |
Op-list shape
Each element is one op, tagged by "op". Node params are bound with the typed
Scalar form — a tagged, capitalized-kind object: {"I64": 5}, {"F64": 1.5}, {"Bool": true}. A worked example (an SMA-crossover → bias signal):
[
{"op":"source","role":"price","kind":"F64"},
{"op":"add","type":"SMA","name":"fast","bind":{"length":{"I64":2}}},
{"op":"add","type":"SMA","name":"slow","bind":{"length":{"I64":4}}},
{"op":"add","type":"Sub"},
{"op":"add","type":"Bias"},
{"op":"feed","role":"price","into":["fast.series","slow.series"]},
{"op":"connect","from":"fast.value","to":"sub.lhs"},
{"op":"connect","from":"slow.value","to":"sub.rhs"},
{"op":"connect","from":"sub.value","to":"bias.signal"},
{"op":"expose","from":"bias.bias","as":"bias"}
]
Piping this document into aura graph build emits a blueprint.json you can
then feed to exec above:
aura graph build < crossover.ops.json > crossover.bp.json
aura graph introspect --params crossover.bp.json
The op kinds are source, input, add, feed, connect, expose, tap,
gang, doc, and use (tap declares a recorded measurement point on an
interior wire; doc declares the composite's one-line meaning, required at
register — C29; use splices a registered blueprint in as a nested composite,
by content id or label; add additionally takes an args object for
arg-bearing types (Session, LinComb, CostSum) — structural,
non-scalar construction consumed before bind; see the authoring guide). See
aura graph introspect --node <T> for a type's exact ports and the op-script
grammar in the design ledger for the full semantics.
Where to go deeper
aura <command> --help— exhaustive, always-current flag grammar.docs/design/INDEX.md— the design ledger: architecture, invariants, the World.docs/glossary.md— canonical terminology (blueprint, family, axis, sweep / Monte-Carlo / walk-forward, R, content-id, …).