Avalon
An adaptive AI runtime. MIT © Lunar Labs.
The LLM is a stateless function f(context) -> token. Avalon is the runtime
around it that persists, perceives continuously, holds state, learns while
running, and acts on a clock — the model is demoted to a frozen language
cortex; all learning lives in hot-swappable LoRA adapters gated by measured
fitness.
This is v1: the first slice of a much larger vision. Every module maps to a constraint measured on real hardware (Mac Mini M4 / MacBook M1 Pro, Qwen3-4B 4-bit, MLX, 2026-07). Where the research hit a wall, v1 ships an honest interface instead of a fake.
Architecture
external events (sensors, messages, tools)
│ land whether or not anyone is talking
▼
┌──────────────────────────────────────────────────────────┐
│ HEARTBEAT (intrinsic time) │
│ tick loop · nested clocks now/day/project/life │
│ ONE append-only event stream · atomic JSON state │
│ daemon mode: avalon serve · intake: avalon inject │
└──────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ DEEP TIME (memory with half-lives) │
│ salience decays exponentially (36 h default) · surprise │
│ events RE-INK referenced memories · event stream -> │
│ episodes · learned circadian histogram (starts blank) │
└──────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ EPISODIC BUFFER (provenance-typed, surprise-gated) │
│ observed │ inferred │ said-by-assistant │
│ ONLY observed is trainable — anti-fabrication defense │
└──────────────────────────────────────────────────────────┘
│ observed entries, retrieval-task Q→A
▼ (NEVER transcripts)
┌──────────────────────────────────────────────────────────┐
│ CONSOLIDATION (sleep) per night │
│ sufficiency gate → 2:1 mandatory rehearsal → collapse │
│ cap → training manifest → pluggable Trainer │
└──────────────────────────────────────────────────────────┘
│ candidate adapter (one file)
▼
┌──────────────────────────────────────────────────────────┐
│ FITNESS GATE (git for a mind) │
│ probe fixed held-out set ± adapter → DIFF = what it │
│ learned → regression check FIRST → promote | roll back │
└──────────────────────────────────────────────────────────┘
│ promoted
▼
┌──────────────────────────────────────────────────────────┐
│ ADAPTER REGISTRY (the registry IS the tool list) │
│ named callable skills · base-version pin · fitness score │
│ ONE live adapter · 2-3 hot pre-fetched · 5 ms swap │
└──────────────────────────────────────────────────────────┘
│ adapter_path per request
▼
┌──────────────────────────────────────────────────────────┐
│ FROZEN BASE (OpenAI-compatible server, mlx_lm style) │
│ never trains · auto-launched local mlx_lm server on a │
│ free port (pilot-run), or your own base_url │
└──────────────────────────────────────────────────────────┘
Measured operating points
These are bench numbers, not defaults pulled from thin air. They live in
avalon/config.py with citations in comments.
| Constraint | Measured value | Where it's enforced |
|---|---|---|
| Consolidation point | lr 5e-5, 48 iters — 3/3 recall after full context wipe; 8 iters = degenerate loops | config.py, pinned in every manifest |
| Paraphrases per fact | ~10 (27 rows about one fact = mode collapse) | consolidate.cap_rows_per_fact |
| Rehearsal | 2:1 mandatory — 0:1 → 0/3 prior facts survive; 2:1 → 3/3 | consolidate.build_manifest |
| Training shape | Q→A retrieval task — transcripts scored 0/3 on a perfect loss curve | consolidate.pair_from_entry, lunarlabs-core TrainingPair |
| Sufficiency | skip empty days — updates on signal, not schedule | build_manifest sufficiency gate |
| Live adapters | ONE — multi-adapter composition costs ~40% each | registry.activate |
| Adapter swap | 5 ms attach; pre-fetch 2-3 hot | registry hot cache + swap latency |
| Adapter size | rank-16, 8 layers = 14.7 MB | config.py |
| Trainable memory | observed only — inferred / said-by-assistant never train | buffer.trainable() |
| Memory half-life | 36 h default — salience = surprise × strength × 0.5^(age/half-life); re-inking resets the clock | deeptime.effective_salience, buffer.reink |
| Episodes | 30 min silence closes an episode; consolidation reads episodes, not a flat day-file | deeptime.segment_episodes |
| Circadian | learned histogram — 24 hour buckets fed by ticks/events; uniform until lived | heartbeat.hour_histogram, deeptime.expected_activity |
| Promotion | behavioral gate — loss lies at 48 iters (fact blending looked perfect) | gate.run_gate |
Run the public proof
Requirements: an Apple-silicon Mac, Python 3.10 or newer, git on your path (for
a one-time dependency fetch; run git --version and install the Xcode command
line tools if macOS prompts you), about 4 GB of free disk space, and an internet
connection for the first run. The first run
downloads a roughly 2 GB model once. Later runs reuse it. Training time depends
on the Mac and may take a few minutes.
pip install "avalon-ai[mlx]"
avalon demo
The command runs two proofs with one local 4-bit model:
- It teaches five invented toy facts, starts fresh answering processes whose prompts contain only the questions, and measures recall from the adapter.
- It gives the unguarded model a poisoned toy note, then asks Avalon's
witnessed-fact gate the same question. With no witnessed fact, the gate
answers
I do not know.and the model is called zero times.
No API key is needed. No cloud model receives the prompts. The only network use
is the one-time public model download. Demo files stay in .avalon/demo_run.
Exit code 0 means all five trained questions survived the context wipe and
the poison gate refused with zero model calls. Exit code 1 means the proof or
runtime failed. Exit code 2 means the machine cannot run MLX.
If startup reports that MLX cannot use Metal, run the command from a normal Terminal rather than a headless or virtualized session. If the model download is interrupted, reconnect and rerun the command. The download resumes and is reused.
The latest isolated-environment terminal capture is in
launch/clean-run-output.txt. It records 5/5
trained recall, 5/5 unseen-phrasing recall, and poison refusal with zero model
calls. Its header states the one untested first-run condition: the model was
already present in this Mac's shared Hugging Face cache.
Use it on your own facts
Turn your own structured config into witnessed facts, arm the gate, and ask:
avalon ingest-facts ./chatterbox.json # dry-run first: shows every candidate, mints nothing
avalon ingest-facts --commit ./chatterbox.json
avalon facts-gate on
avalon answer "what port is chatterbox on" # -> 8100 (witnessed: chatterbox.json:12).
avalon answer "what is the door code" # -> refusal: I do not know. (model calls: 0)
What the gate guarantees, stated plainly:
- It answers only from facts you minted, each carrying a verbatim source line. The value it returns is the witnessed value — never a guess.
- On a fact-shaped question whose key it cannot find among your witnessed
facts, it refuses with
I do not know.and the model is never invoked (zero model calls — there is no channel for it to confabulate into). That is the anti-fabrication guarantee, and it is the honest boundary of the claim: the gate refuses on a witnessed key it cannot find, not on every possible phrasing. A question it cannot key (e.g.chatterbox port?) is not a fact query and falls through to the normal chain. - A witnessed value still passes your send-time constraints (P09): a rule like "never reveal the door code" blocks even a witnessed value.
- Non-fact questions go to the normal draft + coverage chain, which needs a
model backend —
--backend residentreuses the demo's local model, or pointbase_urlat a server you run. A buyer with no model server still gets the canned witnessed value on fact queries (the refusal and the canned value need no model at all).
The gate defaults off — avalon answer behaves exactly as before until you
turn it on. avalon facts-gate status prints the resolved verdict (so a config
typo is visible) and how many facts you have minted (a flag with no facts still
falls through — flags are not reachability). avalon facts-gate off disarms it.
Daily use on your own data (real training)
One command a night, on your own notes, with real learning — no server to
set up, no mock, no silent hang. Apple silicon + the mlx extra:
pip install "avalon-ai[mlx]"
# 1. drop tonight's facts in (a .txt / .md / .jsonl file, or inject one line)
avalon ingest notes ~/notes/today.md
avalon inject "the Northstar cache listens on port 49321"
# 2. run the night — this trains for real and proves it
avalon pilot-run --ask "What port is the Northstar cache on?"
What pilot-run does, in order (each stage prints a wall-clock line):
- Preflight — Apple silicon,
mlx_lmimportable, Metal usable, ≥ 4 GB free. It stops here (exit 2) before any download if the machine can't run. - Intake — your notes become
OBSERVEDfacts (re-running the same file is a no-op; nothing is trained twice). - Extract — turns notes into Q/A pairs through a model server it
auto-launches for you on a free port (first run downloads the model once,
~2.5 GB), then shuts it down before training.
--no-extracttrains the notes as-is with no server. - Sleep — one real LoRA night (the shipped
MaskingMLXTrainer+ a resident gate that actually attaches the candidate adapter). - Verify — the adapter on disk must be a real (> 1 MB) LoRA, or you get
TRAINING DID NOT PRODUCE A REAL ADAPTERand exit 1. No stub ever counts. - Morning proof — the base model's answer vs the live-adapter answer to
each
--ask, printed verbatim, so you can see the weights changed.
--nights N loops it; a night with no new facts is a valid skip (exit 0) —
updates happen on signal, not on a schedule. Roughly 3–6 min per night on an
M-series Mac after the one-time download.
Exit codes: 0 promoted or validly skipped · 1 rolled back / stub adapter · 2 this Mac can't run it · 3 the model server couldn't start · 130 Ctrl-C (the child server is killed).
Privacy: nothing leaves your Mac except the one-time public model download. Your notes train into local weights and stay there.
pilot-run never uses MockTrainer. config.trainer_backend (the safe
default for every other command) is ignored here — this command always trains
for real, by construction.
Optional: deploy/ai.lunarlabs.avalon.pilot.plist runs pilot-run nightly at
03:00 with logs under <home>/logs/pilot.log.
No-GPU smoke test (FAKES learning, CI only)
python3 -m venv .venv
.venv/bin/pip install -e ".[dev,mlx]" # lunarlabs-core comes from git; mlx extra powers the live demo
.venv/bin/pytest # no GPU, no network
.venv/bin/avalon tick
.venv/bin/avalon serve --interval 60 # daemon mode; SIGTERM stops it cleanly
.venv/bin/avalon inject "the deploy moved to friday" # event -> buffer bridge
.venv/bin/avalon consolidate --dry-run
.venv/bin/avalon status
State lives in ./.avalon (override with --home). The default trainer hook
for serve / consolidate is MockTrainer, which writes a stub
.safetensors and learns nothing — it exists for CI and plumbing checks
only. Never use it to evaluate Avalon; use avalon pilot-run, which always
trains for real. On a GPU host, consolidate --mlx plugs in LocalMLXTrainer
(shells to mlx_lm lora). The default model backend is FrozenBaseClient;
tests use MockModel — no GPU in tests, ever.
The organs (P01–P22)
Every research proposal in the program now exists as a module: an interface
matching the measured constraint, a PROVISIONAL default where the real
signal isn't built yet, and the proposal's falsifier as runnable code whose
kill condition exits nonzero. The full map from inventory item to module,
status, and falsifier price is docs/the-organism.md.
bpc (P12, the north-star eval — first real run: docs/p12-first-run.md),
wagers (P06), coverage (P05, gate 3), nightindex (P03), vetoes
(P02), constraints (P09), retraction (P10), changes (P18),
timeweight (P15), flywheel (P01+P04), ledger (P07), masking (P08),
dreaming (P11), skills (P13), physio (P14), perception (P16),
stratify (P17), river (P19), cortex (P21), middlememory (P22),
plus tools/do-ceiling/ (P20, gate 2 kit).
What is still a wall
Stated plainly — the organs build interfaces around these, not over them:
- The real surprise signal — the MECHANISM now ships in Avalon:
predictive_surprise.PredictiveSurpriseScorerscores surprise as the live adapter's measured BPC prediction error on incoming observed text (DEFAULT OFF untilavalon predict-latencyprices the per-event forward pass). What stays JARVIS-side is the owner corpus, the owner model, and the felt-present tuning — not the organ. The other token-overlap heuristics (wager resolver, ledger matcher, change join) remain PROVISIONAL behind their Protocols. - Implicit adapter routing — untested;
NoRouterabstains, embedding similarity is the named candidate. - P12 first run (2026-08-04) — all three adapters LOSE to the frozen base on future-Carter BPC: a uniform style-drift tax (~+0.23) and zero future-specific gain. The adaptive thesis is unproven where it counts; the instrument to fix that now exists with baselines.
- Constraint application in weights — recites 3/3, applies 2/3; the
runtime verifier tier (
constraints.py) is the live answer. - Surgical forgetting — 3 methods failed; the organ is replay-with-
exclusion + periodic rebase (
retraction.py), not subtraction. - Subjective time / anticipation gradients — still research; deep time remains half-lives + episodes + circadian histogram.
- Base-upgrade retraining bill — pins enforced;
cortex.SwapBillprices it, the number is still an estimate. - Real training in CI —
LocalMLXTrainerandMLXScorerneed a GPU host; the suite exercises contracts via mocks only.
Metadata
Release files for avalon-ai 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| avalon_ai-0.1.1.tar.gz | 1.9 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| avalon_ai-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 3.3 MB
Release files / avalon_ai-0.1.1.tar.gz
| Download URL | avalon_ai-0.1.1.tar.gz |
|---|---|
| Size | 1.9 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8d621225b5a4aab9c58590548476b51ec35dd2ea6b62a376c210245f4e70bf0d
|
|
BLAKE2b-256 checksum How to use checksums |
67a2613c39be0093d022377ca8c2d6e35071f61186aa32bb10e3bf1b0177b367
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / avalon_ai-0.1.1-py3-none-any.whl
| Download URL | avalon_ai-0.1.1-py3-none-any.whl |
|---|---|
| Size | 1.5 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ebc12cc202c82b3b4bf5b234592fb2ddd846be5f96ea2ebe7cffc71f06e85d98
|
|
BLAKE2b-256 checksum How to use checksums |
6d1a21d9a2977f4464809ec2634a3dc5615916916f1ab22f2ed8ddda336d51a2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|