Done is earned.
Every task comes back with the receipts attached. The code executed, the tests measured against it, the structure parsed, the rules checked — four axes, four facts, on every single job. You get working code and the evidence it works, in the same delivery. Nothing to double-check, because the checking already happened.
■ python · 8 languages · local-first · AGPL-3.0 / commercial
In plain English
Skip the jargon. Here's what Olympus actually changes for the person using it.
You get your afternoons back
The review pass you do on every AI output — running it, poking it, finding the stub — is the part Olympus does for itself, before it hands anything over. What arrives has already survived it.
"Done" is a measurement, not an opinion
Work reaches done when it runs, the tests genuinely reach it, and the implementation is real. You can see which of those passed on any task, and replay the run that decided it.
It runs on your machine, on your terms
Works offline on the local tier, so routine jobs cost nothing per token and your code never leaves the building. You choose what is ever allowed to reach a paid API. Keys stay encrypted on disk, unlocked by your Windows login.
If it can't show it, it hasn't done it.
Automation you don't have to audit
The reason AI coding tools save less time than they promise is the review pass: nothing ran the output, so you run it. Olympus spends that effort itself, on every task, and hands you the result with the run attached. The time the tool saves is time you actually keep.
An agent that cannot declare its own victory has to earn it instead.
The day it caught its own reviewer
The gate's hardest test is the one it ran on itself.
16 August 2026. Olympus was asked to write a sorting function in C++, then in Java, Go, Rust, C#, JavaScript and TypeScript. Every one came back the same way: not wrong, but unverified. The gate could not certify a single one, and said so rather than passing them.
The cause was in the gate's own reviewer — one missing line, on a path only non-Python work ever reaches. Seven of eight languages had been going unchecked, and the failure was invisible from the outside because the affected code never lied; it refused to answer.
The verifier's own test suite could not have found this. It scores 41 out of 41 by testing each language directly, and never travels the path that was broken. It took real work, in seven languages, to surface it.
One line fixed. Same twenty jobs re-run: the number of them that could be honestly judged doubled.
A tool that can't check something has to say so. That's the whole product.
Where it stands, plainly
Every number here came from a run you can repeat.
What is proven: the gate scores 41/41 against a corpus of deliberately broken programs across eight languages, at zero model calls, and the verification surface matches the one that was approved. Measured 2026-08-27.
What is not: the parts the repair pass hasn't reached are unproven, a clean-machine install has not been rehearsed end to end, and 38 of 121 tracked items are still open. They're listed individually, not summarised into a percentage.
The full state, defect by defect →None of this needs to be believed.
Every number on this page was produced by actually running the code, and you can run the same commands and watch the same results print. That's what "check the proof" means: nothing here requires faith.
The rules checker is worth a look on its own. Shortcuts have to be registered: the build fails on an undeclared one.
Done is earned.
The whole pitch is that nothing here needs to be taken on faith: every claim on this page was produced by running the code. If it can't be shown, it doesn't ship.
The Gate: four axes, three verdicts, one way through.
A task reaches DONE only when the axes that ran justify it. The agent doesn't get to say it worked: it has to show it.
The problem every AI coding tool has: it tells you it's finished, and sometimes it's wrong. Not maliciously: it can't tell. It wrote something that looks right, nothing ran it, and "done" is a claim rather than a fact.
Olympus answers with a gate the agent does not control. Generated code is scored on four independent axes, each returning one of three verdicts. Only when the evidence justifies it does the claim reach done.
Execution: does it build and run?
The code must actually execute in a real run. Importing without crashing is not running. Compiling is not working. The evidence is the process that exited, the output it produced, the behaviour it demonstrated.
The run is the evidence →Coverage: do the tests reach the code?
A test file that sits next to the code and never touches it proves nothing. Coverage is measured, and the measurement is anti-tamper: each run carries a nonce the model never sees, so a fabricated coverage claim can't be written into the output.
Anti-tamper by construction →Structural: is it real, or a stub?
Structure is parsed, never grepped. A stub with a confident name is caught as what it is. A name and a docstring with nothing under them is a defect the system is built to find; it has found exactly that shape before.
Parsed, not grepped →Discipline: does it follow the rules?
Generated code must obey the project's own engineering rules: the eight laws and the registered shortcuts. This is checked mechanically, not by goodwill, so a shortcut that was never declared fails the build.
Checked mechanically →//Three verdicts, and one that never ships
Every axis returns PASS, FAIL, or UNAVAILABLE. The design decision the whole thing rests on:
An axis that cannot be measured must say so: never pass silently. A C++ build with no coverage tool present reports
coverage: UNAVAILABLE. It does not quietly become a pass.
A missing toolchain, a sandbox that wouldn't start, a language with no adapter: all of it lands as "I could not check this", and "I could not check this" does not ship. Unavailable is never a pass.
//The verifier is itself verified
A gate nobody tests is just another confident opinion. Olympus scores its own gate against a corpus of deliberately broken programs: stubs, swallowed failures, tests that pass on nothing, code that doesn't compile, across eight languages: C++, C#, Go, Java, JavaScript, Python, Rust, TypeScript.
Re-verified against the live tree 2026-08-27: 41/41, 0 gaps, 0 cases awaiting an adapter, 0 model calls. Wall-clock varies by machine and by how warm the Docker images are; the verdicts do not.
| Measure | Last measured | What it answers |
|---|---|---|
| Corpus gate | 41 / 41 | Does the gate catch what it is supposed to catch? |
| Model calls | 0 | The gate's own correctness costs no quota to prove. |
| Documented gaps | 0 | When a gap exists it is written down, never hidden. |
//The two principles underneath
- Verification must be measurable without spending quota. "Is the gate correct?" is a property of Olympus's own code: testable with hand-written artifacts whose right verdict is known in advance, zero model calls, in CI. "Are the results good?" needs real models, run deliberately on a fixed goal set, committed so it can be diffed.
- An axis that cannot be measured must say so. This vocabulary predates Olympus: an earlier system of ours shipped with
UNVERIFIABLEas a first-class verdict years before.
//What the gate is for
The gate is not a rubber stamp and not a wall. It is the difference between "I think it works" and "it demonstrably works", and it is the reason a false pass installs no wrong answer: a task that reaches done gets written to the semantic cache with validated=True, which future goals replay without re-running. That is why the gate being honest is load-bearing, not cosmetic.
//What this axis is
Every AI coding tool can say "it works". This axis exists because the agent does not get to be the one who says it. The artifact is run for real, and the only evidence that counts is the run's own record: the process that exited, the output it produced, the behaviour it demonstrated. The agent reports the claim; the sandbox reports the fact.
//How it does it
Generated code runs inside Tartarus, the Docker sandbox, isolated from the host — which is what makes "let's just run it and see" safe to do. The gate's Reviewer submits the artifact and the sandbox returns stdout, stderr and an exit status. That record is ground truth: measured by the machine, never self-reported by the model.
The evidence is the process that exited, the output it produced, the behaviour it demonstrated.
//Two kinds of failure are kept apart
A file that never got as far as running is a different fact from one that ran and failed its assertions. The gate classifies against the compiler's and interpreter's own words — syntaxerror, compilation terminated, cannot find symbol, undefined:, unresolved reference — so a build break reports as fail:broken and a failing test as fail:test_fails. The two need different repairs: the first needs the code fixed, the second needs the behaviour fixed. Collapsing them tells a repair loop the wrong thing to do.
//The verdicts
| Verdict | What it means |
|---|---|
| PASS | The sandbox run exited clean; the output is on record. |
| FAIL | The run failed — it did not build, or the assertions did not hold. |
| UNAVAILABLE | The sandbox itself could not run — most often a Tartarus image that is not built. That is a statement about the install, not the artifact, and it is reported as unavailable. It is never a pass, and never a failing test. |
The last row is the design decision the whole gate rests on: an axis that could not run has verified nothing. "I could not check this" does not ship.
//In the corpus
The gate scores itself against a corpus of deliberately broken programs, each carrying the verdict it must receive, written down in advance: broken_compile in C++, Go and Java, broken_syntax in Python and JavaScript, broken_types in TypeScript, and test_fails cases in every supported language. The tooled workbench recorded 41/41, 0 gaps, 0 model calls, exit 0 on 2026-08-15; the Python lane re-run on this machine: 12/12, 0 model calls.
A gate nobody tests is just another confident opinion. The corpus is that test — and it runs free, in CI, every push.
//What this axis is
Execution proves the code ran. Coverage proves the tests reached it. The failure mode it exists for: a test that imports the solution, asserts nothing that matters, and exits green. The artifact "passed" — and was never exercised. A name and a docstring with a test file that never calls them is the same shape the system was built to find.
//How it does it
A real line-execution trace, captured inside the sandboxed run with the standard library's sys.settrace — no extra dependency, nothing to install. The harness itself is generated by Olympus's own module, never by the model, so the measurement is something Olympus controls from the first line. The lines of the solution that actually executed during the run are counted against the solution's total statements; below the floor — 50% of the body — the axis reports stub_suspected, which lands as fail:low_coverage.
The axis deliberately errs toward not failing good code: a small test may legitimately leave lines unexercised. The floor exists to catch a verification script that only touches one trivial path — not to demand every branch be hit.
//The details that keep it honest
- Exact basename, not
endswith."test_solution.py".endswith("solution.py")isTrue— and when the test file's own lines counted as solution coverage, the entire check became inert and reported 100% for functions never called. A real bug, found and fixed, because the instrument runs inside the same suite it watches. - The assertions actually decide. The harness executes the test module and then explicitly calls every top-level
test_*function, so pytest-style assertions really run. Before that, a test file full of deliberately false assertions passed — the assertions never executed, so nothing could fail. - No test block means it says so. If the Coder produced no distinct test, the run is a smoke check of the solution alone and the axis reports "stub axis not checked" — recorded, never silently passed.
//Anti-tamper by construction
The coverage report carries a per-run nonce — secrets.token_hex(8), generated inside the runner, never present in anything a model sees. The runner prints its own report last and the parser takes the last match, so a forged coverage line printed by the solution loses on ordering before the nonce is even consulted.
The corpus proves it end to end: coverage_marker_spoof is correct code whose test passes and prints a forged copy of Olympus's own coverage line claiming 100% — the verdict still comes from the real measurement.
The honest limit is on record too: a corpus case cannot isolate the nonce itself. A positive control that deliberately broke the nonce and the last-match rule left the verdict unmoved — because Python coverage comes from an in-process tracer, not from anything stdout can feed. The parser's defences are tested where they can actually be reached, with mutation control. The instrument states its own limits instead of claiming more.
//What this axis is
The failure mode: a function with a great name, a confident docstring, and nothing under them — pass, a bare literal return, a passthrough, a not-implemented throw. Execution passes, because it runs. Coverage passes, because the test calls it. And it does nothing. This axis looks at what the code is, not at what it did.
//How it does it
Parsed, never grepped. The solution's actual syntax tree is read: Python through the standard ast module, and C, C++, C#, Go, Java, JavaScript, TypeScript and Rust through tree-sitter grammars. A text search can be fooled by a comment or a confident name; a parse tree cannot — it sees the body, whatever the name claims.
//Two questions
- Is the body hollow? Empty body, bare literal return, passthrough, not-implemented throw →
fail:stub. The corpus names each shape:stub_pass_body,stub_empty_return,stub_passthrough,stub_returns_argument,stub_empty_array,stub_not_implemented,stub_todo. - Does it define what the plan named? Requirements come from the plan and goal — the backtick-quoted
name(contract — and are checked against the parsed definitions. The plan sayssort_desc, the solution declaresarrange→fail:missing_symbol. A confident name that does not deliver the contract is caught as what it is.
//Diagnosis beats classification
Verdicts are ordered most-specific-first. A file that does not parse is fail:broken, even though it also failed to run, because that is the more useful thing to tell a caller — and a corpus case asserting broken must not be satisfied by a generic execution failure.
And when the structural axis has already identified why a build could not succeed, its diagnosis outranks the build failure. The C++ missing-symbol case compiles fine; the linker fails on the absent sort_desc. "You did not define what was asked for" is the finding a repair can act on — "the build broke" is not.
//The plan is graded too
The requirements come from the plan — so a plan that names nothing would pass this axis unconditionally: fail-open. The gate refuses: a plan with no substance sets plan_usable=False and the axis reports it could not run, rather than certify on nothing. Substance is not word count — 1. Implement `add(a, b)`. is three words and a checkable contract; "please just do the thing now" is six words and nothing.
//What this axis is
Three axes can all be green while the work is a lie. A model asked to make a test pass does exactly what a tired engineer does under deadline: hardcodes the expected values, swallows the exception that was failing, special-cases the inputs it was shown. Execution says the test passed — because it did. Coverage says the lines ran — because they did. Structural says the body is not a stub — because it is not: a lookup table is code. Discipline is the third signal: it checks how the code got its answer.
//How it does it — the generated code
Tree-sitter parsed, multi-signal, calibrated to not fail good code. Two detector shapes:
- Rule 8 — a failure caught and discarded.
except: pass, acatch (...) {}that does nothing, a handler that catches every exception without re-raising. The fastest way to make a failing assertion stop failing is to catch the exception it raises; the handler leaves the test green and the defect intact. - Rule 6 — the hardcoded answer. A body that maps specific inputs to specific outputs and computes nothing. The canonical cheat: asked for
is_primeand shown tests for 2, 3 and 4, a model writes three equality checks returning three literals. Every other axis is satisfied — and it cannot answer 97.
Every detector requires multiple corroborating signals before it reports. One literal comparison is a guard clause — if n < 2: return False in a real primality test — and is never flagged. A try/except that logs and re-raises is diligence, not concealment. The corpus proves both directions per language: the cheating artifacts fail, the honest ones pass.
//Why it had to exist before the repair loop
A repair loop applies retry pressure toward one objective: make the test pass. If the gate only checks that tests pass, that pressure selects for cheating — the cheapest way to satisfy a failing assertion is to encode its expected value. Building the loop first would have built a machine that teaches the model to cheat. This axis is the counterweight, which is why it shipped before the loop: Phase 2.0, with the repair loop 2.1.
//Olympus's own code is under the same rules
The same discipline applies to Olympus's build, mechanically, on every build: rule 2, no ghost code, is the wiring census failing on anything built and unreached; rule 3, no blind rebuild, is the gate corpus verdicts refusing to move; rule 4, no skipped option, is every shortcut marker having to be registered in the debt register with the correct form written beside it; rule 6, no constant folding, is an AST check refusing literals in measurement returns; rules 7 and 8, no easy code, is every TEMPORARY marker having to name its closer. Registering a shortcut does not forgive it — it makes it countable, greppable and impossible to hide, and a stale entry, debt already paid, fails the check too.
Rules 1 and 5 — no vibe coding, no guessing — cannot be checked by a program.. The checker says plainly what it cannot do, because a rule enforced by good intentions is a rule that gets broken under time pressure.
Current state: rules clean, 3 declared shortcuts — and the build fails on an undeclared one.
//In the corpus
The corpus carries both directions per language: cheats_hardcoded_answers and cheats_swallowed_failure must fail, while honest_is_prime and honest_specific_catch must pass. An axis that flags honest code is as broken as one that misses a cheat — the corpus is what keeps it honest.
The Pantheon: no single god holds it all.
Each component is named for a god because each one guards a domain no single agent should hold alone: routing, work, models, sandboxing, policy, secrets, memory, and the war room.
Council: the router
Takes a goal and routes it to the guild that must do the work.
The full story →Guilds: the workers
Autonomous workers: programming, artwork, agoge, literacy, cartographer.
The full story →Oracle: the model picker
Picks the model per job across three tiers: local, cheap, frontier.
The full story →Hades & Tartarus: the sandbox
Runs generated code in a Docker sandbox, so "let's just run it and see" is safe.
The full story →Fates: the boundary
Enforce policy on anything that reaches outside the process.
The full story →Aegis: the secrets
Holds API keys encrypted with AES-256-GCM, the key sealed to your Windows account by DPAPI.
The full story →Vault: the memory
Encrypted memory with semantic and exact-token recall, defended against poison.
The full story →HELM: the war room
The war room: a local web UI to watch it work and approve what needs approving.
The full story →Council: the router
Council takes a goal and routes it to the guild that must do the work. It is the assembly that decides which domain the job belongs to, and it keeps the ledger so the owner never has to re-explain.
Guilds: the workers
Each guild is an autonomous loop with its own domain: programming, artwork, agoge, literacy, cartographer. The pipelines are the owner's own, written in his definitions and settled against relitigation.
| Pipeline | Order (the owner's own) |
|---|---|
| Programming | Rex → Thomas → Ada → Quinn → Marcus → Sentinel → correction loop → Clara |
| Artwork* | Ember → Blake → Lex → Nova → Sage |
The pipeline runs end to end and the guild edits a picture rather than only making one. A request in plain English decides what to do — repaint a region, widen the subject, extend the frame — and records why. Where you drag a box, the verb decides which way it is read: keep the face protects it, make the nose shorter repaints it.
Two independent checks decide whether the result matches the request. One verifies the promises the system itself made — a held region byte-identical, the canvas unchanged — and caught three claims that were false at the moment they were printed. The other asks a vision model what it sees and compares that against the original words: the model never sees the question, so it cannot pass itself. Measured, 1 of 11 requested things missing from a correct render against 6 of 7 from a wrong one. What cannot be checked is reported UNAVAILABLE, never passed.
Not solved: widening an object while keeping a detail carved on it. Holding pixels preserves the viewpoint they were drawn from, so a held detail cannot be re-angled with the object around it; describing it instead frees the angle and loosens everything else. Four geometric approaches were built, measured and deleted. Background replacement needs a silhouette this tree cannot compute.
Two correction cycles, then HALT for a human. HALTED is deliberately not FAILED: "this needs a decision" and "this did not work" are different facts. A parked task is surfaced as its own list, excluded from anything that resumes it unattended, and approving it sends it back through the same gate.
Oracle: the model picker
The Oracle picks the model per job across three tiers: local, cheap, frontier. You decide what is ever allowed to reach a paid API: the choice is recorded, never assumed. The local tier costs nothing per token and works offline.
Hades & Tartarus: the sandbox
Generated code runs in a Docker sandbox, so "let's just run it and see" is safe to do. Hades is the quarantine for untrusted code: a guard on the quarantine, not a policy for the house. The machine's verdict on a piece of code is measured there, in isolation.
Fates: the boundary
Fates enforce policy on anything that reaches outside the process. The thread is cut before the web is touched: an outbound act must clear a policy gate, not a model's judgement.
Aegis: the secrets
Aegis holds API keys encrypted with AES-256-GCM, the key itself sealed to your Windows account by DPAPI rather than to a passphrase you have to remember. Nothing is written in plaintext on Windows; the UI shows a four-character tail and nothing more. The 2026-08-13 key migration proved the guardrails around it matter: a real encrypted store was one git add -A from being committed, caught and locked down by extension matching.
Vault: the memory
Most agents start every session from nothing. Olympus keeps a real memory: an encrypted vault with both semantic and exact-token recall, so asking about a specific environment variable name works as well as asking about an idea.
- A gatekeeper refuses to ingest what it can't trust, so memory can't be poisoned by whatever a web page happened to say.
- A compression ladder distils old material instead of dropping it.
- A scheduler maintains all of it in the background.
- Clearance is enforced on retrieval, so a caller only ever sees what its role may read.
HELM: the war room
HELM is the local web UI: watch the system work, approve what needs approving, manage keys and models. It also tells the truth about its own process: how many source files changed since it started, and which, so a half-reloaded server can never quietly serve two versions of one codebase.
Eight laws, written after a system failed.
The owner wrote them watching an earlier agent talk its way around every guardrail. They are not suggestions: they are enforced mechanically.
No vibe coding.
No Ghost Code.
No Blind Rebuild.
No skipping an option because it makes the end result easier to finish.
No Guessing: all actions have to be by source-verified facts.
No constant-folding shortcuts.
No Easy Code, no less risky: do it right the first time.
When debugging, no Easy Code: the easy path produces weak, fragile code.
//Where they came from
The original in the owner's archive has eleven rules. Three differences are settled, and closed:
- Rules 1 and 11 (Visual Studio; "when coding, no python") were countermeasures against one failing harness that butchered Python and kept forgetting what tools it had. The condition is gone: Olympus being Python is not a violation.
- Rule 10 (verify issues by going through sites like GitHub) was satisfied by a different route: building a system that can say "I do not know." Every argument that outbound fetch is therefore mandatory is withdrawn.
//How they are enforced
The discipline axis of the gate checks the rules mechanically. Shortcuts aren't forbidden: they have to be registered, with the correct implementation written down beside them. Right now three are declared, openly, and the build fails on an undeclared one.
The rule checker runs with the project's own interpreter and answers for the tree it ran on. An instrument run from somewhere else answers for somewhere else.
//Why eight rules carry the whole design
Each law targets a failure mode the owner watched happen: code written on vibes, structures that were names without bodies, rebuilds done blind, the tempting shortcut that leaves a fragile path behind it, and, above all, guessing where a source-verified fact was available. Rule 5 is the spine: no guessing, all actions have to be by source-verified facts. The gate, the pantheon, the register: everything downstream is that rule built into machinery.
The State: measured, honest, and not production ready.
Saying otherwise would break the first rule the project is built on. What follows is what is actually true as of the last measured run.
The parts the remediation hasn't reached yet are unproven, and a clean-machine install has not been rehearsed end to end. That is the honest shape of it.
Last measured state // 2026-09-14 · run, not quoted
Full suite: 4,870 tests, 4,870 passed, 0 skipped, 0 failures, 0 errors, 16 minutes. The count is read from the run’s structured result file, not from the tail of a console — a redirected transcript once lost its own summary line, one sentence away from a pass nobody could prove. This run proved the point again: its console output ended at 100% with the summary line absent.
Corpus gate 41/41 on supported languages, 0 documented gaps, 0 cases awaiting an adapter, 0 model calls, 104s.
Register: 38 open of 121, no drift — every item with a detector agrees with its recorded state, checked in both directions.
All seven gates clean: rules, unreached-code census, silent-fallback inventory, dependencies, register, instruments, and the seal on the verification surface. 3 shortcuts declared and none hidden.
Eight instruments, every one declared its reach and proved itself at boot. The eighth is new this week: it asks whether a test can actually fail, by putting four defects that really happened back into the code and requiring the specific test guarding each to go red.
Unreached code: 151 of 1007 public symbols (15%), down from 36% — not because code was wired, but because the instrument was wrong. It could not see a new package at all, so everything that package called read as dead. Seventy-six symbols recorded as dead were alive, including the main application’s own.
The 22 skips are gone, and the fix was not a deferral. They were 22 cases of one test, each skipping because no test double implemented the streaming call — which reads like a postponement and was not one. The test was replaced by one that states out loud what it covers. The suite now reports zero skipped; 16 tests are deselected (the set that invokes real compilers) and are not claimed here.
The doctor’s 60 checks ran: 58 ok, 2 warn, 0 FAIL. Not re-run today, and so not claimed as current: the tests that need real compilers, and a clean-machine install end to end.
What the system caught // 25 entries · newest first · 2026-09-14
Each entry is a defect this project's own machinery surfaced, not a bug report from a user. Click one to read what it looked like, what it cost while it sat there, and what closed it. The log grows: an entry is added when something is caught, and an entry that is still open says so rather than waiting to be tidy.
The status page was telling the owner things that had been untrue for weeks2026-09-14 · Seven open items described a system that no longer existed.fixed
The open-items register is this project’s memory of what is left to do, and this page publishes it verbatim. The owner read it and stopped at an entry saying the system could not read a scanned page. It has been reading scanned pages for two weeks — measured against five real pages of his own textbook.
He was right, and so were his next four objections. An audit of every open item against the running system found seven that were describing something that no longer existed:
- One said a third of the memory engines had no caller. None of them. The caller had existed the whole time — the tool that counts callers could not see it.
- One said the instruments do not declare what they cover. They all do, and the gate that enforces it refused a new one twice that same day.
- One said a queue of proposals had nowhere to be answered. Ninety-eight had already been answered in it.
- One said recall never follows a link between memories. There are 2,024 links, and the code writes them at full confidence.
- One said the memory store has no interface. Four of its five requirements were met by the interface he had been using all week.
Three of them the machinery could not have caught, and this is the part worth saying plainly. Items in this register can carry a detector — a check that re-reads the claim and disagrees if it has gone stale. Twenty-nine of the open ones have none. An item without a detector is written once and never contradicted: it can only go stale, silently, while the work moves past it.
So the failure was not in any one entry. It was that a written claim with nothing watching it will outlive the fact it described, and the only thing standing between that and a reader is somebody noticing.
What was fixed: three items closed on runs rather than readings; four corrected with what is true today, clause by clause; one detector that pointed at a file which could never exist, replaced. And several probes during the audit came back as mere keyword matches — a file that happens to mention the right word — which are recorded as unproven rather than reported as findings. Guessing is how the register got into this state.
Two tests that could not fail, and the instrument that now asks2026-09-14 · Green, running, fully covered — and checking their own scaffolding.fixed
Seven tests were written for the console in one day. The owner asked a question none of the project’s seven gates could answer: can these actually fail, or do they pass just because?
Two could not fail. They built the system under test using helper functions that reimplemented the real setup code rather than calling it. So breaking the real thing — putting the wrong page at the front door, restoring a route that should have been removed — failed nothing at all. The tests were exercising their own copy.
Nothing was faked. No mock, no stand-in, no replaced dependency. That is what makes this shape harder to see than the usual one: a mock is at least visible in the test. And one of the helpers carried a comment explaining how carefully it matched the real code — a sentence that exists precisely because somebody noticed the duplication and reassured themselves in prose instead of removing it.
Coverage would have scored them perfect. Every line ran. Coverage reports that a line executed, never that anything would notice if it changed.
None of the existing checks could see it either. One judges the code, one catches errors that get swallowed, one finds code nothing calls, one makes every reporting surface prove it works. A test that checks nothing has a name, runs, and goes green — and is silent to all of them.
So the project grew an eighth instrument. It takes defects that really happened — four of them, each from the preceding fortnight — puts each one back into the code on purpose, and checks that the specific test claiming to guard it goes red. Not that something fails: that the named one does. A suite failing for an unrelated reason is not evidence the guard works.
It also guards its own blind spot. A recreated defect that no longer matches the code it was written against does not fail — it quietly stops applying, and “nothing to report” reads exactly like “nothing wrong.” So at every start it verifies each defect still describes the code it claims to break.
The instrument found three faults in its own construction before it was finished, which is the argument for building it rather than resolving to be careful: the self-check gate twice refused it for being unable to prove itself, the unused-code census correctly flagged its new function, and the script recording that exception asked an empty registry — its own assertion stopped it before an unverified claim was written down.
The scope is deliberately named rather than universal: four defects, four named places. A tool that mutates everything produces a percentage, a percentage acquires a threshold, and a threshold gets lowered until it means nothing.
A route that looked like a duplicate, and the charts that went with it2026-09-14 · Deleted as redundant, verified as safe, and the capability was gone.fixed
The console was being converted from a hand-written HTTP server into routes on the main application. Twenty-one of its routes turned out to exist on both sides, so the duplicates were deleted. One of them was the chat stream.
It was not a duplicate. The surviving version returns a conversational reply and nothing else — no chart, no price, no market series anywhere in that file. The deleted one called the chart path. That is what draws a graph into the transcript when you ask for one.
Nothing said so. The route still existed and still answered, with the smaller version. No test failed, because no test read the console. No error appeared, because the reply still streamed. The only symptom was a chart that stopped being drawn — invisible until somebody asked for one.
It had already been checked. Forty-six of forty-seven routes compared and matching, every internal name resolved, the relevant test suites green. All of that was true, and none of it could see this: the check compared route names and concluded that two things with the same name were the same thing.
The wiring census found it. That instrument does not compare names — it walks outward from the places execution actually starts and reports what nothing can reach. It reported two functions in the chart path as having no caller left, which is what a stranded capability looks like from outside. It had no opinion about the reasoning that deleted them, and so could not share its blind spot.
Making the instrument honest enough to say so took three fixes of its own, each of which had been making its report wrong rather than merely quiet: the new package was missing from the list of production code, so its calls were invisible; adding it without naming its entry points made the report worse, because the walk never started there; and route handlers looked dead by construction, since a web framework calls them and no line in the codebase ever will. Unreached symbols fell from 36% to 15%, and seventy-six symbols the baseline had recorded as dead were recognised as live — including the main application's own.
The fix restored the route and the guard that came with it. The durable part is the detector now attached to the chart path: if it ever loses its caller again, the census says so the same way it did this time.
A windowless sweep put an import where there is no Python package2026-08-27 · The gate fell from 41/41 to 12/12 and reported it as missing infrastructure.fixed
Fourteen hot paths spawned processes with a visible console, so every stop, restart and health check blinked a window on the owner's screen. A sweep replaced each call with a helper that suppresses it.
One of those call sites was not a call site. It was a string — the text of a runner script written into a sandbox and executed inside a container that has no access to this codebase. The sweep rewrote the string too, so every non-Python language died on line two with a missing-module error.
The corpus gate fell from 41 of 41 to 12 of 12, with 29 cases reported as “awaiting an adapter” — which reads as infrastructure that was never built, not as a defect introduced twenty minutes earlier. The message that would have said so was truncated at 120 characters, so five separate probes saw Traceback (most recent call last): and nothing after it.
Caught by the pre-flight before anything was committed. A sweep that rewrites call sites has to stop at code that only looks like one, and a test now parses the generated script and asserts it imports nothing from this repository.
The GPU lease evicted everything and held nothing2026-08-27 · Two renders each measured a free card and both proceeded.fixed
A lease exists so one consumer can hold the graphics card for the length of a job. This one evicted the other consumers on entry and then held nothing at all, so a model could be back on the card seconds later.
Measured twice. First: the lease freed the card, the model runtime reloaded a 4.35 GB embedding model for the system's own background work while an 11.46 GB checkpoint was still loading, and the run refused with “10.57 GB free, 12.00 GB needed” — then blamed something outside the system, when the holder was the system. Second, worse: two renders ran at once, 15,154 MiB of 15,360, thrashing for nine minutes until they were killed. Both had passed the same check, which evicts and measures but took no lock.
The register named two consumers. The machine has three — a second model server holds 13,269 MiB and appeared nowhere in it. All three respect the lease now, and a caller that cannot have the card is told who does rather than failing on a number.
A flag that fails silently, and a fallback waiting for an error that never comes2026-08-27 · Four fixes sat on disk, none running, each reported broken twice.fixed
The war room started from a tool session inherited that session's process group, so it died the moment the tool restarted. The cost was never the process: four fixes made that morning were on disk and none were running, so each was reported still broken and the work was done twice.
Windows offers a flag that asks a child to break away from that group. When the group forbids it, the flag is ignored silently — the spawn succeeds, no error is raised, and the child is still inside. A fallback written to trigger on that error would never fire, which is a safety net on a condition that cannot occur.
So the trigger is the group itself: ask whether it would kill its children, and only then route around it by having the operating system's own service create the process instead. Measured on this machine, the group permits breakaway and nothing reroutes — the check reads whichever group the caller is actually in.
Twenty-two skipped tests, and nobody had asked twenty-two out of what2026-08-22 · Half of a check has never executed once, and the suite reports it as a skip count.open
The owner read a test summary and asked why 22 were skipped. Counting the answer rather than describing it found something.
They are one test. A file exists because five test doubles in a single day turned out to be poorer than the class they stood in for — each producing a type error naming a keyword the real class accepts happily, reported as a fault in a caller that was fine. So it asserts the property for every double at once, in two halves: the ordinary call and the streaming call.
Counted from the run's own structured result file: 22 doubles discovered, 22 of 22 pass the first half, and the streaming half has run zero times. Not one double in the tree implements streaming, so every case skips with an honest per-case message — and the aggregate, that this half covers nothing, is invisible. The same file already guards this exact shape one function above, where a discovery test that silently finds nothing would pass forever. The subset never got the same guard.
It will not be closed by adding streaming to one double. That buys the number and not the property. Either something in the tests genuinely needs a streaming double, or the honest state is that nothing streams through one yet — and the check should say so out loud instead of leaving it to whoever reads a skip count.
The configuration described a machine that had stopped existing2026-08-22 · Four claims in the file that decides what runs, one of them a speedup a probe had already disproved.fixed
config/models.yaml is where a reader goes to learn why a role holds the model it holds. Four of those explanations were false at once. One promised “~2x speedup via speculative decoding” through a parameter a runtime probe had refuted three weeks earlier — on a model that was no longer even installed. Three more still called qwen3.8:27b the coder, which stopped being true when the coder moved to the MoE two days before, and two of those three were arguments resting on the dead premise, not labels that could be swapped.
The code was never wrong. The parameter is in no request, the working setting is named and validated, and the allowed values are checked against the runtime's own help output. Only the prose was wrong — and every symbol, path and config-key check in this project is blind to comment text by construction, so nothing could have caught it. It depended on somebody reading a thousand lines and noticing.
Fixing four claims and sweeping the file found five more of the same shape. The close is not the edit; it is the rule that now runs in the suite: a role's justification may not name a model nothing points at unless the line beside it says so, no comment may claim a role holds a model it does not, and the speedup claim cannot come back. Each rule has a test proving it fires on the exact sentence that was actually there, because a rule nobody has watched fail is a rule nobody knows works.
The measurement was ranked on the wrong clock2026-08-21 · A 4.2× range in memory read as a flat line, and a flat line is a finding.fixed
Olympus measures its own expert placement so the owner can pick a point on the curve rather than accept a default. The sweep reported 40.3 down to 40.0 tokens a second across 4.2× the video memory — a curve with no shape, which reads as “this dial does nothing” and would have retired a real capability.
The dial worked. The stopwatch was wrong. Wall-clock seconds include HTTP, JSON, and the prompt processing of a conversation that grows every turn — and prompt processing barely moves with placement, so it entered every arm of the experiment as a large, near-equal term and flattened everything. Timed on the server's own reported figures, the same sweep reads 25.4 up to 45.1.
What makes this worth an entry is that a flat curve has two causes with the identical signature: the placement being silently discarded, or the timing including the caller. Guessing between them would have been a coin flip. The distinguishing experiment is to change only the clock and see whether a spread appears. A curve with any row not timed by the server is now refused rather than ranked, because a number that cannot be trusted should not be presented as a ranking.
The record said a setting was in use, and nothing had ever checked2026-08-21 · The recommendation and the reality were printed as the same fact.fixed
The placement record is written beside the measurements so the owner can read what each option costs and buys. It marked the recommended row as the one in use. Those are two different claims, and only the first had been computed. If the configuration had ever pointed somewhere else, the document explaining the machine would have quietly described a machine that was not running.
It now reads the configured placement out of the configuration file and reports what is actually in force. Found by reading the generated document rather than the code that generates it — the same way the timestamp defect beside it was found, where re-saving a choice was re-dating the measurement.
A diagnostic that can kill the process it is diagnosing2026-08-20 · Code whose only job is a better error message can take the whole system down.open
When a configuration write fails because something else holds the file, Olympus tries to name the holder. On Windows that enumeration can raise a native access violation, which is not a Python exception and cannot be caught: it ends the interpreter. Observed once, when the full test suite exited -1073741819 with the faulting thread inside the enumeration call.
The severity is not the failed run. It is that a routine written purely to improve an error message can take down whatever is running, the war room included. try/except is not a fix and must not be mistaken for one — an access violation never reaches Python. The honest options are to run the enumeration in a child process and lose only the child, to replace it with a call that does not fault, or to drop the detail and accept a less specific message. Recorded as open with no detector, because an intermittent native fault that simply does not happen would let any detector report success forever.
The instrument that answers “is it wired?” could be fooled by code that never runs2026-08-18 · It closed two tracked items by itself, twice, by two different mechanisms.open
The wiring census is how this project answers the single question it cares most about: is this built, or is it actually reachable? It marks a symbol as reached if a call to it exists anywhere in production code — without asking whether the caller can itself ever run. So a whole subsystem that nothing constructs launders every symbol it touches into the “reached” set.
It did exactly that. A listener class nothing constructs called a channel method, and the next baseline regeneration silently moved four symbols out of the unreached map and closed two open register items that nobody had touched. It was the second false closure of the same pair, by a different mechanism than the first. The drift check caught it: the register was right and the instrument was wrong.
Consequence stated rather than glossed: “138 of 608 unreached” is a lower bound, and nobody has measured how many of the other 470 are reached only from code that never runs. It closes when the census reasons transitively from real entry points, proven by planting a symbol called only from an unconstructed class and asserting it still reports unreached.
The ledger was not the record of what the models did2026-08-17 · 556 calls served, 1 recorded, and the name gave no hint of the gap.fixed
A file named for the model calls is read as the record of the model calls. It is the record of what went through one component. Embedding calls post to the runtime directly and write nothing anywhere, so every embedding — on ingest, on recall, on the consolidation ladder — is invisible to the only ledger the system has.
Measured on one day: the runtime served 556 embedding calls; the ledger held one. In a four-hour window it served 72, 134, 206 and 144 while the ledger's newest entry of any kind was twenty hours old. That is why “what is holding nine gigabytes of video memory” was not answerable from anything the system writes down.
Not a cost problem, since these are local and free, and not a quality problem, since the store already records which model answered. It is the shape that matters: a reporting surface that looks like it reports everything. It closes when an embedding appears in the ledger with its latency and its outcome — and when anything reporting from the ledger states its own reach, so a reader can tell “no calls” from “no calls I count”.
The day the guard stopped Olympus from running pip2026-08-17 · The rule held against the people who own the codebase, which is the only test of a rule.fixed
A new dependency gate found a real advisory on its first run: a cryptography library four major versions behind, with a padding-oracle flaw fixed upstream. It was then scheduled to run hourly inside the war room process, so that finding things would stop depending on someone typing a command. Its first cycle in production returned a refusal.
"state": "failed" · "error": "FatesViolation: Direct subprocess execution outside The House of Hades is denied by the Fates."
The gate had been shelling out to pip. Every test passed, because the guard is not armed under the test runner — it is a runtime property, so only the runtime could find this. Adding a fourth door would have taken one line. The three doors that exist are for programs that set a file permission, print a username, or restart Olympus. pip is the program that downloads and executes arbitrary code from the internet.
So the gate stopped needing a subprocess instead: the standard library reads the same installed records, and requirement specifiers are compared in process. It is faster, needs no shell, and works in every process rather than only the ones where the guard is off. Four things happened without anyone remembering to look: the gate found a real advisory, discovered it could not run where it mattered, said so out loud, and the constraint that blocked it made the result better. The full account is on the Fates page.
Ten of fourteen roles pointed at models that were not on the machine2026-08-19 · And the check that would have printed all ten was already written. Nothing ran it.fixed
The owner removed some models and pulled others. Ten of fourteen local roles were left naming models that no longer existed. The war room started clean, the configuration parsed, and every one of those roles would have failed at the moment of use, inside a task, rather than at boot where a failure is cheap.
The part worth reading twice: the doctor script already had the check and it was right. It walks every role and compares it against the runtime's own model list. It would have printed all ten failures the moment it was run. Nothing ran it — not boot, not the suite, not a gate. A correct check nobody runs is indistinguishable from a check that does not exist.
A separate finding came out of the same pass: four entries in the model list answer 403 Forbidden on every call while still being counted as available, so “ten models pulled” included four that cannot serve.
Speculative decoding never once ran2026-08-03 · The whole path existed. The runtime accepted the parameter and threw it away.fixed
Configuration named a draft model, settings read it, the router passed it, the provider attached it to every request. Every call carried it. The runtime accepts unknown top-level keys in a request body and discards them without a word, so the parameter had never done anything.
It was not found by reading code — reading code is what produced the belief. It was found by two runtime probes: sending a draft model that does not exist returned a normal completion with no error, and unloading the real draft model then making a call with it named left the draft model still unloaded afterwards. A real implementation would have rejected the first and loaded the second.
Two false claims followed from it and were corrected with it: a configuration comment promising a speedup that was unmeasured and unachievable through that path, and a setting pinning a model in memory that was never loaded. The capability is now real on the second provider, where the setting is named, validated, and checked against the runtime's own list of what it accepts.
Olympus never said what context window it wanted, and paid for it in memory2026-08-19 · 4,704 MiB of cache nobody asked for, on a 15 GiB card.fixed
Told nothing, the runtime used each model's full trained context window — 262,144 tokens — and sized its key-value cache to match. On a card with 15 GiB total that is 4,704 MiB spent on a window no task uses, which is why the first placement reading came back at 8,437 MiB instead of 3,733 and made the whole dial look less effective than it is.
Found in the same pass: the runtime's own fitting logic was being switched off on every start, because a layer count was always sent whether or not anyone had chosen one. The runtime had been logging this every single time and nobody had read the log. Olympus now asks for no split unless a split was actually decided.
The war room asked the same question 21 times a minute2026-08-18 · Registered under a name that turned out to understate it threefold.fixed
A health probe was reached from a status route the browser polls every four seconds, for as long as a tab is open. Measured from the runtime's own access log: a steady 21 calls a minute, every minute — one every 2.9 seconds — for a list that changes only when the owner pulls a model.
Fixed with a short cache. A failure is cached for the same span, deliberately: caching only successes would leave a dead service probed every four seconds forever, making the unhealthy case the expensive one. The item was registered as “every ten seconds” before the rate was measured, and the name was kept as written, because the register should record what was believed as well as what turned out to be true.
The conversation was being shredded, one page load at a time2026-08-18 · Nothing was broken. Every turn was written correctly, to a new file each time.fixed
The browser minted a fresh conversation identifier when the page parsed, and history is read from a file named after that identifier. So a refresh, a second tab, or a restart filed the next turn in an empty drawer. Measured before the fix: 16 conversation logs, 74 turns, most of them two turns long.
The cause is older and worse than the line of code. A build step had been closed as “skip” with the reasoning that Olympus is a goal-driven workforce and no session concept exists — and the architecture added conversation as a first-class category the next day. The premise was false within 24 hours and the decision was never reopened, so every conversational feature since was built beside the record rather than from it. Nobody ever wrote down what a session is, which is precisely why the browser invented one.
A suite run that lost its own summary line2026-08-22 · One sentence away from reporting a pass that could not be proven.fixed
Two full test-suite runs were launched with their output redirected to a file. Both transcripts end at [100%] with the final count line missing, lost in the redirect buffer. The runs almost certainly passed. That is not the same as knowing they did, and the difference is the whole point of this project.
Counts are now read from a structured result file rather than the tail of a console. A structured artefact cannot lose its last line. The entry stays in the log because the near miss is the lesson: the failure would not have been a wrong number, it would have been a confident number with nothing behind it.
The reviewer crashed on seven of eight languages, and the corpus could not see it2026-08-16 · The 41/41 corpus tests each language directly and never travels the path that was broken.fixed
A single missing line in the gate's own reviewer meant every C++, Java, Go, Rust, C#, JavaScript and TypeScript job died at the moment a verdict was due. Nothing was wrongly certified — the axis reported that it could not check, and unavailable is never a pass — but seven languages had gone unverified.
The 41/41 corpus could not have caught this. It tests each language directly and never travels the path that was broken. It took real work in seven languages to surface it. Fixed with the corpus re-run and every verdict identical, and the verification surface re-approved before the change was accepted.
The gate certified code it never tested2026-08 · A deliberately wrong function went straight through and was certified done.fixed
With no test present, the gate ran the solution file, it imported without crashing, and every remaining check was vacuously satisfied: a deliberately wrong def add(a, b): return a - b was certified done. Closed with a mechanical test that fails if it ever returns that verdict again.
The exam was written by the candidate, on every task, for the life of the feature2026-08 · The role was undefined, the error was swallowed, and the fallback was the model being graded.fixed
A second call is meant to write the test from the plan alone, with the solution withheld, so the gate runs a test the solution's author never saw. That was built, and then it never happened: the test-author role was never defined, the error was swallowed by a bare except, and the fallback was the model that had just written the solution. Every test, on every task, for as long as the role was missing, was a self-graded exam. Nothing said so. The independence is restored and enforced — and the model holding that role is now independent of both the coder and the planner, with the memory cost of that independence stated rather than hidden.
Five subsystems built, correct, tested, and called by nothing2026-08 · Real code with zero reach, passing its own tests the whole time.fixed
Real code with zero reach. Wired, with a census that records every unreached symbol and fails the build the moment a new one appears — the same census that later turned out to over-report, which has its own entry above. The instrument being imperfect does not make the finding wrong; it makes the number a lower bound.
A safety guard whose alarm could never become true2026-08 · The condition was written so it could never fire.fixed
The condition was written so that it could never be satisfied, so the guard could never raise. Corrected, and a mechanical test now proves the alarm is reachable. A guard nobody has watched fire is a guard nobody knows works — the rule that every later checker in this project has been built to.
Every tool has defects like these. This one finds them.
scripts/open_items.json by update_register.py: the page held a hand-copied list that was three days and ten items stale, which is the drift the register exists to stop.//The defect register: how this project gets things wrong
The register is a record of what was actually happening, not of what got fixed. Its opening lines are the shape of every entry:
The failure mode is almost never a crash. It is a green tick over something that did not happen.
Most defects produced no error, no warning, and no log line: they returned success, or returned empty, or returned a number nobody measured. That is why they survived weeks of active development by an owner who was watching closely. The register records severity as used here:
| Severity | Meaning |
|---|---|
| CRITICAL | produced a confidently wrong answer, or exposed the machine |
| HIGH | destroyed or hid real data/work, silently |
| MEDIUM | wasted the owner's time, tokens, or quota; misdiagnosed a cause |
| LOW | inaccurate but not load-bearing |
And the classes it is organised into:
- Class A: false passes. It said DONE and nothing had been verified. The class the whole project exists to remove.
- Class B: false failures. The machine's fault, reported as the model's. A missing compiler called broken code; a container memory cap called a compile error.
- Class C: silent nothing. It ran, it reported success, it did nothing. Recall that returned nothing, always; memory permanently invisible while the vault reported healthy.
- Class D: built, correct, tested, and called by nothing. The five subsystems, the safety guard whose condition could never be true, enrichment reachable only through an argument nobody passed.
- Class E: numbers nobody measured. A number printed and never compared: the fabricated coverage denominator, the latency nobody timed.
- Class F: security. The machine exposed, or the road to it left open: seven unauthenticated state-changing routes on the server.
- Class G: prose that outran the code. Not cosmetic: an owner reading the file forms a false belief about what the system does.
- Class H: unbounded growth. State that grows without a cap: append-only logs with no rotation.
//Provenance: what is not protected
Honesty about the state extends to the project's own paperwork. Two facts are recorded as open risks, both the owner's call to fix:
- The working document is not in git. The
.gitignoreexcludesdocs/*/, so the living record lives outside version control, protected only by a backup mirror that has been caught going stale. - No git remote is configured. There is no
origin: every commit lives on one disk.
Neither is hidden. Both are written down, because the system's first law is that an unverified claim is a failure, and these are claims the project has not yet made.
Check the proof: nothing here needs to be believed.
Every number on this site was produced by actually running the code, not by claiming it works. The commands below do the same thing on your machine: run them, watch the same results print. If a check can't run, it says so, and that never counts as a pass.
None of the claims above need to be taken on faith. Every one of these was run to produce the numbers on this page, and every number was re-verified against the live tree on 2026-08-15.
The suite: the full test suite, green.
The gate, scored against broken code: the corpus of deliberately broken programs the verifier is verified against.
The rules, mechanically: the eight laws, checked by script, not by goodwill.
The wiring census: a public symbol built and never called fails the check.
The gate seal: has the verification surface itself moved?
//How a claim becomes a fact
The design rests on one split. "Is the gate correct?" is a property of Olympus's own code: it is tested with hand-written artifacts whose right verdict is known in advance, zero model calls, in CI. "Are the results good?" needs real models, run deliberately, on a fixed goal set, committed so it can be diffed. The two questions are never blurred, and only the first one ever certifies the machinery.
Coverage is anti-tamper: each gate run carries a nonce the model never sees, so the coverage claim cannot be fabricated in the output. And captured runs record the exit code: the only thing that answers pass or fail, never a human-readable summary that can be truncated or mistaken.
//The records rule
Read a record to learn what happened. Verify against the code before acting on it.
The project's own records directory is explicit about its contract: everything in docs/records/ is a frozen snapshot of a moment, accurate when written, never maintained afterwards. A record is evidence of what was believed on a date, never a description of the tree today. This site's numbers are cited as last measured, for exactly that reason: the code is the authority, the records are the history, and any claim that matters gets re-derived from source before it is acted on.
//Before anything lands on the owner's disk
The project's own standard for shipping is not a feeling: it is a checklist, and the state of each item is reported per change:
- python scripts/doctor.py: the environment's own health
- the full test suite
- python -m evals.gate
- python scripts/check_rules.py
- python scripts/gate_seal.py --check, or re-seal with a note saying why
Then, per change: what was executed, and what was not. That last line is why "done" means something here.
Council: the router.
One assembly decides where every goal goes. Council takes a goal, routes it to the guild that must do the work, and keeps the ledger so the owner never has to re-explain.
//What Council is
Council is Olympus's routing layer: one assembly, the Council, decides where every goal goes. It receives the goal text and decides which guild must do the work. It is not a model that makes a suggestion: it is the point of decision, and it records that decision.
The Council decides which guild will take on a new goal.
Before any model is spent, Council consults the Semantic Cache. If the exact goal was solved before, validated, it replays: no model call, no rework, just the same proven path with its proof attached. A cache hit is a validated replay of forward work, never a blind shortcut. The Owner_Agent_Definitions file is explicit: "Never use semantic cache blindly." A goal that failed yesterday is reworked today, not replayed.
//The guilds it can route to
| Guild | Domain | Proven |
|---|---|---|
| Programming | Code that compiles, runs, and passes its own tests | ✓ |
| Artwork | Images and other visual assets | ✓ |
| Agoge | System tasks and mission work | ✓ |
| Literacy | Writing and documentation | ✓ |
| Cartographer | Web research, URL fetching and content | proven live, tests unwritten |
The roster is derived from the Council's implemented guilds at call time, and Council fails loudly if a guild is routable but undescribed: it cannot tell the owner a guild does not exist while that guild sits there ready to run. A first-class goal summary is written after every run, including skipped steps, and a goal can be routed to "no one" with a written reason. The ledgers: the task board tracks each task; goals.parquet tracks each goal; logs/task_board.jsonl holds the task history.
//The flow of a goal
goal → Council → cache check → guild routing → task on board → guild loop → The Gate → DONE or FAILED
A semantic-cache hit replays the whole forward path in one step, and the replay is labelled honestly: "reused a validated cached outcome". A cache miss runs the full path: route, plan, work, self-check, review, gate. The gate's verdict is what closes the loop, and a task that reaches DONE with evidence becomes the next cache hit.
Guilds: the workers.
Each guild is an autonomous loop with its own domain and its own pipeline. The pipelines are the owner's own: written in his definitions, settled against relitigation.
//What a guild is
A guild is an autonomous worker with a single domain. It owns its pipeline, its exit conditions, and its handoff. The guild loop is the workhorse of Olympus: route, plan, work, self-check, review, done or failed, with a step history the war room streams live.
//The guilds, and what is proven
| Guild | Pipeline | Proven |
|---|---|---|
| Programming | Rex → Thomas → Ada → Quinn → Marcus → Sentinel → correction loop → Clara | ✓ proven live |
| Artwork | Ember → Blake → Lex → Nova → Sage | ✓ proven live |
| Agoge | system tasks and mission work | ✓ proven live |
| Literacy | writing and documentation | ✓ proven live |
| Cartographer | web research, URL fetching and content | proven live, tests unwritten |
"Proven" means the pipeline ran a real task to the gate. The cartographer's live success is on record; what is missing is the test that would make that success reproducible, and the register names it: guild-parity, phase 3, with a detector watching the cartographer's module. The open item guildmaster-owns-the-handoff tracks a deeper gap: some guild loops hand off without their guildmaster owning the exit condition, and the fix is to make the guildmaster responsible for the handoff, not a checkpoint inside it.
//The correction loop, and when it stops
Each guild runs its pipeline, then a correction loop: review the verdict, fix what failed, try again. The owner's rule is recorded in the source: after 2 full correction cycles, HALT and wait for human approval. Do NOT start a 3rd cycle without explicit human approval. A bounded correction loop that runs out of cycles stops here rather than guessing.
HALTED is a real state, deliberately not FAILED: "this needs a decision" and "this did not work" are different facts. A parked task is surfaced separately, most urgent first, excluded from every unattended resume path, and approving it sends the work back through the same gate it faced before. There is deliberately no HALTED-to-DONE edge: approving a park never waives the gate.
//The handoff: who owns the exit
A guild's work is not done when the code compiles; it is done when the guildmaster owns the handoff. Each guild exits through its own gate evidence, and the completion gate checks generated code inside the Hades sandbox: missing (a plan-named symbol the solution never defined) and stub (the verification run never actually executed a line of the solution's own logic, checked via a real sys.settrace execution trace), against ground truth.
Oracle: the model picker.
The only gateway to a model in Olympus, and the one component allowed to spend real money. It thinks in tiers, cheapest first, records every call, and hands the true cost to the Task Board.
//What the Oracle is
Give the Oracle a role and messages, and it returns a CallResult. Its own source says what it is for plainly: it is the sole Olympus component allowed to spend real money, it thinks in tiers, cheapest first, and it logs every attempt, including rejected and escalated ones, to logs/oracle_calls.jsonl so tier thresholds can be tuned against real usage instead of guessed.
It does not decide what work to do. Council decides which guild handles a request, and Council consults the Semantic Cache before the Oracle spends anything. The Oracle's job is narrower: pick a model per job and make the call.
//Three tiers, cheapest first
| Tier | Provider | Cost | When it is used |
|---|---|---|---|
Local tier0_local | Ollama on this machine | 0.00 per million tokens | The default for every call |
Cheap tier1_cheap | OpenRouter | 0.20 budget ceiling, a target, not a real price | Escalation when the Local tier fails or is not enough |
Frontier tier2_frontier | OpenRouter | Paid, reserved | Tier 1 self-check failure, or an explicit guild-flagged high-stakes task |
Every call defaults to the Local tier. The call signature is one method with a default: oracle.call(role, messages, tier="tier0_local"). The Cheap tier is declared google/gemma-4-26b-a4b-it:free, which is $0 on OpenRouter today, so a normal escalation does not touch real spend; the Frontier tier declares deepseek/deepseek-v4-pro and carries reserved: true, pinned to two uses: a Tier 1 self-check failure or an explicit guild-flagged high-stakes task.
//Who decides what reaches a paid API
The owner decides. Choosing where a cloud tier sends its work is a per-install choice, not a property of Olympus. Two ways to make the choice: edit the provider and models fields together in config/models.yaml, or use the war room route PUT /api/config/provider, which writes both fields in one atomic edit.
The choice is recorded, not assumed. Every tier declares its own credential; api_key_env is not inherited and is not defaulted. That rule is a scar: the router once read the Cheap tier's key no matter which tier called it, so a Frontier escalation spent the Cheap tier's credential on a paid model while the comments on that tier described it as costing nothing. The router now refuses: a paid provider with no declared key raises rather than falling back to another tier's credential.
On top of the config sit two money guards. The reserve computes every paid call's true worst case before it is placed and refuses a call that could overdraw the balance. The voluntary daily ceiling was built and tested but declined by the owner, who said the system should be handled by responsible adults; the mechanism stays in place, simply unconfigured on purpose, and the decision is recorded in the register so it stops being re-proposed.
//The cost threading: cost is real
Every call returns a CallResult with .text and .cost_usd, threaded through to the Task Board. That threading is recent and deliberate: for a long time record_spend() on the board had zero callers outside tests, so every one of the 278 rows in the task board log carried spend_usd: null while the war room surfaced that field as a live figure. The Oracle knew the cost all along and simply never handed it to anything. The fix rides a per-thread spend sink: a task's real spend reaches the board with one line, board.record_spend(task.id, spend.cost_usd).
//Tier instructions in goals are honored
If the owner writes "Using Tier 2, create X", Olympus actually uses Tier 2. Council parses the goal text for a tier instruction and maps it to the configured tier, rather than silently running the cheapest model. The parse is a single pattern, "tier N" with N in 0 to 2; "Tier 3" is silently ignored rather than routed to a tier that does not exist. An explicit choice by the owner wins; the default is Local.
//The Local tier: free, on this machine
Ollama on this machine at localhost:11434, priced at 0.00 per million tokens, no credential at all. The constraint is stronger than the price: no disk in the inference path. Weights may rest on disk but must never be read from it while generating, so Ollama's memory-mapped mode is forced off. Measured on this box: 7.62 GB resident disk-backed versus 4.71 GB copied into RAM once with the disk idle. The memory budget is measured, not assumed: a 16 GB VRAM laptop card capped near 14 GiB so the desktop keeps rendering, and about 47 GB of honest RAM, which fits a 70-billion-parameter class Q4 model at roughly 43 GB. The split is computed per call from real hardware probes, so the number the cockpit displays is the number Ollama receives.
//Honest state: what is still open
llama-cpp-moe: a LlamaServerProvider with MoE expert placement is not built; the config also claims a speculative-decoding speedup that Ollama silently ignores. 2.8
chat-cannot-see-the-running-system: the conversation has no read path to Olympus's own operational state, and answers anyway; the Oracle once invented a latency explanation when the measured cause was model cold-load, a fact that sat in the call log the whole time. 4 / 6.5
The register, not prose, wins: these are the open items as recorded in the open-items register, drift-checked 2026-08-15. The llama.cpp item has no mechanical detector, because a new provider's absence cannot be detected from the census, which only reports symbols that exist.
Hades & Tartarus: the sandbox.
Generated code runs in a Docker sandbox, so "let's just run it and see" is safe to do. Hades is the quarantine for untrusted code: a guard on the quarantine, not a policy for the house.
//What Hades and Tartarus are
The pair has a deliberate division of labour. The House of Hades is the whole sandboxed domain: the module that owns every unverified run. Tartarus is the specific place inside it where anything unverified actually executes. Three kinds of code run there, on equal terms: the Coder's own generated code being tested against the completion gate, an import's code being verified before its manifest is registered, and ingested content that can only be judged by observing what it does when run. The rule is absolute: nothing generated, imported, or ingested runs in the main process while it is still unverified. It runs in Tartarus first.
This is the ONLY sanctioned way generated, imported, or ingested code ever executes , and the subprocess guard refuses any subprocess.Popen or os.system call made outside of it.
//Why generated code runs in a locked room
- Measure the machine's verdict in isolation. A sandboxed run tells you what the code actually did, not what a model hoped it would do.
SandboxUnavailableis its own error type: the caller must be able to tell "your code failed" from "I refused to run your code without real containment". A timeout, a memory breach, and a refusal are three different facts. - Contain buggy or hostile code. The Fates' subprocess guard makes the sandbox the only door: any
subprocess.Popenoros.systemcall outside of it is refused outright. - Throw the container away. Every run gets a fresh scratch directory, deleted in a
finallyblock whether the run succeeded, failed, timed out, or crashed, and the container itself is removed on exit regardless of outcome. Nothing accumulates between runs; nothing survives them.
//What real containment means
Its own network
--network none is a genuine kernel-level denial, not env-var scrubbing. A real socket.create_connection() attempt from inside a sandboxed run returned [Errno 101] Network is unreachable. The code cannot phone home, because there is no network to phone out on.
Its own filesystem
A --read-only root filesystem plus a single bind-mounted scratch directory as the only writable location. A real absolute-path write attempt to /etc/... returned [Errno 30] Read-only file system.
Bounded appetite
Cgroup-enforced --memory and --cpus limits, a --pids-limit against fork bombs, --cap-drop ALL, --security-opt=no-new-privileges, swap capped at memory. The image runs as a non-root tartarus user, and the container inherits no host environment. A breach is SIGKILLed by the cgroup OOM killer and reported as memory_exceeded.
Each claim above was proven live by an adversarial test, not assumed. And since 2026-08-04, HELM's boot path calls ensure_docker(), which starts Docker Desktop detached when it is installed but not launched, so an owner with Docker installed stops silently getting the weaker engine.
//When Docker is not running: refusal, not a silent pass
The non-technical guide is exact: "(When Docker isn't running, it tells you plainly that the walls are weaker and refuses to run anything you haven't explicitly approved.)" run() tries Docker first and falls back automatically only when Docker itself is not reachable, never silently on any other kind of failure. The fallback is an explicit opt-in since 2026-08-02, because a warning is not a gate: SandboxUnavailable raises, naming OLYMPUS_ALLOW_UNSANDBOXED=1 as the way to accept the documented weaker containment. A per-call require_docker=True escalation cannot be overridden even by that install-wide opt-in: some workloads have no safe fallback at all.
//Where the sandbox sits in the path of a task
you → Council.classify() → Task on board → Programming loop → Coder generates → completion gate runs it inside Tartarus → machine verdict → The Gate → validated
The sandbox is where "does this work" gets answered by the machine instead of by hope. run_python_file() is the single entry both the Coder's completion-gate self-check and an import's code verification call, and the result is a verdict with a shape: exit code, timeout, memory breach, or startup failure. When the container never even started, the run is reported as UNAVAILABLE, not as a failure of the code. The same engine does quieter work: the semantic cache revalidates a hit with one sandbox run per recheck, and per-language toolchains run generated code in their own images: the corpus scored 41/41 across eight languages in real containers with zero model calls.
//Honest state: what is still open
remote-runner: macOS and Apple SDKs cannot be containerised, so a real Mac accepting a file set and a command IS the only route to honest Apple verification. A second machine widens the blast radius and deserves the containment thinking Tartarus already got. 6
swift-toolchain: Swift THE LANGUAGE compiles on Linux and there is an official Swift container, so server-side and cross-platform Swift packages fit the existing Tartarus pattern exactly. Apple PLATFORM targets need Xcode and Apple SDKs, which cannot run in a Linux container. 6
image-publish-workflow-unrun: all seven images are built locally and all six language images genuinely work. What is ACTUALLY unrun is the narrower thing the id always said: the GitHub Actions build-verify-publish workflow, blocked on the exhausted GitHub quota for this billing month. debt
docker-cold-start-flake: closed 2026-08-13. A session-scoped fixture warms the engine before anything is timed, and the sandbox's two-clock handling tells container startup apart from code execution. closed
The register, not prose, wins: these are the open items as recorded in the open-items register, drift-checked 2026-08-15.
Fates: the boundary.
The guardrail layer between Olympus and everything outside it. Policy is enforced on anything that reaches beyond the process, and the thread is cut before the web is touched. Inside the machine, the machine decides; outside it, Policy decides.
//What the Fates are
The Fates are Olympus's guardrail layer, and they exist to answer one question: is this allowed, and if something slips past, how do we know and how do we undo it? It is asked before any agent action runs. The layer is two tiers, deliberately split. A hardcoded floor lives in source code, not in config: the deny-list of tool names a tool registry may never accept, and the interpreter-level subprocess guard. A configurable Policy lives in fates/policy.yaml: what requires approval, what is whitelisted, content-safety toggles, the Hades-enforcement toggle. The floor is a Python literal on purpose, "so a config edit cannot widen it": changing it means changing and re-reviewing source code, not editing a file.
The Fates are Olympus's guardrail layer. They exist to answer one question before any agent action runs: is this allowed, and if something slips past, how do we know and how do we undo it?
Policy is not something a guild, an agent, or an import can change. Every change goes through a separate CLI the owner runs directly with an exact typed confirmation on a real terminal; it is never imported by agent code. Every init, enable, disable, promote and resign is permanently appended to logs/fates_audit.jsonl and re-signed into policy.hash. At startup the integrity check runs before the guard installs: a mismatch means "the policy file changed outside the human-only channel", and Olympus refuses to start rather than run under quietly-altered rules.
//Why a policy gate beats a model's judgement
The approval keys in Policy were not always real. An audit found them orphaned — their only reader was the tool that edits the policy, and nothing obeyed it. Olympus built the gate, scoped deliberately small: "a permission layer that permits everything is not a permission layer."
The default approver is deny-all, so an unattended Olympus can never self-approve: that is the single most important property here.
The ApprovalGate classifies every action three ways: ALLOWED, NEEDS_APPROVAL, or DENIED, and refuses to let a gated one proceed unless a human approver says yes. DENIED never comes from the Policy file at all: it comes from the hardcoded floor in source, so "no amount of whitelisting makes bash an approved action." Precedence is stated, not incidental: a name in both lists stays gated.
The reason the floor is deterministic rather than a soft signal is itself measured, recorded in the open-items register on 2026-08-14: "an LLM's severity is NOT DETERMINISTIC. Identical input, different level between runs." Severity from a model is a soft signal, and that is exactly why the boundary is not a model's judgement: the guard does not ask "how likely?", it asks "who approved this?", and a policy gate has no run-to-run jitter.
//How the thread gets cut
The cut is made at the interpreter level, not by convention. install_subprocess_guard() patches subprocess.Popen.__init__, os.system, and the whole os.exec* / os.spawn* / fork family inside the Python process, installed by both entry points before anything else runs. Outside a Hades sandbox context, any of those calls raises FatesViolation with the instruction: "Route through hades.sandbox.run() instead."
Three deliberately tiny doors exist, and none of them is a blanket bypass. The owner's interactive auth login (claude auth login), HELM relaunching itself on the owner's Restart click, and the key-hardening ACL call (icacls plus whoami) each get their own context, and entering the context is not sufficient: the executable must also be on a hardcoded allow-list.
//The day the guard stopped Olympus from running pip
On 2026-08-17 Olympus grew a dependency gate: what is installed, whether every package's requirements are satisfiable, and how far the network-facing packages sit behind their published releases. It found a real advisory on its first run — cryptography four major versions behind, with a Bleichenbacher oracle in PKCS#7 decryption fixed upstream in 50.0.0. Nothing in Olympus called the affected API, and it was upgraded anyway: a pin on the library holding the owner's keys is a record of what was tested, not a reason to sit behind a published advisory.
The gate was then scheduled to run on its own, hourly, inside the war room process, so that finding things would stop depending on someone typing a command. Its first cycle in production returned this:
"state": "failed" · "error": "FatesViolation: Direct subprocess execution outside The House of Hades is denied by the Fates (Architecture.md §11). Route through hades.sandbox.run() instead."
The gate had been shelling out to pip list and pip check. Every test passed, because the guard is not armed under pytest — it is a runtime property, so only the runtime could find this. The first time the code ran inside Olympus, Olympus stopped it. The rule held against the people who own the codebase, which is the only test of a rule that means anything.
It was also seen, which is a separate property and not a lucky one. The scheduled check was built so a failure inside it becomes a reported state rather than a dead background thread, and the report carries the sentence "this says NOTHING about whether the dependencies are healthy" — because a broken instrument that reads as a clean result is worse than no instrument. Nobody was watching that hour. The war room said so anyway.
Adding a fourth door would have taken one line. The three that exist are for programs that set a file permission, print a username, or restart Olympus.
pipis the program that downloads and executes arbitrary code from the internet.
Widening a guard whose entire purpose is keeping arbitrary execution out, in order to admit the package installer, is not a compromise; it is a deletion of the rule with extra steps. A guard with a convenient exception acquires another one. The value of the boundary is that it costs something.
So the gate stopped needing a subprocess instead. importlib.metadata is standard library and reads the same installed records pip reads; requirement specifiers are now compared in process and nothing is launched. This is the same move the hardware probe made when the guard denied nvidia-smi and it read NVML in-process instead — the second time the boundary has produced a better implementation rather than an exemption.
- Proof it agrees. Package list against
pip list --format=freezeon a 319-package tree: identical. No name differences, no version differences. - Proof it can fail. Two matching clean results prove nothing, so the checker is driven against a deliberately broken tree and must report both failure shapes
pip checkreports. - Proof it stays fixed. A test makes any
Popencall at all fail, so the gate cannot quietly reacquire a subprocess.
The replacement is faster, needs no shell, and works in every process rather than only the ones where the guard is off. Four things happened without anyone remembering to look: the gate found a real CVE, discovered it could not run where it mattered, said so out loud, and the constraint that blocked it made the result better.
//The machine decides inside, Policy decides outside
The boundary is drawn at the process edge. Anything not listed in Policy defaults to unattended: local test and build commands a guild already owns, and anything inside the install root. Anything that reaches outside clears Policy, and each shape is named:
- Starting a process. Subprocess execution outside Hades is refused outright by the guard.
- Reaching the web. Outbound HTTP is not approval-gated. The owner decided on 2026-08-02: "to go to the internet, should not need to be asked if I tell it to." It is shape-restricted instead: non-http(s) schemes, credentials embedded in the URL, and any host resolving to a loopback, private, link-local or multicast address are refused, so a goal string cannot steer the machine at 127.0.0.1:8770, the LAN, or cloud instance metadata.
- Writing to disk. A write outside the install root is refused by the write guard: it resolves the target, collapsing
..traversal and following symlinks, so neither a dotted path nor a planted symlink can smuggle a write out. Enforced directly by path containment, not routed through an approval prompt.
//Where the Fates sit in the path of a task
goal → _enforce_fates_policy() → install_subprocess_guard() → Council → guild loop → Hades sandbox → fates.gate.assert_write_allowed() → The Gate
The Fates are first in the process and last over the deliverable. Startup verifies Policy integrity and installs the guard before anything else runs. Every generated program then executes inside the Hades sandbox, which is the one place the guard lets a process start. And when the guild's work comes out, the Fates have the final word again: delivery hands each artifact key to the same assert_write_allowed(), scoped to the task's own authorised folder, and a PathEscape is deliberately not caught: "it is a FatesViolation and must surface as one." Even hardware probes live under the rule: nvidia-smi is a subprocess, so the guard denies it, and the hardware probe reads NVML in-process instead.
//Honest state: built, tested, and still open
The Fates are real code, not a design. The rules layer carries nine dedicated tests, the guard is installed on every real run, the gate enforces the approval keys, and the integrity check is wired at startup. The register still records what it owes, verbatim, with detectors:
approvals-routing-unwired: The channel attach path and the approval broker are built, tested and fail-closed, with no caller. A gated effect is therefore DENIED rather than queued for the owner, and the guild loop has no 'this is a judgement call' outcome. 4 / 6.5
perception-facade-unreached: the documented front door has NO production caller. fetch_page, look_at_screen, describe_image and act_on_desktop on the perception facade each appear exactly once in real code: their own def line, and SharedServices.perception, the property that hands the facade out, is never read either. 6
One more layer is honest-only-on-paper, and is recorded as such. The OS-level ACL layer (Windows icacls) is written and checked against real documentation but has never been executed on a real Windows machine, and real separation needs a restricted account distinct from the owner's, which does not exist yet. Until then the in-process guard is the layer actually enforcing.
The register, not prose, wins: these are the open items as recorded in the open-items register, drift-checked 2026-08-15.
Aegis: the secrets.
The one vault in the house that exists for exactly one job: holding the provider API keys that pay for the paid tiers. Every key is encrypted with AES-256-GCM before it touches disk, and the only thing any UI ever shows of a key is its last four characters.
//What Aegis is
In the source, Aegis is named after its guard: Cerberus, the hound at The Gates of Hades. The encrypted store lives in The House of Hades at hades/secrets.aegis, deliberately not in Tartarus, because a secret store is the opposite of untrusted code.
Before this module existed, the only place a real key could live was .env: plaintext on disk, protected by nothing but .gitignore. The module's own docstring is blunt about that trade: it holds against an accidental commit and is useless against everything else. Aegis replaces "plaintext file the owner hand-edits" with "encrypted file the owner unlocks", and keeps the .env path for the CI, test, and headless runs that depend on it.
The store is the one thing in this install that must never escape, and Cerberus is the hound at The Gates of Hades.
The scare is not hypothetical. The one real leak Olympus ever had was not a git vector at all: it was a full source snapshot that carried an environment file with a live API key inside it, deleted 2026-08-02 and ignored ever since. A leaked key is live money and live access; a leaked encrypted store is an offline attack on the key itself. That is why the store's ignore rules are matched by extension rather than by name.
//How the encryption works
Aegis is one versioned JSON envelope holding the whole secrets map as a single AES-256-GCM blob: one nonce, one tag. A fresh nonce is drawn on every write, and the file is swapped in atomically, so a crash mid-write can never leave a store that will not open:
- The cipher.
CIPHER_NAME = "AES-256-GCM"with a random 32-byte key generated once at initialization and a fresh 12-byte nonce per write. One blob, so there is no per-entry ciphertext an attacker could swap. - The key's protection. The AES key is itself encrypted with Windows DPAPI (
CryptProtectData) and stored in the envelope, tied to the owner's Windows user account and, via fixed entropy, to this machine. Copying the store file elsewhere defeats it. - No passphrase (v2, 2026-08-08). The owner never types one, never forgets one, never loses access to their own keys because of one. The confirmation gate is a single [Unlock] click per session; the secret is the Windows login the owner already authenticated with.
- The v3 binding (2026-08-13). Version, key_protection and protected_key are now bound as additional authenticated data: editing the envelope metadata, notably downgrading key_protection from dpapi-entropy to plaintext, is an InvalidTag instead of a silent success. A v2 store still opens and upgrades itself on the first write.
| Envelope field | What it holds | Authenticated |
|---|---|---|
| version | format version; writes 3, reads 2 and 3 | yes, v3 AAD |
| key_protection | dpapi-entropy on Windows, plaintext elsewhere | yes, v3 AAD |
| protected_key | the AES key, DPAPI-wrapped | yes, v3 AAD |
| nonce / ciphertext | the encrypted secrets map | yes, GCM tag |
| cipher | AES-256-GCM, fixed | fixed |
| created_at / updated_at | timestamps | outside the tag |
The refusal logic is deliberate: a build that cannot open its own store locks the owner out of his own account, which the source calls "a far worse outcome than the tampering AAD defends against". The read set is {2, 3}, not a free-for-all; a future version is still refused.
//The UI shows four characters and nothing more
HELM's preferences pane is the face of the store. The only listing method returns name, updated_at, and a preview of the last four characters, empty for any value shorter than eight: enough to tell "the key I just rotated to" from "the old one", and useless to anyone reading over a shoulder.
The rule is structural, not remembered: no route ever returns a secret value, not to localhost, not once, not for debugging. Every route is token-gated at registration, not per route, and unlocking requires a JSON body of {"confirm": true} to force a CORS preflight, so a cross-site form cannot trigger it. A locked store is a normal, expected state: every restart produces one, which is why "locked" is a 409 and not a 500. Locking zeroes the AES key in memory in place.
//Decisions the guardrails had to earn
The 2026-08-13 key-migration near-miss
The v2 to v3 AAD migration wrote a real DPAPI-protected store named hades/_migration_probe.aegis.bak. The ignore rules of the day were anchored on the name secrets.aegis; they caught the named store and missed the probe. A live encrypted store sat untracked and fully committable from 2026-08-13 until 2026-08-15, one git add -A from being committed.
The extension-matching lock
The fix was to match by extension, not by name: hades/*.aegis and hades/*.aegis.*. It was the third time a name or suffix anchored rule had missed a real file: "anything that is an aegis store is an aegis store regardless of what the tool that wrote it decided to call itself."
One stray delete, 2026-08-02
A store holding the owner's freshly entered OpenRouter key was destroyed by a Remove-Item secrets.aegis cleanup, and there was nothing to restore from. Every write now copies the store aside to a .bak snapshot after a successful swap, never before: "an encrypted store with no backup is one stray delete away from gone."
//Where Aegis sits
you → HELM preferences pane → encrypted store → the model tiers → provider
Keys are keyed by environment-variable name, OPENROUTER_API_KEY, not "openrouter". Resolution order: first the Aegis store, if it exists and is unlocked; then os.environ, which is where .env lands; then MissingSecretError, naming both routes and saying so explicitly if a store exists but is locked. The store is an enhancement, not a prerequisite.
The honest boundary, from the module's own docstring: Aegis does not protect against a process running as the owner while Olympus is running, nor against the owner's own process calling CryptUnprotectData on the stored blob. And encryption carries an audit cost: on 2026-08-08 the store held http://locahost:11434, a typo that passed the exact-match check, won over the literal default, and broke every Tier 0 call with getaddrinfo failed, invisible to grep because the value lives encrypted.
//Honest state: what is still open
portable-secret-store, phase 6. open "Aegis protects its AES key with Windows DPAPI, and says plainly what happens elsewhere: on non-Windows the key falls back to PLAINTEXT." Closes when the key is protected by a real platform facility on each supported OS, macOS Keychain, a Linux keyring or an explicit passphrase, and Aegis reports which protection is in force. No detector exists, so the state is a human claim, reported as unverified.
aegis-aad, phase 4. closed 2026-08-13 The filed premise, per-entry ciphertexts, was wrong: the whole secrets map is one AESGCM blob, so there was no entry to swap. The real gap was the unauthenticated envelope metadata. Fixed as v3 with a migration, proven against a copy of the real store first: v2 opened, the three real secret names came back, a write upgraded it to v3, a reopen matched, and a downgraded key_protection was refused. Eight permanent tests cover it.
The register, not prose, wins: these are the open items as recorded in the open-items register, drift-checked 2026-08-15.
Vault: the memory.
Most agents start every session from nothing. Olympus keeps a real memory: an encrypted vault with both semantic and exact-token recall, so asking about a specific environment variable name works as well as asking about an idea.
//What the Vault is
Exact-token recall means asking for the literal name of an environment variable finds the row that spells it out. Semantic recall means asking about an idea finds the rows that mean it, even when the question shares no word with the stored text. Underneath, the Vault is one SQLite database, memory/vault.db: items with a full-text mirror for keyword search and embeddings for similarity search, plus the relationships between items and the notifications they raise. Around it sit the layers that give the store meaning: episodic memory is the plain, append-only log every task attempt writes to; the substrate stacks the Semantic Cache, skill and failure stores on top of that log; Athena is the librarian standing in front of the store; and Mnemosyne is the context provider that decides what actually reaches an Oracle prompt.
//The four mechanisms
Gatekeeper
Refuses to ingest what it cannot trust, so the memory cannot be poisoned by whatever a web page happened to say. Validates the source, classifies sensitivity, rate-limits intake, chunks by type, then routes.
Compression Ladder
Distils old material instead of dropping it. Compresses clusters into summaries, forgets what is safe to retire, and distils lessons, with an honest "skipped" reason when it cannot.
Scheduler
Maintains all of it in the background. Horae runs the ladder behind the scenes, and refuses to run unattended compression unless the write path reports that it is gated.
Clearance
Enforced on retrieval, so a caller only ever sees what its role may read. Unknown roles get nothing: the default is deny, and a corrupt label is treated as most-restrictive.
//The Gatekeeper
That refusal is not one check, it is a pipeline. The validator checks the source against allow and deny lists and validates documents before they are admitted. The classifier reads the content, flags secrets by pattern, and stamps a sensitivity tier onto the item: PUBLIC, CONFIDENTIAL or SECRET. Katharsis scans for contamination and normalizes what it admits. The rate limiter throttles intake with a sliding-window log, and the chunker splits large content by type. Only then does the router send the item to the right store, and the write path every engine uses is ingest_through_gate.
Why the gate is non-negotiable: the finding that started the hardening pass. Lethe, the maintenance engine, wrote its summaries with a bare ingest call, so Katharsis, the secret classifier and the rate limiter were all bypassed on the one path that stores model output. A summarizer that echoed an API key would have written it to disk in the clear, readable by any role. Now those writes route through the gate, and a refusal is not an error: it means the cluster was not stored, and the originals stay readable.
//The Compression Ladder
The ladder "distils old material instead of dropping it." Three engines run it: the SemanticCompressor folds a cluster of items into one summary, AutoForget retires what is safe to retire, and the Distiller turns what worked into distilled lessons. It does not fake success: when it cannot do a pass, it reports a skipped reason, no_summarizer, no_real_embeddings or below_min_entries.
The owner's mandate for the Vault is four words long: "bypass nothing, compress correctly, organize correctly, and filter correctly." Compress correctly is gated on reversibility and readability: every level-two summary must render with its ancestors and mark itself expandable, because a one-way ladder is a shredder with better manners. And summaries are born with a sensitivity label: a summary inherits the highest tier among its sources, never below it, so compression cannot launder a SECRET into a PUBLIC read.
//The Scheduler
Horae, the maintenance worker, runs compression and distillation in the background so the Vault does not grow without bound. Before Horae will schedule unattended compression, it asks the maintenance layer a real question: does your write path report that it is gated? The old guard grepped the source for the word "writer", which already existed in comments before any gated writer existed, so that guard could never fire. The new guard is a property of the object: if a future edit stopped routing writes through the gate, the property would become false and the scheduler would stop. A maintenance worker that cannot prove its writes are gated is not allowed to run unattended.
//Clearance
Clearance is enforced on retrieval: "a caller only ever sees what its role may read." The tiers are fixed. Viewer reads up to PUBLIC, user reads and writes up to CONFIDENTIAL, admin reads, writes and deletes up to SECRET. An unknown role has no clearance at all, and a label the system cannot parse is coerced to the most-restrictive tier. The gate's contract test is parameterized over every read entry point: a viewer never receives a row above PUBLIC, an unknown role receives zero rows, a corrupt tier label is treated as most-restrictive.
The order matters more than it looks. Mnemosyne runs clearance first and budgeting second. Candidates come exclusively through the clearance-filtered search, so an item the caller's role may not read is removed before anything else: it can never be selected, truncated into, or leaked into the digest that reaches the Oracle. Only the cleared hits are packed into the token budget, highest relevance first, with a default of 8% of the context window.
//Why the memory is encrypted
The Vault lives under the same discipline as Aegis because it is the same kind of thing: durable, long-lived, and read back forever. A secret that leaks into memory is worse than a secret at rest, because memory is what every future session consults first. The owner's intake contract is explicit: anything brought into the perpetual mind vault was meant to be stripped so it could cause no harm if read. Encryption is the last line of that stripping: even if content slips past every classifier, the file on disk is not plaintext anyone can read.
//Where it fits
intake → Gatekeeper → VaultCore (Alexandria) → secure_search (clearance) → Mnemosyne (budget) → Oracle prompt, with Horae maintaining the ladder in the background
The Vault is not one thing, it is the stack beneath everything: episodic log, semantic cache, skill library, failure library, in one database kept apart by content type. The Council's Semantic Cache replays validated work instead of spending a token.
//Honest state, what is still open
vault-performance-unmeasured: "Vault read/search performance has never been measured at scale. 107 items is not a size that tells you anything." Closes when a measurement exists at a realistic corpus size. 4
vault-threat-scoring-absent: threat scoring of API keys, passwords and private keys is verified absent across every production package. Closes when every item entering the vault carries a risk score and a test proves a planted secret is caught. memory
vault-organisation-is-mechanical-only: below guild, organisation is similarity only. Closes when a fact can be filed to a location a human chose and recall can be scoped to it. memory
vault-plan-verifier-is-ghost: the plan claims a verifier asserts the delivered state, and that file does not exist. The nine gate tests are real and do pass; what is missing is the summarising verifier the document claims exists. memory
event-extractor-port: "No temporal layer exists at all, so 'what did I do last Tuesday' is unanswerable." Closes when a temporal query returns events ordered in time. memory
The register, not prose, wins: these are the open items as recorded in the open-items register, drift-checked 2026-08-15.
HELM: the war room.
The local web war room on the machine: watch Olympus work, approve what needs approving, manage keys and models, and see the truth about its own process. It watches the system from the one place the system actually lives.
//What HELM is
HELM is the local FastAPI war room behind the pantheon's web UI, and it runs on the owner's own machine at :8770, serving one self-contained page. The developer guide is explicit about what it is for: "It is meant to stay up." It autostarts at logon via the "Olympus HELM" scheduled task, and a killed pytest orphan once took HELM down with it, which is why the guide says never to force-kill python processes by PID while doing verifying or shutdown work.
This is not a dashboard; it's a place you COMMAND the workforce from. The centerpiece is a chat console: you type a goal, it POSTs to /api/goal, then an EventSource on /api/goal/{id}/stream shows the guild loop turn LIVE and renders the result." · helm/static/index.html
The page is deliberately self-contained: no CDN, no build step, no external fonts or scripts, so it loads with the FastAPI server and nothing else, and works fully offline. A Tauri desktop wrapper can point at this same server later without changing the API; for now the owner decided, 2026-07-30, "browser for now, app when finalized".
//What the war room shows
The center of it is the live task stream. The board's state machine is PLANNING → WORKING → SELF_CHECK → REVIEW → DONE, or FAILED, with a step history; a semantic-cache hit replays the whole forward path in one go, labelled honestly: "reused a validated cached outcome".
| Panel | What it shows |
|---|---|
| Live task stream | The guild loop turn, live: routing → planning → working → self_check → review → done/failed, from /api/goal/{id}/stream |
| State snapshot | Catalog + cost + jobs + memory, polled from /api/state |
| Model cockpit | The editable model, role and tier catalog; live model discovery per provider; deprecations for roles whose model left its provider |
| Secrets | Aegis state and display rows, never a secret value: a four-character tail and nothing more |
| Control panel | Subsystem liveness, the startup script's captured stderr, restart and shutdown, and the source-change truth |
//Keys, models, and the machine
Two kinds of management live here, both owner-only. Secrets go through the Aegis store, and the API contract says the response is "NEVER a secret value". Models go through the editable catalog: PUT /api/config/models sets one role's model, comment-preserving, and /api/catalog/available discovers what each provider can actually serve right now, so a deprecation is visible as itself and not as a silent failure.
The auth model is the machine reasoning about trust. Read-only routes need no auth beyond localhost, because Olympus is single-owner, but "localhost is enough" reasons about network reachability and says nothing about the owner's own browser, "which will happily POST to 127.0.0.1 on behalf of any page they visit". So every state-changing route needs the owner token from logs/.helm_token and a JSON body, which forces a CORS preflight a cross-site form cannot satisfy. The process controls are the ones that needed it most: restart and shutdown call real injected operations, not inline os._exit, because a test that reached them must not kill the test runner.
//Approvals and the Gate: parked work
Approvals plug straight into the existing Gate. ApprovalBroker.approve satisfies the Gate's approver signature exactly, so it drops into EffectGate(approver=...) with no change to the gate, to any call site, or to any existing test. The one invariant is not negotiable: every failure mode resolves to DENY. Nobody listening, the wait times out, the broker shuts down, an unknown id, the broker itself raising, a decision arriving late: each one is a refusal, because an approval path that fails open turns every transport bug into silent authorisation to act on the world.
What triggers it is a parked task. The board has a state for waiting on a person: HALTED, "not done, not failed, and the distinction is the point". Folding it into FAILED would have been cheaper and would have been a lie. The owner's rule it exists for is recorded in the source: "After 2 full correction cycles, HALT and wait for human approval. Do NOT start a 3rd cycle without explicit human approval."
queued → planning → working → self_check → review → done/failed · working, self_check or review → halted · halted → working only
And approving sends the work back through the same gate it faced before. There is deliberately no HALTED-to-DONE edge: approving a park means the work resumes and faces the same gate as everything else; it never means the gate is waived. The decision is made by a person, but the judgement is still the Gate's.
//The truth about its own process
On 2026-08-15 the owner attached a video, wrote a description for it, and got back intake: internal error (TypeError: ingest_document() got an unexpected keyword argument 'note'). The code on disk was correct: a fresh process ingested that exact file with that exact description and found it by his own words. What was wrong was the running process, started at 19:50, against an intake module edited at 19:53. HELM imports one intake module inside the route body, fresh at upload time, while the vault importer was loaded at boot: a half-reloaded process, serving two versions of the same codebase to one request.
A running process keeps the modules it already imported and loads new ones fresh, so it can serve two versions of the same codebase to one request. Restart HELM and try again before treating this as a code defect.
The control panel reports source_files_changed_since_start on /api/manage/status, plus the newest changed files, capped and listed by name. It is deliberately not auto-reloading: reload mode "would restart it under him mid-goal", so this module only ever reports.
//Honest state: what is still open
approvals-routing-unwired: "the approval broker [is] built, tested and fail-closed, with no caller", and the loop has no "this is a judgement call" outcome yet; the register's closes_when names exactly the parked-task shape the board now has as HALTED. 4 / 6.5
dispatch-board-port: "Olympus has no equivalent" to the old inter-agent message board; the detector pins a module that does not exist, and the item ends "STAYS OPEN, NOT NEXT." memory
fastapi-on-event-deprecated: "Every HELM start prints two DeprecationWarnings before it says anything useful"; requirements.txt pins fastapi==0.115.6, so it becomes urgent the moment someone unpins it. debt
proactor-connection-reset-noise: "FIX INSTALLED 2026-08-15": a loop exception handler counts ConnectionResetError and reports the first at INFO, exposed as client_disconnects on /api/manage/status. debt
The register, not prose, wins: these are the open items as recorded in the open-items register, drift-checked 2026-08-15.