August 3, 2026 · 8 min read
Designing an agentic OS that fails safe
Most AI agent frameworks are a language model with tools attached. That works until the agent runs unattended, and then the interesting question stops being what it can do and becomes what happens when it is wrong.
AETHERIS is my answer to that question, written as a specification and an architecture before any kernel code. The premise: an agent that acts on a real machine is an operating system problem, not a prompting problem. It needs a process model, a scheduler, a resource manager, a syscall interface, crash recovery, and a protection model. Those six things are what separate an operating layer from a script runner.
The premise decides the shape
AETHERIS runs on one person's machine, under that person's account, doing work that person asked for. There is no tenant boundary and no untrusted operator. The user already has root. Nothing the agent can do is something the user could not do faster by hand.
That premise decides the entire architecture. Innovation is not made safe by restraint. It is made safe by a competent operator who can see what is happening. The system's job is therefore not to decide what the user is allowed to want. It is to make the machine's actions visible, reversible, and bounded by what the user actually said, and then to get out of the way.
A system that second guesses its owner is not safer. It is slower, and slowness is its own failure mode: an operator who has to fight the tool stops reading its output, starts approving blindly, and the whole review apparatus becomes theatre.
Two kinds of guardrail, with opposite defaults
Everything that looks like a restriction in this system belongs to exactly one of two classes. Confusing them is how agent platforms end up simultaneously annoying and unsafe.
Class A is policy, and it is configuration rather than safety. A single policy.yaml expresses what the user wants done without being asked: scopes, auto grants, allowed binaries, budgets. It is the user's dial. Opening it all the way is a supported setting, not a misuse, and the kernel must not override it, warn about it repeatedly, or quietly narrow it. The shipped default is conservative because that is the right starting point for a system whose behaviour the user has not yet observed, not because the wide setting is wrong.
Class B is integrity, and it is not about the user at all. It protects the user's intent from being corrupted by things neither the user nor the agent chose: a web page containing instructions that pretend to come from the user, a model that confidently produces a self contradictory plan, a power cut halfway through a multi step task.
These are not limits on ambition. They are the reason an unattended run is worth leaving alone. Switching off taint tracking does not grant freedom, it grants a random blog comment the ability to speak with the user's authority. Switching off the journal does not make the agent faster, it makes an interrupted task unrecoverable.
The distinction in one line: Class A asks what do you want, and obeys. Class B asks whether this is actually what you wanted, and it is the only reason the answer to the first question can be trusted.
Three processes

A Tauri shell in Rust owns the window and the latency critical native drivers: the accessibility tree, seat arbitration, and screen capture. A Python daemon holds the kernel and is the only process permitted to cause a side effect. Inference always runs as a served endpoint, never in process.
That last one matters more than it looks. A GPU fault takes down its own process. If that process is the kernel, an overnight run dies with it.
Privilege rings and taint

The structural rule is worth stating twice: a task whose context has touched untrusted input cannot hold a privileged capability. It must hand off to a fresh, clean context task.
This is classical information flow control, and it is the only real defence against prompt injection in a system that both reads the web and executes shell commands. Not a filter, and not a classifier trying to spot malicious instructions, but a structural guarantee that text from a web page and the authority to delete files cannot occupy the same task.
The escape hatch is not an approval prompt, it is a fresh context. A tainted task that needs privileged work summarises its conclusion and spawns a child at taint zero. A summary crosses the boundary. Page bytes never do.
One gate, not many

Every side effect passes through a single capability broker. Not most side effects, and not side effects in the risky modules. One chokepoint, because a chokepoint gives you one place to enforce policy, one place to audit, and one place to fix a bug.
The broker evaluates in a fixed order: resolve the capability, apply the default for anything unknown, check the scope against a resolved symlink free path, check the taint ceiling, check the trust floor, check the budget. Path resolution before glob matching is not a detail. Without it a scope of ~/dev/** happily accepts ~/dev/../../Windows/System32.
The model never touches a driver. It emits a data structure, and code decides. That boundary is the difference between a language model with tools and an operating layer.
The journal is the product

Every state transition is written to disk before it is applied. That ordering is the whole design; reverse it and crash recovery becomes guesswork.
One append only structure then serves four consumers: crash recovery replays it, the console streams it as a live execution feed, the code panel reads it to show which task wrote which line, and it is the permanent audit trail. Because it is append only it doubles as the undo index, so reversing a write is a lookup rather than a reconstruction.
Always take the cheapest rung

Four ways to touch the machine, tried in order of cost: native calls at around a millisecond, the Windows accessibility tree at twenty, the browser DevTools protocol at a hundred, and vision in seconds.
Rung two is the one most agent stacks skip, and it is roughly a ten times win over vision for desktop control. A system that screenshots its way through a Settings dialog is doing nothing that an accessibility tree walk could not do faster, more reliably, and without a model call.
This is also where the preference for reversibility lives. Deletes go to the trash, writes snapshot first, and code edits land on a task branch. The worst outcome of a bad unattended run should be a branch to delete, not a mangled repository. The system's instinct on encountering risk is to make it undoable rather than to refuse it.
Four kinds of memory

Working memory is the task context. Episodic memory is the journal. Semantic memory is a local database projected into editable markdown. Procedural memory is a set of written procedures the agent produces when it solves something novel, retrieved and injected the next time a similar task appears.
Every semantic note carries its source and a confidence. Without that, the vault silently launders taint: a claim scraped from a page gets written down, and on retrieval next week it arrives as clean context. Provenance is how taint survives a round trip through storage.
Designing for the failure, not the demo
Unattended autonomy is a reliability problem far more than a capability problem. The interesting question is never whether it can do the task, it is what it does at 3am when step seven of twelve fails.
So the system is built to lose capability without losing correctness. If the vision model is unavailable, tasks that need it suspend and the rest continue. If local inference dies, the router fails over. If the shell crashes, the daemon keeps running headless and the console reattaches to live state. If the daemon crashes, the shell restarts it and the journal replays.
There is exactly one unrecoverable condition, and it is deliberate: if the journal cannot be written, the system stops. If state transitions cannot be made durable then every other guarantee is void, and continuing would produce work nobody can audit or undo. Stopping loudly is the correct behaviour.
Unattended approvals time out to deny, never to approve. A task that exceeds its token or wall clock budget is suspended, not silently continued, because an agent looping on a metered API overnight is an uncapped liability.
The human keeps the seat
The agent shares a physical desktop with its user. If the user touches the mouse or keyboard, any task holding the seat yields within 200 milliseconds. An agent that fights you for the cursor is not usable no matter how capable it is.
Why the brain stays unmodified
The specification explicitly rejects weight modified models with their refusal behaviour stripped out. In an autonomous system, model judgement is a fault tolerance component. A model that pauses on a malformed or self contradictory instruction is the last check between a misunderstood goal and an irreversible action, and that reflex fires far more often on genuine ambiguity than on anything else. It is the cheapest error detector in the stack.
Friction on legitimate administrative work is a prompting and schema problem rather than a weights problem, and it is measured against a benchmark probe rather than assumed away.
Status, honestly
Specification version 1.0 and the architecture are complete. The provider layer and a model conformance harness with thirty probes are built and tested. The kernel is in development.
I am publishing the design before the implementation because the design is the part I want argued with.