This walks the whole path once, end to end, with a rule that is actually in the library. Nothing here requires knowing how the compiler works.
The path is: idea → .nano source → generated IR → deterministic replay → CI → pull request.
Fifteen minutes, most of it spent writing the comment header.
git clone https://github.com/AetherAI3/Nano.git
cd Nano
python -m venv .venv
# macOS/Linux: source .venv/bin/activate
# Windows PowerShell: .venv\Scripts\Activate.ps1
python -m pip install -e ".[dev]"
python -m pytest -qPython 3.10+. The suite runs in a few seconds; run it often.
Pick something you can state as one threshold against one number.
When gross exposure reaches three times equity, propose that the book pause.
That is nano/library/risk/leverage_ceiling.nano, and it is a good shape for a
first contribution: one signal, one comparison, one intent, no ambiguity about
when it should fire.
Nano's grammar is small on purpose. A library entry stays inside the v0.1.0
subset — one every block, one if rule, AND-chained numeric conditions —
because that is what keeps the checked-in IR byte-stable across versions. If
your idea needs else, arithmetic, or or, it is a great
language proposal
but not a first library entry.
This is the step people skip, and it is the whole design.
Nano does not fetch anything. Your rule compares a number the host supplies. So before writing a line of source, answer: what is the number called, what range is it in, and who is responsible for producing it?
GROSS_LEVERAGE — gross exposure divided by equity. Nonnegative, rises as
the book gets riskier. The host owns the definition of
"gross" and publishes the value each 5m tick.
Two conventions matter here:
- Direction. In
risk/andwatchdog/, every series is nonnegative and rises as the situation worsens, so every control is a>=against a ceiling. A stack of them then reads the same way top to bottom. If your natural measurement falls as things get worse, the host publishes the complement. - No negative literals. Baseline IR cannot carry one. Signals with a
negative natural range are shifted or negated by the feed —
WILLR_POS,ZSCORE_NEG— and the header documents the transform.
If the signal already appears in the library README tables, that table is its definition and you do not need to redefine it. If it is new, it gets defined in your header.
Create nano/library/risk/leverage_ceiling.nano:
// Leverage ceiling: pause when gross exposure reaches 3x equity.
// REGIME: all of them. Leverage is a constraint, not a forecast.
// CONDITIONS: none. Armed whenever the book is open.
// INVALIDATION: none. It is reset by reducing size, deliberately, outside this
// rule.
// SHAPE: 5m; gross exposure moves on fills and on marks, and 5m is fast enough
// to catch both without re-checking a number that has not changed.
// NOT position_concentration_cap: that measures how the book is distributed,
// this measures how large it is in total. A perfectly diversified book at 5x
// gross passes the concentration cap and belongs to this rule.
// CALIBRATED ON: nothing instrument-specific, but 3x is a house figure rather
// than a universal one - a futures book and a cash equity book do not mean the
// same thing by "gross".
strategy LeverageCeiling {
agent RiskDesk
every 5m {
if GROSS_LEVERAGE >= 3 {
pause()
}
}
}
The header is longer than the program. That is normal and it is the point: the
source says what the rule computes, the header says when it is wrong. The five
required fields and the NOT convention are documented in
the library README.
agent RiskDesk names who the intent escalates to. It is a label in the IR, not
a process — Nano cannot notify anyone.
python scripts/check_contribution.py --write nano/library/risk/leverage_ceiling.nanoThat writes leverage_ceiling_ir.json in the library's exact format — scalars
one per line, effects inline, one node per line, so a strategy reads as a list
of steps in a diff. Do not hand-format it, and do not use nano compile's
output directly; its default JSON indentation is correct IR in the wrong shape.
Run it again without --write and it checks, rather than writes:
$ python scripts/check_contribution.py nano/library/risk/leverage_ceiling.nano
1 entry ready for review.It verifies the pair compiles to the pinned IR, that the IR round-trips, that
two runs over one frame produce identical results, that every signal is
documented somewhere a host implementer will look, that the header carries all
five fields, and that the entry declares no effects beyond intent.emit and
log.append.
That last one is the boundary. A library entry proposes intents and writes its own run log. It cannot place an order, call an API, read a clock, or reach the network — and nothing in the grammar lets it.
python -m pytest tests/test_library.py -qThe library tests discover your pair automatically. You do not need to write a test for a normal entry.
Add a focused fire/no-fire test only when your rule covers a runtime edge the suite does not already exercise — a new intent action in a category, a boundary that has to be exclusive, twin rules that must never both fire. When you do, pair every no-fire assertion with a positive control on the same frame:
def test_credential_age_alert_observes_rather_than_pausing():
graph = _load("watchdog/credential_age_alert.nano")
frame = MarketFrame(
timestamps=(0, 86400, 172800),
signals={"CREDENTIAL_AGE_DAYS": (79.0, 80.0, 91.0)}, # 79 is NOT >= 80
)
result = execute(graph, frame)
assert [(i.action, i.timestamp) for i in result.intents] == [
("OBSERVE", 86400),
("OBSERVE", 172800),
]A test that only asserts "nothing fired" passes just as well against a rule that never fires at all.
git checkout -b feat/leverage-ceiling
git add nano/library/risk/leverage_ceiling.nano nano/library/risk/leverage_ceiling_ir.json
git commit -m "feat(library): leverage ceiling risk control"
git push -u origin feat/leverage-ceilingFill in the template. The validation and boundary checkboxes are the ones a reviewer reads first — if you could not run something, say so rather than ticking it.
CI runs the full suite on Python 3.10 through 3.13, builds the wheel, verifies
your files are packaged in it, and runs scripts/check_contribution.py --all.
Everything CI runs, you can run locally first.
| You want to… | Go to… |
|---|---|
| Add a rule reading system or policy state, not markets | The watchdog/ category — same path, different signal table |
| Understand how a watchdog is admitted, evaluated, and receipted at runtime | The Watchdog profile — the runtime side; you do not need it to contribute an entry |
| Propose a category that does not exist | Open a strategy proposal first; a new category also updates EXPECTED_CATEGORIES in tests/test_library.py and the README table |
| Change the grammar, IR, or runtime | A language proposal before any implementation |
| Report something reproducibly broken | A bug report |
| Report something security-sensitive | SECURITY.md, privately, not a public issue |
Both follow from the same design, so they are worth stating plainly.
Anything that lets a rule act. Nano emits intents; the host's
DecisionGate decides. No ambient I/O, no external actuation, no clock or RNG
dependency, no path around the gate. This is not a roadmap item.
Proprietary strategy code, transcribed. Translate only an idea you can
describe cleanly. If you consulted public material, record it truthfully in one
non-empty SOURCE: line; if provenance is unknown, omit the line rather than
inventing a citation. Do not paste someone's implementation.