A LangGraph-based recursive self-improvement (RSI) engine that turns any formal system into a self-inspecting, self-mutating, scale-aware scaffold.
A law is any generative formal system that can produce lesser replicas of itself (instantiations): a physics theory, a legal code, a metaphysical schema. A scale (or base) is a layered rewrite grammar that lets one compact high-level statement expand into many consistent low-level instances — the way binary encodes decimal, or a zodiac encodes individual charts. NomosLoop's job is to recover those scales from raw text, expose them as mutable state, and then evolve them so each generation spawns more world per symbol: higher instantiation capacity.
formal text / PDF
→ ingest → scale-induction (motifs → LLM-identified bases + moduli)
→ spawn mutator swarm → inhabit mesh world → fitness by instantiation capacity
→ runnable artifact → elevate lattice v0 → v1 → repeat
Descriptive accuracy is cheap. What matters is compression + generative power: one equation that births arbitrarily many consistent worlds (e.g. one 5D vacuum statement → 4D gravity + Maxwell + dilaton + all their charts, gauges, and field configurations).
Design rules enforced by the loop:
- Compression + generativity beats mere description.
- Every pattern found in an instantiation becomes eligible for elevation into the higher law.
- No descriptive bloat without worldly capacity — rejected mutations leave the lattice untouched.
- Everything is a mutation target: graph topology, routing policy, fitness function, artifact generator.
pip install -r requirements.txt # runtime deps
pip install pytest # only needed to run the tests
# Cheapest working version: deterministic, no API keys
python -m nomosloop.cli --input examples/5d_em_fragment.txt --offline
# Run the shipped first artifact (stdlib only)
python artifacts/artifact_v0_5d_em.py
# Run tests
python -m pytest tests/ -q
# Benchmark: measured capacity vs a no-mutation baseline (scoreboard)
python -m nomosloop.benchmarkTypical benchmark scoreboard:
case capacity instances coverage accepted rolled iter
m=0,i=1 9.224 256 1.00 0 0 1
m=6,i=2 11.697 1536 1.00 8 4 2
[bench] PASS: measured capacity delta +2.474 (9.224 -> 11.697)
With OpenRouter keys (mechanical work → cheap model, synthesis → frontier). A .env file is loaded automatically at import; real environment variables always win:
cp .env.example .env # fill OPENROUTER_API_KEY
python -m nomosloop.cli --input examples/5d_em_fragment.txt --iterations 2
python -m nomosloop.cli --input path/to/code.pdf --iterations 2 --dump-state state.jsonTypical offline output:
[nomosloop] lattice v3 levels=['L3', 'L2', 'L1', 'L0']
[nomosloop] motifs=16 holdout=4 proposals=6 accepted=4 rollbacks=0
[nomosloop] capacity: baseline=5.303 v1=6.228 v2=6.722 v3=6.722
[nomosloop] mesh=31 fitness={... 'capacity': 5.303, 'instances': 36}
[nomosloop] artifact: artifacts/artifact_<run>.py
NomosState carries tokens, motifs, a versioned scale_lattice plus full lattice_history, mutation_proposals / accepted_mutations, a serializable mesh snapshot (mesh_nodes / mesh_edges), the inhabitants and their spawn_log, fitness_report, artifact_path / artifact_code, loop counters, and audit trails (routing_log, topology_log).
Each ScaleLevel carries what the frontier LLM identified for it:
base/base_spec{radix, alphabet, encoding}— the rewrite radix packing this level into the one below (e.g.R-vac5 → [gravity, gauge, scalar]; KK-triple(g, A, φ); coordinate-digits).modulus/modulus_spec{period, constraint, operator}— the cycle/wrap/constraint closing the scale (e.g. cylinder∂₅=0,U(1)gauge periodicity,15 = 10 + 4 + 1partition, Bianchi identities).
ingest → scale_induction → mutation_proposal → mesh_spawn
→ fitness → artifact_emit → rectify (⇢ mutation_proposal | END)
| Node | Delegates to | Tier |
|---|---|---|
ingest |
tokenizer_agent |
cheap |
scale_induction |
motif_miner_agent → hierarchy_synthesizer_agent + consistency_checker_agent |
cheap → frontier |
mutation_proposal |
N × mutation_proposer_agent (NOMOSLOOP_MUTATORS, default 4) |
cheap |
mesh_spawn |
MeshWorld.spawn_from_lattice + inhabitant spawn round |
local |
fitness |
N × fitness_evaluator_agent (measured by execution) |
frontier |
artifact_emit |
artifact_designer_agent → render_artifact |
frontier |
rectify |
accepted mutations in score order, keep/rollback gate | local |
Nodes are thin orchestrators; heavy lifting lives in nomosloop/agents.py (AGENT_REGISTRY). Topology is data: TOPOLOGY, inspect with describe_topology(), rewire at runtime with mutate_topology({"add_edge": [...]}) and rebuild via build_graph().
| Tier | Model (override via env) | Tasks |
|---|---|---|
cheap |
OPENROUTER_CHEAP_MODEL (default google/gemini-2.0-flash-001) |
tokenise, motif mine, consistency, unit tests, simple rewrites, bulk mutation generation |
frontier |
OPENROUTER_FRONTIER_MODEL (default anthropic/claude-opus-4.5) |
hierarchy synthesis, scale invention, fitness eval, artifact design |
No OPENROUTER_API_KEY or NOMOSLOOP_OFFLINE=1 → deterministic offline heuristics, so the demo, artifact, and tests run keyless. Every decision is logged to routing_log (agent, task, tier, model, offline). load_dotenv() is called once at package import, before the graph reads its module-level configuration.
The frontier synthesizer is prompted with a strict JSON schema demanding, per level, base, base_spec, modulus, modulus_spec, executable rules, and an expansion_map grounding the top rule to the base.
Online, _extract_json() (tolerant of prose and code fences) and _validate_llm_lattice() accept the response verbatim (provenance = frontier-llm:base-modulus). The validator is strict: 2–6 levels, each with ≥1 rule and a non-blank base and modulus. A level missing either is rejected outright — no placeholder is invented, because the point is that the LLM identifies them. Offline or on invalid JSON, a 5D-EM-flavoured heuristic fills the same shape so the pipeline never breaks. The consistency checker independently flags any level missing a base or modulus.
Every mesh node is inhabited by one agent holding a minimal seed of the NomosLoop itself — {law_id, lattice_version, rule_id, level, generation, budget, offspring}. From that seed an inhabitant can instantiate:
- peers (
spawn_peer) — same-level agents embodying a mutated variant of its rule, e.g. a new Maxwell variant beside the old one; - children (
spawn_child) — lower-level agents embodying the downward expansion of its rule, e.g. a concreteA_μmode fromPOTENTIAL→FIELD.
Three guards are non-negotiable:
- Same-or-down only — an inhabitant can never spawn above its own level; an inhabitant at the ground base
L0is sterile downward. - Budget — every spawn costs 1; at 0 the inhabitant is sterile. Heirs inherit the parent's remaining budget; newborn objects start at 0 until elevation grants more.
- Provenance — each spawn appends a
spawn_logevent and mesh edges (spawned-peer/spawned-child/expands-to), so the full genealogy is inspectable from state alone.
mesh_spawn runs one governed round per cycle: up to NOMOSLOOP_SPAWN_PEERS peers and NOMOSLOOP_SPAWN_CHILDREN_CAP children. Newborns do not spawn further in the same round, so the population cannot explode in one step.
There is no direct agent-to-agent messaging in graph/v0. Exchange is blackboard-style through shared state:
- Mutators read the same
scale_lattice, each owning onelevel × operationslice, and append tomutation_proposals. mesh_spawnmaterializes the world (mesh_nodes,mesh_edges[src,dst,"expands-to"]) and its inhabitants; agents re-hydrate it withMeshWorld.from_snapshot()and sense it vianeighborhood(id, radius),upward(id),downward(id).fitnessscores proposals againstmesh_stats;rectifyelevates the winner. The next generation reads the new lattice version.
Distance is topological, not Euclidean: hops along expands-to edges and hierarchy steps. There are no coordinates, embeddings, or L2 norms in the codebase — deliberate, because L3…L0 is a discrete rewrite hierarchy where expansion-steps are the natural metric. To add Euclidean structure, embed rules (e.g. by (abstraction_score, generative_power) or text embeddings) and add MeshWorld.embed() + distance for niching/repulsion.
Fitness is measured instantiation capacity — see the next section.
Fitness is no longer estimated from declared generative_power; mutations are
scored by applying them and executing the rewrite structure:
enumerate_instances()walksexpansion_mapwith executable semantics:[children]rewrites into those rules,[]is an explicit terminal macro, absent means implicit downward pack into the lower level. Ground rules instantiate their modulus-closed space (int modulus period→ that many values, e.g.256byte classes; otherwiseradix²oralphabet²), producing executable content (positional digit strings, chart slots) with distinct instances deduplicated by construction.measured_capacity = log2(1 + distinct instances) × 10 / log2(10 + description symbols).- At induction, ~1 in 4 mined motifs is held out; mutations must not regress
holdout_coverage(fraction of held-out motifs still in the lattice's vocabulary). - Score:
5 + 4·tanh(20·Δcapacity) + 2·tanh(10·Δcoverage), clamped 0–10. The LLM is consulted for an audit note only; it cannot set the number, and the proposer cannot influence it by writinggenerative_power. rectifyapplies accepted mutations in score order, each behind the gate: kept only if measured capacity does not regress and coverage does not drop; otherwise the mutation is rolled back and logged instate["rollbacks"].capacity_historyrecords every version (induction + elevations).
rewire_topology mutations patch TOPOLOGY only if the patch references
known nodes, duplicates no edge, is acyclic, and build_graph() still
compiles; otherwise the previous topology is restored. Kept patches persist
for the process and take effect on the next build_graph() call.
Primary modality: elegant, runnable, self-contained demos. artifacts/artifact_v0_5d_em.py (stdlib only) shows the recovered L3 → L2 → L1 → L0 lattice with per-level BASE/MODULUS, executable expand() fan-out (48 ground instances from one principle), capacity accounting, and the elevated rewrite elevated_kk_split() (5D vacuum ⇒ 4D triple, asserting 10 + 4 + 1 == 15). self_test() asserts the rewrite closes; main() prints the lattice.
nomosloop/ state.py (schema) | routing.py (OpenRouter + .env) | agents.py (sub-agents)
inhabitants.py (loop-carrying agents) | mesh.py (world) | graph.py (LangGraph + RSI loop)
rewrite.py (executable enumeration + measured fitness + holdout)
mutations.py (pure lattice transforms) | benchmark.py (scoreboard)
cli.py | ingest.py (txt/pdf)
examples/ 5d_em_fragment.txt (Kaluza-Klein seed law)
binary_decimal_fragment.txt (radix/byte benchmark seed)
artifacts/ artifact_v0_5d_em.py (first runnable artifact)
tests/ test_pipeline_offline.py (end-to-end, keyless)
test_spawn.py (inhabitant peer/child spawn + guards)
test_lattice_validation.py (LLM lattice parse + base/modulus contract)
test_measured_fitness.py (executed fitness, rollback + topology gates)
.env.example OPENROUTER_API_KEY / CHEAP / FRONTIER model ids
| Variable | Default | Effect |
|---|---|---|
OPENROUTER_API_KEY |
— | unset → offline heuristics |
OPENROUTER_CHEAP_MODEL |
google/gemini-2.0-flash-001 |
mechanical tasks |
OPENROUTER_FRONTIER_MODEL |
anthropic/claude-opus-4.5 |
synthesis / fitness / artifacts |
NOMOSLOOP_OFFLINE |
auto (no key → offline) | 1 forces deterministic run |
NOMOSLOOP_MUTATORS |
6 |
gen-0 swarm size (--mutators 0 = baseline) |
NOMOSLOOP_ACCEPT |
5.0 |
fitness threshold 0–10 |
NOMOSLOOP_SPAWN_BUDGET |
2 |
spawns granted per inhabitant |
NOMOSLOOP_SPAWN_PEERS |
2 |
peer spawns per cycle |
NOMOSLOOP_SPAWN_CHILDREN_CAP |
8 |
child spawns per cycle |
CLI: --input (txt/md/tex/pdf) --title --iterations --artifact --offline --dump-state.
graph/v0mutators are single-shot functions, not persistent agents with memory; mesh sensing is read-only during scoring.- Spawn content is currently deterministic scaffolding; LLM-driven peer-to-peer negotiation is not wired into the inhabitant spawn round yet.
- Offline motif mining is regex-based; online LLM labels are not yet fed back into motif kinds.
- Rewrites execute structurally (expansion + base/modulus specs); arbitrary physics notation in
pattern/rewritestrings is not parsed or symbolically checked. - Ground spaces are capped (
MAX_INSTANCES = 10k, truncation flagged);description_costis symbol length, not Kolmogorov complexity. - Topology patches take effect on the next
build_graph()(the running compiled graph is not hot-swapped mid-run). - Natural extensions: persistent inhabitants with mailboxes, learned Euclidean embeddings for speciation, Lean/Agda artifact backends, self-verifying rewrites (instances must satisfy the parent statement).
MIT — see LICENSE.