{"slug": "overlord-a-trust-kernel-for-ai-agents", "title": "Overlord – a trust kernel for AI agents", "summary": "A new open-source tool called Overlord provides a trust kernel for AI agents, giving them transactional isolation, provenance, and arbitration for untrusted execution in dependency-free Python on Linux kernel primitives. Overlord installs via `sudo bash packaging/install.sh` to `/usr/local/lib/overlord/` with a compiled ELF launcher at `/usr/local/bin/overlord`, and runs on Ubuntu 24.04+ with AppArmor, WSL2, or Docker Desktop using the fuse backend. The tool is model-agnostic and not an AI itself, offering scoped grants for paths, budget, network egress, and time window, plus a built-in jailed agent and a chat workspace where nothing on disk changes until the user presses Commit.", "body_md": "An agent hypervisor — the trust kernel for delegated computing.\n\nHand a program — or an AI agent — a fully writable copy of a directory. Let it\nrun. Then review every change it made as a hashed, attributed manifest and\n**commit or roll back**, all or nothing. Transactional isolation, provenance,\nand arbitration for untrusted execution — in dependency-free Python, on the\nkernel's own primitives. It ships with a built-in agent (`overlord agent`) that\nruns a model with its hands jailed, so you can watch the whole loop happen\ninside the transaction and sign off on the diff. For people who just want to\nuse it, `overlord ui` opens a chat workspace where you talk to that agent and\npress Commit when you like what it did — nothing on disk changes until you do.\n\nComputing is transitioning to a new operator: machine agents. The OS has no native concept of a machine actor. Every agent today runs with its principal's full authority on infrastructure that cannot distinguish the principal's intent from the agent's behavior. Every harness vendor duct-tapes around this independently and badly.\n\nOVERLORD is the missing layer between the agent harness and the operating system. Not an AI. Model-agnostic. A boring, load-bearing primitive — the SQLite pattern, not the Windows pattern.\n\n1. **Capability, not identity** — an agent receives a scoped grant (paths, budget,\nnetwork egress, time window), not a user account. Commander's intent expressed\nas kernel-enforced constraints.\n2. **Provenance** — every mutation traceable to actor, instruction, and the reasoning\nartifact that caused it. A flight recorder for machine action.\n3. **Reversibility** — agent action is transactional: snapshot, execute, inspect,\ncommit or roll back. The keystone. Delegation is blocked on \"what if it breaks\nsomething\"; this removes the question.\n4. **Arbitration** — when N agents contend for a resource, authority is scheduled\nthe way CPU is scheduled.\n\n```\nsudo bash packaging/install.sh\noverlord doctor\n```\n\nInstalls the engine to `/usr/local/lib/overlord/`, a compiled ELF launcher to\n`/usr/local/bin/overlord` (the AppArmor attachment point), the AppArmor profile\nthat enables the kernel backend on Ubuntu 24.04+, and the runtime deps\n(`fuse-overlayfs`, `strace`).\n\nThe engine is Linux kernel machinery — overlayfs, user namespaces,\n`mount(2)` — so OVERLORD does not run natively on Windows or macOS, and a\nport would be a different product. It runs in the Linux those systems\nship or host:\n\n- **WSL2** (Windows):`wsl --install -d Ubuntu` , then the install above\ninside Ubuntu (the AppArmor step skips itself) and`overlord ui` ; WSL2\nforwards localhost, so a Windows browser opens`http://127.0.0.1:7777` .\nKeep the project folder in the Linux filesystem (`~/projects/…` , seen\nfrom Windows as`\\\\wsl$\\Ubuntu\\home\\…` ), not under`/mnt/c/` , where\nthe overlay is slow and`doctor` may fall back to the fuse backend.\n- **Docker Desktop** (Windows, macOS): the`Dockerfile` builds an image on\nthe fuse backend; its header has the run line (`--device /dev/fuse --cap-add SYS_ADMIN` , a data volume, accounts and a certificate first).\n\n`overlord doctor` is the ground truth on any machine: it names the\nbackend it found and what each grant will mean there.\n\n```\noverlord run -t /srv/app -- some-agent --do-things   # transactional execution\noverlord run --jail --net none --timeout 300 -t /srv/app -- <cmd>   # scoped grants\noverlord run --manifest cap.json -t /srv/app -- <cmd>               # grants from file\noverlord run --trace -t /srv/app -- <cmd>            # + syscall flight recorder (strace)\noverlord run --trace ebpf -t /srv/app -- <cmd>       # kernel-side recorder (root-only)\noverlord run --merge-base -t /srv/app -- <cmd>       # keep base copy for commit --merge\noverlord shell -t /srv/app                           # interactive transactional shell\n\noverlord agent -t /srv/app \"add a Makefile with a test target\"   # jailed by default\noverlord agent --net none -t /srv/app \"<task>\"       # ...and offline too\noverlord agent --net proxy --net-allow github.com --net-allow \"*.pypi.org\" -t /srv/app \"<task>\"  # recorded, allowlisted egress\noverlord agent --no-jail -t /srv/app \"<task>\"        # opt out: tools reach the real fs\noverlord agent --provider openai-compatible --base-url http://127.0.0.1:11434/v1 \\\n               --model llama3 -t /srv/app \"<task>\"   # a local model, same jail\noverlord agent --effort xhigh --max-tokens 64000 -t /srv/app \"<task>\"   # generation knobs\noverlord models --provider anthropic                 # what the endpoint serves right now\noverlord mcp add github --command npx --arg -y --arg @modelcontextprotocol/server-github \\\n                 --env GITHUB_TOKEN=…                # register an MCP connector (stdio)\noverlord mcp add docs --url https://host/mcp --header 'Authorization: Bearer …'   # (http)\noverlord agent --connector github -t /srv/app \"<task>\"   # grant it; actions ask you first\noverlord agent --audit -t /srv/app [\"focus\"]         # containment audit of its own jail\noverlord memory show -t /srv/app                     # what the agent is told before message one\noverlord memory user --add \"Prefers pytest.\"         # a note about you, across every folder\noverlord memory accept <session> --all               # keep the notes an agent proposed\noverlord users add alice --role admin                # accounts: the UI now asks who you are\noverlord tls selfsign --host overlord.lan            # a certificate for --bind\noverlord sso set --issuer https://login.example.com --client-id … --client-secret-stdin \\\n    --domain example.com --admin alice@example.com   # OpenID Connect; accounts provisioned on sign-in\noverlord ui --bind 0.0.0.0 --tls-cert ~/.overlord/tls/cert.pem --tls-key ~/.overlord/tls/key.pem\noverlord skills add skills/python-testing            # packaged know-how, loaded when it fits\noverlord skills new release -t /srv/app              # a project skill: part of the tree, reviewed like code\noverlord webhooks add team https://hooks.slack.com/… # tell the channel when work waits for a person\noverlord secrets set-command 'vault kv get -field=value secret/overlord/{name}'   # then secret://NAME anywhere\noverlord export <session> -o review.ovl              # one signed file: record, retained versions, pending changes\noverlord import review.ovl -t /srv/app               # replay its changes here as a new pending session\noverlord cost                                        # what the models spent, by model / account / day\noverlord cost budget --day-usd 20 --month-usd 500    # lines no conversation crosses\noverlord audit verify                                # walk the signed chain\noverlord audit checkpoint pin.json                   # witness the head off-box\noverlord audit verify --pin pin.json                 # prove the live log still carries it\noverlord audit witness https://witness.example/log --auto   # send a signed head off-box on every act\noverlord audit verify --witness                      # check the live log against the witnessed head\noverlord audit verify                                # the hash chain of every consequential act\noverlord gc --dry-run                                # what retention would prune\n\noverlord sessions                # pending/committed history with command provenance\noverlord diff <session>          # added / modified / deleted / replaced-dir\noverlord log <session>           # per-change sha256 before -> after, syscall count\noverlord savepoints <session>    # the layer stack: one savepoint per command that wrote\noverlord rewind <session> --to 3 # discard everything above savepoint @3\noverlord resume <session> --note \"...\"   # agent carries on from there, reading the note\noverlord fork <session> --at 3   # a second continuation of the same moment, as a new session\noverlord compare <sess-a> <sess-b>   # where two continuations diverge, per path\noverlord review <session> --provider openai   # a second model countersigns the diff\noverlord commit <session>        # verify no external drift, replay onto real tree\noverlord commit --countersigned <sess>       # ...only with a fresh approval on record\noverlord commit --drop tool:shell <sess>     # replay all but the shell tool's layers\noverlord commit --only turn:2-4 <sess>       # replay only what turns 2–4 did\noverlord commit --merge <sess>   # three-way merge non-overlapping drift (needs --merge-base)\noverlord commit --force <sess>   # commit despite drift (explicit override)\noverlord rollback <session>      # discard — target byte-identical\noverlord blame <path>            # which session, turn, tool call, instruction put each line here\noverlord doctor                  # backend / dependency diagnostics\n```\n\nThe wrapped command sees a fully writable tree and exits believing everything\nhappened. Nothing touches the real tree until `commit`. Commit re-verifies the\nsnapshot fingerprints (size + mtime_ns of every file) and **refuses to clobber\nexternal changes** made while the session was pending. If the session was run\nwith `--merge-base`, `commit --merge` three-way merges non-overlapping drift\n(git merge-file against the kept base) and still refuses overlapping edits.\n\nThe model thinks on your machine; only its hands are jailed. Every way to\nreach one is in `providers.py`, stdlib only, behind one contract, and the\nworkspace, the CLI, the daemon and the SDK all share it:\n\n| provider | reaches | auth | \n|---|---|---|\n| `anthropic` | Claude, native Messages API: streaming, adaptive thinking, `effort` , refusal handling, opt-in server-side refusal fallbacks | `ANTHROPIC_API_KEY` or the key store | \n| `openai` | OpenAI Responses API ( `/v1/responses` ): streaming,`reasoning.effort` , function tools with reasoning, encrypted reasoning carried across tool turns, nothing stored server-side;`--api chat` for the old shape | `OPENAI_API_KEY` | \n| `azure` | Azure OpenAI deployments ( `--base-url https://<resource>.openai.azure.com` , deployment as the model,`--azure-api-version` ) | `AZURE_OPENAI_API_KEY` | \n| `openai-compatible` | anything speaking the Chat Completions shape behind a base URL: Ollama, vLLM, LiteLLM, Groq, Together, your gateway; `--api responses` once it grows the new shape | optional | \n| `gemini` | Google Gemini REST: streaming, function calling | `GEMINI_API_KEY` | \n\nEvery provider takes a **base URL** and **extra headers**, which is how a\nproxy or an enterprise gateway sits in front of it. `overlord models` lists\nwhat an endpoint serves right now, and the workspace's Settings shows the same\nlist. Keys live in `~/.overlord/keys.json` (mode 600); environment variables\nwin over the store.\n\n**Generation knobs** — `--max-tokens`, `--temperature`, `--top-p`, `--stop`,\n`--effort low|medium|high|xhigh|max`, `--thinking summarized|off`,\n`--api responses|chat`, `--system` (appended instructions), `--no-stream`,\n`--no-fallbacks` — are\nmodel-aware: nothing is sent unless you set it. That matters because the\ncurrent Claude family rejects `temperature` and `top_p` outright and takes\nits depth from `effort`; blank means the model's own default. Replies stream\nas they are generated (the CLI prints them live; the workspace renders them\ninto the bubble), tool inputs that stream in are parsed strictly and handed\nback as an error rather than run when malformed, and a reply cut off at\n`max_tokens` or declined by a safety classifier never executes its tool\ncalls. The knobs and the endpoint a conversation ran with are recorded on\nthe session, so `resume` uses the same model the same way.\n\n`mcp.py` is a Model Context Protocol client, stdlib only: stdio servers\n(a command) and streamable-HTTP servers (a URL), configured in\n`~/.overlord/mcp.json`. Their tools are offered to the model next to the\nbuilt-ins, namespaced `mcp__<server>__<tool>`, and a call is routed back to\nthe server that owns it.\n\nConnectors are different from everything else here, and the design says so:\n\n- **They run on the host, outside the jail and outside the transaction.** A\nconnector that sends an email has sent it; Discard cannot unsend it. So a\nsession must be*granted* each connector by name (`--connector` , or the\ncheckboxes on a new conversation), policy can list the connectors a\nbrokered session may have (`\"connectors\": [\"github\"]` or`\"*\"` ), and every\ncall is written to the transcript with the server that served it and shown\nto the countersigning reviewer as an**external action** .\n- **Actions ask first.** A tool declares itself read-only through MCP's`readOnlyHint` ; anything else goes through the approval gate. In`ask` mode\n(the default) the run pauses: the CLI prompts on the terminal, the\nworkspace shows an approval card with the exact input, a brokered run with\nno one to ask is denied.`auto` allows,`readonly` refuses every action.\nEach decision is recorded in the transcript.\n- **The call is bound into the record, like a commit.** Because it cannot be\nrolled back, each connector call is written to the keyed audit chain, not\nonly the transcript:`connector.call` names it by a fingerprint of the\nserver, tool and arguments;`connector.decision` carries that fingerprint\nand, when a person approved, the account that approved it;`connector.result` records the result's hash and size. So an approval authorizes one exact\ncall — a different call has a different fingerprint — and the whole effect\nis tamper-evident under the audit key.\n- A stdio server gets only the environment you configure for it plus PATH,\nHOME and LANG, never the whole process environment, so one connector's\ntoken is not another's. `overlord mcp test <name>` connects and lists its\ntools with their read-only status.\n\nWhat the agent knows before your first message, and how that is allowed to\nchange (`memory.py`). Three sources, all injected into the system prompt,\nall capped, and the session records what the model was told:\n\n- **Project notes** —`OVERLORD.md` at the root of the working folder:\nconventions, commands, things learned about this codebase. The agent may\nextend it with the`remember` tool,**inside the transaction** : the note is\na file change in the diff with a savepoint and a cause, committed or\ndiscarded with everything else. Memory is a file the person reviews.\n- **Your notes** —`~/.overlord/memory.md` , about you and your preferences,\nacross every folder. The agent cannot write it.`remember` with scope`user` only*proposes* a note; you accept it (a Save button in the\nworkspace,`overlord memory accept` on the CLI) and the acceptance is\nrecorded in that session's transcript.\n- **The journal** — one line per committed agent session in a folder: the\ntask, what the model said it did, which files changed. Derived from the\nengine's own records at commit time, never written by the model, so\n\"recent work in this folder\" is always true. Rolled-back sessions and\nplain command sessions leave no entry.\n\nNothing outside a transaction changes without a human hand, which is the same rule as everywhere else in OVERLORD.\n\n`overlord ui` binds loopback with no accounts: the person at the keyboard\nis the operator — proven by a **launch token**, not by loopback. Red team\nA13: a session granted `net: host` shares the host's loopback, so\n\"reachable on 127.0.0.1\" would include the agent, which could then read\nevery session and commit its own. `overlord ui` prints\n`http://127.0.0.1:7777/?token=…`; the token lives in `~/.overlord/ui.token`\n(mode 600, outside the jail), the page keeps it as a strict cookie, scripts\nsend it as `Authorization: Bearer`, and a request with neither is refused. The first `overlord users add` turns\nsign-in on (`auth.py`), and from then on every request names a principal —\na login cookie (HttpOnly, SameSite=Strict, Secure under TLS) or a bearer\ntoken for scripts (`Authorization: Bearer ovl_…`, hashed at rest,\nrevocable one by one, `overlord users token`). Five wrong passwords in five\nminutes lock that address+name for a minute; passwords are scrypt hashes in\na mode-600 file.\n\nThree roles, checked on every route:\n\n| role | may | \n|---|---|\n| **admin** | everything: accounts, policy, connector config, every record on the machine | \n| **operator** | their own conversations — start, commit, discard, rewind, fork, review — and their own settings, API keys and notes; connectors may be used, not configured | \n| **viewer** | read every record, change nothing (an auditor) | \n\nEach account has its own `~/.overlord/users/<name>/` with its `ui.json`,\n`keys.json` (a key you set is yours; the machine's shared key is the\nfallback an admin can provision) and `memory.md`. A session records its\n`owner`; sessions opened from the CLI have none and are the admin's to see.\n\n**Single sign-on** (`oidc.py`): OpenID Connect, authorization code with\nPKCE, state and nonce. `overlord sso set` names the issuer and client;\naccounts are provisioned on first sign-in when the e-mail domain is\nallowed, with the role from a listed e-mail (`--admin`, `--viewer`), a\ngroups claim (`--role-claim groups --role-map auditors=viewer`) or the\ndefault. SSO accounts have no password and the password form refuses\nthem; local accounts coexist. What the trust rests on, stated plainly:\nthe TLS channel to the provider's token and userinfo endpoints plus the\nstate / nonce / PKCE round-trip — the ID token's claims are checked, its\nsignature is not (no RSA in the standard library), and the same identity\nis confirmed by `userinfo` directly from the provider.\n\nLogins outlive a restart of `overlord ui` (a mode-600 file keyed by the\ncookie's hash, never the cookie), and a per-address rate limit\n(`--rate-limit`, 3000 requests a minute by default) answers 429 to a\nrunaway script.\n\nBeyond loopback the server refuses to start without both accounts and TLS\n(`--bind 0.0.0.0 --tls-cert … --tls-key …`; `overlord tls selfsign` makes a\ncertificate with openssl for a private deployment). A `--host name`\nallowlist backs the Host check, HSTS is sent, and a plain-HTTP probe at the\nTLS port is shrugged off. A workspace that can commit an agent's changes to\na real tree is not something to leave on a LAN behind a Host header.\n\nPackaged know-how the agent pulls in when it fits (`skills.py`). A skill is\na folder with a `SKILL.md` — front matter `name`, `description`, an optional\n`when` — and any supporting files. Two homes, one rule:\n\n- **Project skills** live in`.overlord/skills/<name>/` inside the working\nfolder. They are part of the tree, so part of the transaction: an agent\nmay write or improve one, and that change is a diff a person reviews,\ncommitted or discarded with everything else. The next conversation in\nthat folder is offered it.\n- **Machine skills** live in`~/.overlord/skills/<name>/` , installed by a\nperson (`overlord skills add <path>` , or Settings → Skills), offered to\nevery conversation, capped per folder by the policy rule`\"skills\": [...] | \"*\" | []` .\n\nThe model is told the catalogue — names and descriptions only — and loads a\nskill with the `skill` tool when its description fits the task, so the text\nis not in the prompt until it is needed. A project skill shadows a machine\nskill of the same name entirely. Every load is a transcript event; the\nsession records what it was offered. Two examples ship in `skills/`.\n\nA conversation's message list grows with every turn; without a rule it\ngrows until the model refuses it. The rule: when a call has used three\nquarters of the context window (a generation setting, default 128k\ntokens), the agent writes a **handover note** — the task, decisions, every\nfile touched, what remains, what bit it — the older turns are dropped, and\nthe note plus the last few messages become the conversation. The cut is\nitself a transcript event (`compaction`: the note, what was kept, the\ntokens that triggered it), the note's call is on the ledger, and a resume\nrebuilds exactly the view the model had — nothing the model was told is\nlost from the record, only from its context.\n\n`overlord export <sid>` writes a signed `.ovl` (a tar.gz): the session's\nrecord (meta, transcript, provenance, output), the retained file versions\nits provenance names — so `blame` keeps working where it lands — and, for\na pending session, every changed file's content and the list of deletions\n(`bundle.py`). A manifest hashes every member and is HMAC-signed with the\nmachine's `bundle.key` (`overlord bundle key` to share it).\n\n`overlord import file.ovl` keeps the record on this machine (an altered\nmember or a forged manifest is refused; without the key the import is\nmarked unverified, and `--require-signature` refuses it). `overlord import file.ovl -t <folder>` replays a pending bundle's changes as a **new pending\nsession** on that folder — every write inside the transaction with an\n`import` cause, the transcript carried across with a note of where it came\nfrom — so review, diff, savepoints and commit apply as to any other work.\nNothing reaches the folder until a person commits. Extraction is strict\n(relative names under known prefixes, regular files only); both directions\nare audited; the inspector offers Export.\n\nAnywhere OVERLORD stores a secret — a provider key, a connector's env or\nheaders, the SSO client secret, a webhook's signing secret — the value may\nbe a reference, `secret://NAME`, resolved at the moment of use by a\ncommand you configure (`vault.py`): `overlord secrets set-command 'vault kv get -field=value secret/overlord/{name}'`, or `pass`, or `aws secretsmanager …`. The command runs without a shell, its stdout is the\nsecret, values are cached for a configurable while, and `overlord secrets test NAME` reports a length, never a value. Provider keys also have a\nconvention: with a resolver configured and no key in any file,\n`providers/<provider>` is asked for — so a fresh machine needs no key file\nat all. Whole values only: a reference inside a longer string is left as\nit is.\n\nA gate nobody is told about is a gate that stalls. Webhooks (`notify.py`)\nsubscribe to **audit actions** — the log is already the machine's index of\nconsequential acts — and two acts exist for this: `session.needs_review`,\nwhen an agent finishes with changes waiting for a person, and\n`connector.approval_requested`, when an external action waits at the\napproval gate. `budget.stop`, `session.commit`, `review.verdict` and the\nrest are there to subscribe to. Format `slack` posts `{\"text\": …}` that\nSlack-compatible incoming webhooks render, with a link to the conversation\nwhen a base URL is set; format `json` posts the audit entry with a text\nline and an HMAC signature (`X-Overlord-Signature`) when a secret is set.\nDelivery is off the caller's path — a background queue, three attempts\nwith backoff, a 4xx tried once — and a failing endpoint never fails the\nwork. `overlord webhooks add|list|test|rm|base-url`, or Settings →\nNotifications.\n\nEvery model call returns its token usage; OVERLORD prices it (`cost.py`),\nwrites one ledger line per call (`~/.overlord/ledger.jsonl`: session,\naccount, model, tokens, dollars) and keeps the running total on the\nsession, in its `done` event and in the inspector. Prices are a table in\n`~/.overlord/cost.json` — a few list prices ship as defaults, yours\noverride them (`overlord cost set-price <model> <in> <out>`); an unpriced\nmodel is still counted in tokens.\n\nBudgets are lines, not estimates: `session_tokens`, `session_usd`,\n`day_usd`, `month_usd`, from the global config (` overlord cost budget`),\nthe policy rule for a folder (`\"budget\": {...}`) or the account (` overlord users budget`), the most restrictive of each winning. A conversation is\nchecked **before every call** and stops with reason `budget` at the first\nline it has reached — the work done so far stays in the transaction for\nreview, the stop is in the transcript and on the audit log. Second-model\nreviews are on the ledger too.\n\n**The usage meter** in the workspace rail shows what is left, from two\nhonest sources rather than a guess. The provider states its own rate-limit\nheadroom on every reply — tokens and requests per minute, how many remain\nand when the bucket refills — and OVERLORD keeps the latest reading per\nprovider (`~/.overlord/ratelimit.json`, from the `anthropic-ratelimit-*`\nand `x-ratelimit-*` headers, a 429's `retry-after` too). The meter reads\nthat against the per-minute limit, and your ledger spend against the\n`day_usd` and `month_usd` lines. Set `month_usd` to your provider's\nmonthly spend cap: the API never reports the cap, so that is the one number\nyou supply for the monthly bar to mean \"how much of my plan is left\". The\nbar turns amber under a quarter left and red under a tenth; with no key\nhaving replied yet, the rate rows say so instead of inventing a number.\n\nSessions keep their own records; `audit.py` keeps the machine's:\n`~/.overlord/audit.jsonl`, one line per consequential act — open, reopen,\ncommit, refused commit, rollback, rewind, fork, review verdict, connector\ndecision, memory acceptance, connector or policy or budget change, sign-in\nand failed sign-in, account change, budget stop, gc — from the CLI, the\ndaemon and the web UI alike, since they share the engine. Each line carries\na MAC over the line before it, keyed by `~/.overlord/audit.key` (mode 0600,\nmade on first use). `overlord audit verify` walks the chain and names the\nfirst altered or missing line, `doctor` checks it, the workspace shows it to\nadmins and viewers. The actor is the signed-in account, else the session's\nowner, else the OS user.\n\nThe key is what makes the log an anchor rather than a self-consistent\nstory. An unkeyed hash chain is tamper-evident only to someone who did not\nalso rewrite it — the file's owner can recompute every hash. Keying each\nlink means a forger needs the key too: a rewrite without it is caught, and\nso is a *downgrade* that strips the signatures to fake an unkeyed log.\nVerify says `signed` or `UNSIGNED` so a missing key never passes silently.\n\nTwo honest limits follow. First, a local key defends against anyone who has\nthe log but not the key; it does not, by itself, stop the key's holder.\nSo copy it off-box (`overlord audit key` says where it is) and, for the\nholder-as-attacker case, **witness the head**: `overlord audit checkpoint`\nemits `{seq, hash}`, you store it somewhere the host cannot reach, and\n`overlord audit verify --pin <file>` proves the live log still carries that\nentry — catching a truncation or rewrite at or below it even by someone\nwith the key.\n\nOVERLORD can do the witnessing for you against a remote endpoint the host\ndoes not control. `overlord audit witness <url> --auto` points it at an\nappend-only receiver; from then on it POSTs a **signed** head (the `{seq, hash}` plus a MAC keyed by the audit key) after each consequential act,\nthrottled. `overlord audit verify --witness` fetches the head the witness\nholds and checks the live log still carries it — so a truncation or rewrite\nbelow the witnessed point is caught even when the attacker holds the key,\nbecause the witness keeps the higher sequence they would have to retract.\nThe MAC proves the witnessed head came from this OVERLORD, so a third party\nwho can write to the witness cannot plant a head it would accept. The\nwitness only stores and serves the latest `{seq, hash}`; it never sees the\nlog's contents.\n\n`overlord gc` prunes finished records older than `keep_days` (default 30,\nthe newest `keep_last` committed kept regardless), objects no remaining\nrecord refers to, and locks nobody holds — never a pending session, never\nthe audit log or the ledger. `packaging/overlord-gc.timer` runs it nightly.\n\nTo run it for a team: `packaging/overlord-ui.service` (a system unit that\nserves TLS on a bind address with `--log-json`, one JSON line per request\non stderr), `/healthz` for a load balancer or container runtime (no login,\nnothing an outsider learns), and a `Dockerfile` for the fuse backend\n(`--device /dev/fuse --cap-add SYS_ADMIN`). `overlord doctor` reports\naccounts, TLS material, the audit chain and disk usage next to the\nbackends.\n\nThe system prompt states the agent's situation up front — its model, the\nsandbox it is in (jail or cooperative), its network, any budget line, whom\nit works for, and that every command is recorded and provenance is derived\nby the engine rather than from what it reports. A model that has to guess\nwhat it runs under goes looking; one that is told has nothing to discover,\nand the design loses nothing by saying it, which is the test of whether a\nsafeguard is real. The safeguards are locks, not rules: understanding the\njail does not open it, and the one act that changes the world — commit —\nbelongs to a person. `test/escape_test.py` is an agent that tries anyway.\n\nAn agent that did try, in a real deployment, reported four findings; here is what each one is:\n\n1. *OVERLORD's own source is readable and writable* — only when the working\nfolder is the OVERLORD checkout itself. Its code is public and holds no\nsecret; an edit to it is a diff a person reviews and changes nothing\nrunning. The conditions block now says so, and \"OVERLORD's own files\"\nmeans what it always meant: its**state** — keys, accounts, records,\nthe audit chain — which stays unreachable.\n2. *The real host path is in the mount table* — by design: the lower layer\nis the real tree (a copy would be the wrong trade) mounted at its real\npath so absolute paths in tooling keep working. Host-identifying detail\nis disclosed; it is not a way out.\n3. *All capabilities, no NoNewPrivs, no seccomp* — the finding that earned\na fix (red team A14). Inside a user namespace those capabilities reach\nonly the namespace's own resources, but the surface was far wider than a\nbuild needs: the jail now clears ambient capabilities, drops the\nbounding set, sets NoNewPrivs, installs a seccomp policy (mount,\npivot_root, unshare, setns, bpf, keyctl, module loading, io_uring, …)\nand zeroes every capability set before a command runs.\n4. *Sensitive code in-tree* — the same as 1: the code that handles secrets\nis not the secrets.\n\nA session may consume only what it was granted of the machine (`limits`:\n`memory_mb`, `pids`, `cpu_pct`, `disk_mb`, `fsize_mb`, `nofile`; defaults\n4 GiB, 512, 200 %, 8 GiB, 4 GiB, 4096; `--limit key=N`, 0 = unlimited;\na policy rule's `limits` are ceilings). Enforcement, in layers: rlimits\nin every command on both backends (process count, file size, open files,\nand the data segment when nothing better exists); a cgroup around the\nwhole session where the host allows — v2 through `systemd-run --user --scope`, v2 directly, or v1 — created before the holder is launched so\nevery process is born inside it, released after; and the disk grant\nmeasured per layer, whose crossing ends the session's ability to run\nanything (what was written stays for review; the agent stops with reason\n`limit`, audited). `overlord doctor` says which layer this host provides.\nA fork bomb or a disk fill costs the session, not the machine.\n\nGrants scope what a session may do — commander's intent as enforced constraints.\nSet via flags or a JSON manifest (`--manifest cap.json`, flags override):\n\n```\n{ \"jail\": true, \"net\": \"none\", \"timeout\": 300, \"merge_base\": false }\n```\n\n- **jail** — pivot_root jail: the process sees system dirs (ro by real perms),\na private /tmp and /proc, and the target.`$HOME` ,`/mnt` , and the rest of\nthe filesystem*do not exist* . Kernel backend only.\n- **net: none** — private empty network namespace. No egress, no loopback to\nhost services. Kernel backend only.\n- **timeout** — hard wall-clock limit; the process group is killed (exit 124).\n- **merge_base** — keep a base copy (`cp --reflink=auto` ) enabling`commit --merge` .\n\nArbitration: one executing session per target (flock; `--wait` queues), and a\nnew session is refused while another is pending on the same target (`--stack`\noverrides).\n\nA session's upper layer is a *stack*. Every command that writes seals its\nlayer; the next command starts a new one. Each command runs in its own mount\nnamespace over the current stack (`lowerdir=layer_k:…:layer_0:target`), so a\nsavepoint costs one mount and copies nothing — it is SQL's `SAVEPOINT` on the\nkernel's own overlay primitive. For the agent that means one savepoint per\ntool call, stamped with the call that caused it. Three things fall out:\n\n```\noverlord savepoints <session>          # @0  1 path  turn 2 write_file(src/hello.py)\n                                       # @1  2 paths turn 3 shell(rm scratch.txt; rm old.txt)\n                                       # @2  1 path  turn 5 write_file(lib.py)\noverlord rewind <session> --to 1       # world as it was after turn 3; transcript cut to match\noverlord resume <session> --note \"keep scratch.txt; make a() return 3\"\noverlord commit <session> --drop turn:3          # undo a decision, keep what came after\noverlord blame src/lib.py              # per line: session · turn · tool call · the prompt\n```\n\n- \n**Rewind the agent to a thought.**`rewind` drops the layers above a\nsavepoint and cuts the agent's transcript at the same point (the dropped\ntail is archived, because a rewind is itself an act with provenance).`resume` reopens the session on the surviving stack, rebuilds the model's\nmessage history exactly as it saw it, delivers your note as the next user\nturn, and lets it continue — from a world that matches what it remembers.\n- \n**Commit by cause, not by path.**`commit --only` /`--drop` take selectors\n(`layer:N` ,`layer:A-B` ,`turn:N` ,`tool:NAME` ,`call:ID` ) and replay just\nthose layers in order. Overlayfs copies a whole file up on first write, so\nevery layer's entries are complete and any ascending subset replays to a\nwell-defined tree. Conflict detection covers every path the selected layers\nwould touch on replay — not only the net diff — so a file one layer created\nand a later one deleted still cannot clobber a same-named file that\nappeared outside.\n- \n**Blame to the prompt.** Commit retains the content it replaced and the\ncontent it wrote (content-addressed under`~/.overlord/objects` , capped per\nfile by`OVERLORD_OBJECT_MAX` ).`blame` walks the committed versions of a\nfile and attributes each line to the session, turn, tool call and task that\nfirst produced it —`origin` for lines older than the record,`drift` for\nlines changed outside OVERLORD since the last commit.\n- \n**Fork a moment.**`fork <session> --at N` copies the stack up to a\nsavepoint into a new pending session on the same snapshot, transcript cut\nto match, whiteouts and all. Resume both with different notes and`compare a b` shows, per path, where the two continuations diverge before\neither is committed. Committing one makes the other's snapshot stale, and\nconflict detection says so.\n\nLayers are addressed relative to the session dir (the mount data page is 4 KiB) and capped at 200 per session; past the cap the top layer keeps absorbing writes. On the fuse backend the merged view is remounted between commands; a lingering process that pins it makes the next savepoint coarser rather than failing. Rewind is refused while a command is running.\n\nTwo rules the reviewer's judgment cannot be talked out of. **Fail closed\non a partial view**: when the dossier had to omit or truncate part of the\ndiff, the verdict records it, the reviewer is told it has not seen the\nwhole diff, and an approval on a partial view never countersigns a\ncommit. **Protected paths**: a policy rule's `protect` globs — or, when\nthe working folder is OVERLORD's own source, the harness's modules —\nneed a fresh, complete countersignature to commit and are refused\n`--force` outright. Connector tools that look like a shell (`shell`,\n`exec`, `run_command`, `terminal`, …) are withheld from the agent unless\nthe session holds the `connector_shell` grant, which policy must allow:\na general shell on the host is the one capability the transaction exists\nto contain.\n\n```\noverlord review <session> --provider openai      # the agent ran on anthropic\noverlord commit --countersigned <session>\n```\n\nA pending diff is put in front of a second model that had no part in making\nit. The reviewer never touches the tree: it gets the task, the grant\nenvelope, the agent's tool-call transcript and the full diff, plus one\nread-only tool (`read_file`, served from the session's own flattened view),\nand its only way to act is `approve` or `reject` with a reason. The verdict\nis bound to a fingerprint of exactly what was reviewed — every path's kind\nand before/after hash — so a rewind, a resume or a `--drop` makes it stale.\nA fresh rejection blocks `commit` unless `--force`; `--countersigned` refuses\nwithout a fresh approval; a policy rule `\"require_review\": true` makes the\ndaemon demand it for a target. The reviewer must be a different model from\nthe agent (`--same-model` overrides, and the record says so). The review has\nprovenance of its own: `review.jsonl` is the reviewer's transcript, and every\nverdict ever given stays in the session record.\n\n```\noverlord ui          # http://127.0.0.1:7777 — localhost only\n                     #   /          the workspace (chat)\n                     #   /console   the document of record\n```\n\nThe friendly face: a chat window, like the assistants people already know.\n**One conversation is one transaction.** The first message opens a sandboxed,\noffline copy of a folder and sets the agent to work; each later message\nresumes it on the same copy; the panel on the right shows the running diff.\n**Nothing on disk changes until you press Commit** — so you let it run, read\nwhat it did, and decide. Discard throws the copy away and the folder is\nbyte-identical.\n\n- **Conversations** are listed on the left and selectable; each is a\ntransaction you can come back to, commit, or discard.\n- **Settings** holds the model provider (Anthropic, OpenAI, Azure OpenAI,\nOpenAI-compatible, Gemini), the model picked from the endpoint's live list,\nthe endpoint and extra headers for a gateway, the API key (stored in`~/.overlord/keys.json` , mode 600, never shown again), the generation knobs,\nthe working folder, and the sandbox grants. Each provider keeps its own\nprofile when you switch. On the kernel backend the agent's hands are jailed\nand offline by default; the model still thinks on your machine with network,\nonly its tools are confined.\n- **Countersignature** under Settings names the second model (provider and\nmodel); the inspector's Second-model check uses it, and the server refuses\nthe agent's own model rather than guessing another provider.\n- **Accounts** (when on) appear under Settings: an admin adds people and\nsets roles; everyone can change their own password and mint a script\ntoken. A conversation you may only read shows a read-only composer.\n- **Memory** lives under Settings: your notes (editable), the folder's\nproject notes, and its journal of committed work; a note the agent proposes\nabout you shows in the chat with a Save button.\n- **Connectors** are granted per conversation from the welcome screen and\nconfigured under Settings; when the agent wants to run one that acts on the\nworld, the chat pauses with an Allow / Deny card showing the exact input.\n- **The inspector** is the review moment made friendly: the changed files, the\nsteps that made them, Commit, Discard, and a one-click second-model check\n(the countersignature). Committed sessions link back into the console.\n\nThe model runs in-process, so `overlord ui` is the whole product — no daemon,\nno build step, one file of stdlib Python, loopback only, with the same origin\nguard and nonce CSP as the console.\n\n```\noverlord ui          # then open /console\n```\n\nThe review moment for human eyes, rendered as a document of record rather than a dashboard. The register indexes sessions; the dossier is the instrument a human signs: the grant envelope the session ran under, a manifest of every changed path with its before → after hashes and the tool call that caused it, the savepoint chain (rewind or fork at any row; untick a row and the commit drops it), the countersignature (request one, see whether it still binds), and the disposition — commit or void. Any committed path opens its blame sheet: every line, and the session, turn, tool call and task that put it there. Zero dependencies (stdlib http server), server-rendered first paint, binds 127.0.0.1 only.\n\n`overlord daemon` (or the systemd unit in packaging/) makes OVERLORD a\nresident broker on a 0600 unix socket. Sessions requested through it are\nbound by `~/.overlord/policy.json` — deny-by-default targets, forced jail/net,\ntimeout caps, force-commit gating — and callers can never obtain a looser\nscope than policy grants. The Python SDK (`overlord_client.py`, installed to\n/usr/local/lib/overlord/) embeds this in any harness: `run() -> Session`,\n`session.diff/log/commit/rollback`. See docs/INTEGRATION.md.\n\n`test/redteam.sh` attacks the jail: symlink escape, dotdot traversal,\nsession-record tampering, host sysctl writes, device forgery, host pid\nvisibility, real-fs reads, overlay-internals reach, fd leaks, mount games.\nFinding A3 (session records reachable via the strace bind) was found by this\nsuite and fixed — records are never exposed; strace gets an isolated trace/\nbind only when in use. Every future breach becomes a fix + regression test.\n\nThe agent can run the audit itself: `overlord agent --audit` (or `/audit` in\nthe workspace chat) presets an *authorized containment audit* as the task.\nA well-aligned model rightly declines \"break out of your sandbox\"; the same\nprobes framed as what they are — sanctioned, scoped to a disposable session,\nwith every gap written as a failing red-team check before it is reported —\nare ordinary assigned work, and A11–A15 came out of exactly that. The preset\nneeds the jail (there is nothing to audit without one), flags the session\n`audit: true`, and an optional task narrows the focus. A clean audit is a\nresult too: the model is told to say so rather than invent a finding.\n\nReading an audit: from inside, `overlord doctor` reports the kernel backend\nas blocked, because a jail cannot be nested (no capabilities, no new\nprivileges, seccomp). That is the jail holding, and `doctor` now says so\nfirst; a command can tell where it is by `OVERLORD_JAIL=1` in its\nenvironment, and OVERLORD run inside a jail keeps its state on the jail's\nprivate `/tmp`, never in the project tree. The mount table names the real\nproject path on purpose: the agent is told it works at that path so\nabsolute paths in builds resolve. A finding is a gap between what the\nconditions block claims and what the probe shows, not the claim itself.\n\nNames with the network: a `net=host` jail gets DNS. On WSL2 and\nsystemd-resolved hosts `/etc/resolv.conf` is a symlink out of `/etc`\n(`/mnt/wsl/resolv.conf`, `/run/systemd/resolve/stub-resolv.conf`), trees\nthe jail does not bind, so the link dangled inside and a command had a\nnetwork but no names. The real file is now bound at its real path,\nread-only, only when the network is granted; `net=none` binds nothing.\n\nTwo overlay backends, auto-detected, kernel preferred. `overlord doctor` names\nthe active one — read it before you trust a session. The fuse backend counts\nas available only when a mount can actually happen: the binaries on PATH\n*and* `/dev/fuse` this user can open. A container started without\n`--device /dev/fuse`, or a jail, gets the reason instead of a session that\ndies on its first mount; every test suite says `SKIP` with that reason\nrather than failing when no backend exists.\n\n|  | containment | privileges | \n|---|---|---|\n| **kernel** | full — the overlay is mounted over the target's own path in a private mount namespace, so even absolute-path writes into the target are captured; `--jail` makes the rest of the filesystem cease to exist for the process | unprivileged user namespaces; on Ubuntu 24.04+ the shipped AppArmor profile grants exactly that to the `overlord` binary alone, nothing else weakened | \n| **fuse** | cooperative — the overlay is the working directory, but absolute-path writes elsewhere are not intercepted | none | \n\n**Recorded egress (`--net proxy`).** `--net host` gives the agent the host's\nnetwork and records nothing — the diff shows what it wrote, never what it\nsent. `--net proxy` closes that: the command runs in an EMPTY network\nnamespace with no route out (a direct `connect()` returns `ENETUNREACH`,\nenforced by the kernel), and its only path is an HTTP proxy OVERLORD runs.\nEvery connection is one line on the session's egress log (`egress.jsonl`:\nhost, port, method, allowed, bytes each way), and `--net-allow host` (or\n`*.suffix`, repeatable) turns recording into refusal — a host outside the\nlist gets 403. It needs no `slirp4netns`, `passt` or host root: a tiny front\ninside the namespace hands each client socket to a back in the parent's\nnamespace (which has the real network) over `SCM_RIGHTS`, so the proxy is\nthe only way out by construction, not by cooperation. TLS is not\nintercepted; a CONNECT tunnel records host, port and byte counts, which is\nwhat a tunnel honestly exposes. DNS resolves in the back, so a proxied jail\nneeds no resolver of its own.\n\nThe design choice that matters: **the fuse backend refuses `--jail` and\n`--net` rather than pretending to honor them.** A degraded backend that\nsilently ignored a containment grant would be worse than no grant at all — so\nit errors, and `doctor` tells you which backend is live before you rely on one.\nThe strong guarantee is the kernel backend; the cooperative one is a clearly\nlabelled fallback, never a disguise.\n\n**Threat model, one line.** OVERLORD makes a program's writes to a target tree\ntransactional and reviewable, and — with `--jail --net none` on the kernel\nbackend — confines the process to that tree with no network. It is *not* a\ndefense against kernel exploits or a determined userns escape; it is the\nmissing transaction, provenance, and scope layer between an agent harness and\nthe OS, built on the kernel's own isolation rather than a new one.\n\nEvery session records `provenance.jsonl` — one record per change with sha256\nbefore (lower) and after (upper), the savepoint layer that holds the final\ncontent and the cause stamped on it (`caused_by`: turn, tool, call id,\nsummary), which survives commit and is what `blame` reads. With `--trace`, a\nsyscall-level record (`syscalls.jsonl`: exec, file mutation, connect, per pid,\ntimestamped) is captured via strace. `--trace ebpf` uses the bpftrace recorder\ninstead (installed to /usr/local/lib/overlord/provenance.bt) — lower overhead\nand unfakeable by the traced process, but root-only and with a known\nattach-race at process start.\n\n```\nbash test/all.sh                  # every suite, one line each; logs kept; exit 1 on any failure\nbash test/smoke.sh                # 26 core transactional + replay-safety assertions\nbash test/redteam.sh              # 10 jail escape attempts (kernel backend)\npython3 test/daemon_sdk_test.py   # 17 daemon + SDK + policy + live-session assertions\npython3 test/agent_test.py        # 10 agent loop, tool, provenance, and jail-default assertions\npython3 test/savepoint_test.py    # 10 savepoint / rewind / resume / commit-by-cause / blame assertions\npython3 test/review_fork_test.py  # 6 countersignature / fork / compare / policy assertions\npython3 test/providers_test.py    # 10 provider adapter assertions: wire shapes, streaming, knobs, listing (offline)\npython3 test/mcp_test.py          # 6 connector assertions: stdio + http transports, grants, approval gate, policy, workspace\npython3 test/memory_test.py       # 7 memory assertions: injection, caps, transactional remember, proposals, journal, CLI, workspace\npython3 test/auth_test.py         # 7 auth assertions: open mode, sign-in + lockout, roles, ownership, tokens, TLS + Host allowlist, CLI sessions\npython3 test/cost_test.py         # 6 cost assertions: prices, ledger, policy / global / account budgets, review ledger, CLI, workspace\npython3 test/audit_test.py        # 5 audit assertions: chained acts, tamper detection, actors + healthz, gc, --log-json + doctor\npython3 test/skills_test.py       # 6 skills assertions: catalogue + shadowing, jailed / host loads, authored in-transaction, policy, CLI, workspace\npython3 test/sso_test.py          # 5 SSO assertions against a fake provider: config, PKCE round-trip, role mapping + domains, password refusal, audit\npython3 test/session_test.py      # 5 long-conversation assertions: compaction + record, exact resume replay, the window setting, durable logins, rate limit\npython3 test/webhook_test.py      # 5 webhook assertions against a local receiver: config, needs-review + link + signature, approval gate + budget, retries, API\npython3 test/vault_test.py        # 5 vault assertions with a fake resolver: CLI, provider keys + convention, cache, connectors / SSO / webhooks, audit\npython3 test/bundle_test.py       # 5 bundle assertions: signed export, second-machine import + tamper/forgery refusal, replay + commit, UI, crafted tars\npython3 test/escape_test.py       # 5 escape assertions: the agent is told the truth; env / keys / home / pid 1 / net / writes-out all fail; all recorded; DNS with net=host; A13\npython3 test/netproxy_test.py     # 5 egress-proxy assertions (offline): allowlist, CONNECT tunnel, 403, absolute HTTP, per-connection log\npython3 test/netproxy_live_test.py # 5 net=proxy assertions on the kernel backend: empty netns, proxy-only egress, recorded, allowlist refusal\npython3 test/limits_test.py       # 6 limits + gates assertions: rlimits, cgroup, disk grant + agent stop, policy ceilings, protected paths + truncated review, shell tools\npython3 test/chat_test.py         # 12 workspace assertions: settings, model config, streaming, resume, commit\npython3 test/ui_test.py           # 12 mission-control API + origin-guard + savepoint + blame + review assertions\npython3 test/ui_browser_test.py   # 10 mission-control DOM assertions (needs playwright)\npython3 test/chat_browser_test.py # 8 workspace DOM assertions (needs playwright)\n```\n\n`savepoint_test.py` and `review_fork_test.py` run on whichever backend is\nlive; `OVERLORD_TEST_BACKEND=fuse` forces the cooperative one, so both\nstacking implementations are exercised. `chat_test.py` drives the workspace\nHTTP API with the scripted provider (no network); `chat_browser_test.py`\nloads the chat in Chromium and drives it as a person would.\n\n`ui_test.py` drives the HTTP API; `ui_browser_test.py` loads the page in\nChromium and asserts on the rendered DOM — console errors, the dossier\nswapping on a register click, attribution reaching the manifest, the drift\nrefusal, keyboard navigation, and phone-width layout. The browser suite skips\ncleanly when Playwright or Chromium is absent, so it never blocks a bare\ncheckout; it exists because an API-only UI test let a click-breaking\nReferenceError ship undetected.\n\nCore suite (`smoke.sh`): isolation, diff completeness, provenance hashes,\nbyte-identical rollback, exact-replay commit, commit finality, conflict refusal\n\n- `--force` , create-collision refusal, shell, syscall trace, absolute-path\ncontainment, mode-000 cleanup, timeout, manifest, arbitration, three-way merge,\njail sealing, net:none isolation. Kernel-only tests self-skip where userns\ngrants are absent.\n\n- fine-grained path grants (extra read-only / writable mounts in the jail)\n- token/cost budget grants for LLM-backed agents\n- eBPF recorder hardening (attach-race close, structured output)\n- multi-target sessions; cross-target atomic commit\n- blame across renames\n- countersignature by a human-in-the-loop channel (a signed approval from outside the machine, same fingerprint binding)\n\n- \n2026-09-01 — repo created; thesis.\n- \n2026-09-01 — v0: transactional run/diff/commit/rollback, dual backend, e2e suite.\n- \n2026-09-01 — v0.1: conflict detection, provenance flight recorder (hashes + strace), interactive shell, kernel-backend overmount containment, AppArmor packaging, installer.\n- \n2026-09-01 — v0.2: capability manifests (jail / net / timeout), pivot_root jail, network scoping, arbitration (locks + pending guard), three-way merge on commit, eBPF recorder wired ( `--trace ebpf` , root-only).\n- \n2026-09-01 — v0.3: red team suite (10 attacks; found + fixed A3 session-record exposure), resident daemon with policy brokering (deny-by-default, grant caps, force gating), Python SDK, systemd unit, integration docs.\n- \n2026-09-01 — v0.4: mission control web UI ( `overlord ui` ) — session review,\nper-file diff with hashes, one-click commit/rollback, policy editor;\nzero-dependency, server-rendered, localhost-only.*(v0 → v0.4 landed in one build sprint on 2026-09-01.)*\n- \n2026-09-10 → 09-11 — v0.5, two parts. **The agent** —`overlord agent` runs a\nmodel (Anthropic / OpenAI, stdlib-only adapters) whose read/write/shell tools\nexecute*inside* the transaction, jailed and offline by default, with every\nchanged path linked back to the tool call that caused it; plus live sessions\n(open/exec/close, streaming daemon ops, SDK`LiveSession` ) and mission control\nrebuilt as a document of record.**The hardening** — agent jailed by default\n(an unjailed one had been writing outside the transaction); UI cross-origin /\nCSRF refusal, CSP, and session-id path-injection guards; four replay-safety\nfixes (root-naming and kernel`.wh.` whiteouts, drifted-symlink escape,\nadded-dir-over-file, post-snapshot descendants); clean teardown on a failed\nlaunch; a Chromium DOM test suite.\n- \n2026-09-15 — merged to `master` . SonarCloud quality gate green; 80 assertions\nacross six suites.\n- \n2026-09-16 — v0.6: savepoints. The session holder now stays outside the jail and every command enters its own mount namespace over the current layer stack, so each writing command (each agent tool call) seals its own overlay layer with its cause stamped on it. On that: `rewind` (layers and\ntranscript cut together, tail archived),`resume` (message history rebuilt\nas the model saw it, operator note injected),`commit --only/--drop` (replay a selection of layers; conflicts checked on everything replay would\ntouch), and`blame` (committed content retained content-addressed; per-line\nattribution to session, turn, tool call and task, with drift detection).\nMission control renders the chain with rewind-here and keep/drop controls;\ndaemon ops and SDK methods for all of it. Also fixed:`log` printed its\nrecords twice; on the fuse backend a brand-new directory read as`replaced-dir` and its marker files could reach the tree on commit.\n- \n2026-09-16 — v0.7: the two-person rule, forks, blame in the UI. `review` puts a pending diff before a second model (read-only`read_file` , verdict\nbound to a fingerprint of the diff; a fresh rejection blocks commit,`commit --countersigned` and policy`require_review` demand a fresh\napproval; reviewer must differ from the agent).`fork --at N` copies a\nstack to a savepoint as a new pending session and`compare` shows where\ntwo continuations diverge. Mission control gets the countersignature\nblock, fork-here, and a per-line blame sheet reachable from any committed\npath. 101 assertions across eight suites, both backends.\n- \n2026-09-16 — v0.8: the workspace. `overlord ui` now opens a chat front door\nat`/` (the console moves to`/console` ): a conversation is a transaction, the\nfirst message opens a sandboxed offline session and runs the built-in agent\nin-process, follow-ups resume it, and the inspector shows the live diff with\nCommit / Discard and a one-click second-model check. Conversations are listed\nand selectable; a Settings panel holds the provider, model, API key (stored\n600, never echoed), working folder and sandbox grants. Shares the console's\nloopback bind, origin guard and nonce CSP; no daemon required. Also hardened`save_meta` to write atomically, so a reader never sees a half-written record.\n119 assertions across ten suites, both backends.\n- \n2026-09-16 — v0.9: model-configuration depth. `providers.py` — Anthropic\n(streaming, adaptive thinking, effort, refusal handling, opt-in server-side\nfallbacks), OpenAI, Azure OpenAI, OpenAI-compatible (Ollama, vLLM, LiteLLM,\ngateways) and Gemini, stdlib only, every one behind a base URL with extra\nheaders; live model listing (`overlord models` ,`/api/models` , SDK`models()` ); model-aware generation knobs that are never sent unless set;\nstreamed replies in the CLI and the workspace; strict parsing of streamed\ntool inputs; cut-off and refusal never run tool calls; per-conversation\nprovider/model with the config recorded on the session. 131 assertions\nacross eleven suites.\n- \n2026-09-16 — v0.10: connectors. `mcp.py` , a Model Context Protocol client\nover stdio and streamable HTTP; tools namespaced into the agent's set;\nconnectors are grants (per session, per conversation, capped by policy);\nnon-read-only tools pass an approval gate (ask / auto / readonly) — the CLI\nprompts, the workspace shows an Allow / Deny card, a brokered run without an\napprover is denied; every call and decision in the transcript and in the\nreviewer's dossier as an external action;`overlord mcp add|list|test|rm| approval` . 137 assertions across twelve suites.\n- \n2026-09-16 — v0.11: memory. `memory.py` : project notes (OVERLORD.md in\nthe folder), the person's notes (~/.overlord/memory.md) and a per-folder\njournal of committed agent sessions, injected into the system prompt with\ncaps and recorded on the session; a`remember` tool whose project scope is\na transactional, attributed file change and whose user scope only proposes\na note a person accepts;`overlord memory show|user|journal|accept` ; a\nMemory section in the workspace with proposal cards. 144 assertions across\nthirteen suites.\n- \n2026-09-16 — v0.12: accounts, TLS, a team on one machine. `auth.py` :\nscrypt-hashed accounts in a mode-600 file, login cookies and hashed\nbearer tokens, lockout after repeated failures, roles admin / operator /\nviewer checked on every route, per-account settings, keys and notes,`owner` recorded on sessions and honoured in both pages;`overlord users` and`overlord tls selfsign` ;`overlord ui --bind/--tls-cert/--tls-key/ --host` , refused beyond loopback without accounts and TLS; a sign-in page,\nan Accounts panel and read-only conversations in the workspace. 152\nassertions across fourteen suites.\n- \n2026-09-16 — v0.13: cost, audit, retention, deployment. `cost.py` : a\nprice table, a per-call ledger, dollars on the session, budgets from\npolicy / config / account checked before every call with a`budget` stop.`audit.py` : a hash-chained machine-wide log of every consequential\nact with`overlord audit verify` .`retention.py` :`overlord gc` .`/healthz` ,`overlord ui --log-json` , systemd units for the UI and\nnightly gc, a Dockerfile, doctor rows for accounts / TLS / audit / disk.\n163 assertions across sixteen suites.\n- \n2026-09-16 — v0.14: skills. `skills.py` : a SKILL.md catalogue from the\nfolder (`.overlord/skills/` , part of the transaction — an agent may author\none and it is a reviewed diff) and the machine (`~/.overlord/skills/` ,\npolicy-capped per folder), told to the model up front and loaded on demand\nwith a`skill` tool, every load on the transcript;`overlord skills list|show|add|new|rm` ; a Skills panel; two example skills. 169 assertions\nacross seventeen suites.\n- \n2026-09-16 — v0.15: single sign-on. `oidc.py` : OpenID Connect\nauthorization code with PKCE / state / nonce, identity from userinfo,\naccounts provisioned on first sign-in with roles from an e-mail list, a\ngroups claim or a default, allowed domains, SSO accounts without\npasswords;`overlord sso set|show|test|off` ; a sign-in button; every\nsign-in, refusal and change audited. 174 assertions across eighteen suites.\n- \n2026-09-16 — v0.16: long conversations. Context compaction: at three quarters of a configurable window the agent writes a handover note, older turns are dropped, the cut is a transcript event and a resume replays it exactly; the note's call is on the ledger. Logins persist across restarts (hashed at rest); a per-address rate limit. 179 assertions across nineteen suites.\n- \n2026-09-16 — v0.17: notifications. `notify.py` : webhooks subscribed to\naudit actions, slack text or signed json, links to the conversation,\nbackground delivery with retries;`session.needs_review` and`connector.approval_requested` become audited acts;`overlord webhooks` ;\na Notifications panel;`?sid=` deep links. 184 assertions across twenty\nsuites.\n- \n2026-09-16 — v0.18: bring your own vault. `vault.py` :`secret://NAME` references resolved at the point of use by a configured command, for\nprovider keys (and a`providers/<name>` convention), connector env and\nheaders, the SSO secret and webhook signatures; cached, audited without\nvalues;`overlord secrets set-command|show|test|off` . 189 assertions\nacross twenty-one suites.\n- \n2026-09-16 — v0.19: hand-offs. `bundle.py` :`overlord export` writes a\nsigned`.ovl` with the record, retained versions and a pending session's\nchanges;`overlord import` keeps the record (hashes and signature\nchecked, strict extraction) or replays the changes onto a folder as a new\npending session with`import` causes; Export in the inspector. Fixed:\nthe CLI printed a traceback instead of the error line for errors raised\nin submodules (a second copy of the engine was being imported). 194\nassertions across twenty-two suites.\n- \n2026-09-16 — v0.20: red team A11 + A12, and operating conditions. A11: the sandboxed executor inherited the operator's environment — provider keys and OVERLORD_HOME were readable with `env` , and after an in-process\nscrub still with`cat /proc/1/environ` ; the holder chain now starts with\nan allowlisted environment, on both backends. A12: the jail bound the\nhost's /etc, /usr and /opt read-write — a sandboxed command could write\nthe machine's system tree; every host bind and its submounts is now\nremounted read-only with its locked flags kept. The system prompt states\nthe agent's model, sandbox, network, budget and owner, and that everything\nis recorded.`escape_test.py` : an agent that probes its situation; every\nprobe fails and is recorded. 197 assertions across twenty-three suites.\n- \n2026-09-16 — v0.21: red team A13. With `net: host` a session reaches the\nhost's loopback — and so OVERLORD's own UI, which in open mode trusted\nloopback: an agent could read every session and commit its own. Open mode\nnow has a launch token (`?token=…` printed by`overlord ui` , kept in`~/.overlord/ui.token` where the jail cannot see it); the conditions\nblock names the served model as the API identifier to trust. 198\nassertions across twenty-three suites.\n- \n2026-09-16 — v0.22: red team A14. A jailed command held every capability in its user namespace with NoNewPrivs off and no seccomp policy (found by an agent from the inside). The jail now clears ambient caps, drops the bounding set, sets NoNewPrivs, installs a seccomp filter (x86_64, aarch64; ptrace allowed only under a strace trace) and zeroes every capability set before exec. The conditions block names OVERLORD's own source when it is the working folder. 198 assertions across twenty-three suites.\n- \n2026-09-16 — v0.23: resource grants and deterministic gates. `limits` (memory, pids, cpu, disk, file size, open files) enforced by rlimits in\nevery command, a cgroup around the session (v2 via systemd, v2, v1) and a\nper-layer disk measure that ends the session and stops the agent; policy\nceilings;`--limit` . Protected paths need a complete countersignature and\nrefuse --force; a truncated reviewer dossier fails closed; shell-shaped\nconnector tools withheld without the`connector_shell` grant. The\nworkspace names the second model under Settings instead of guessing a\nprovider. 204 assertions across twenty-four suites.\n- \n2026-09-16 — v0.24: OpenAI over the Responses API. GPT-5.5 refuses function tools together with a reasoning effort on `/v1/chat/completions` , which\nbroke every tool-using run and the second-model check the moment an\neffort was set. The`openai` provider now speaks`/v1/responses` : the\nsame streaming contract,`store: false` , the model's encrypted reasoning\nreplayed ahead of the tool calls it produced so a multi-turn run keeps its\ntrain of thought,`incomplete` and`refusal` mapped to the stop reasons the\nagent already checks (chat completions'`length` and`content_filter` now\nmap too).`--api` / Settings → Generation → OpenAI API picks the shape per\nendpoint; compatible servers and Azure keep chat completions. A usage\nmeter in the rail shows the provider's own rate-limit headroom and spend\nagainst the daily and monthly budget lines;`month_usd` budget; current\nClaude 5-family list prices. A`net=host` jail resolves names: the\nresolver behind a symlink out of`/etc` (WSL2, systemd-resolved) is bound\nat its real path, read-only. The fuse probe requires`/dev/fuse` and`doctor` names the missing piece; suites SKIP without a backend.\n- \n2026-09-16 — v0.26: the audit chain is signed. Each link is a MAC keyed by `~/.overlord/audit.key` (0600), so a rewrite or a downgrade to an unsigned\nchain is caught, not just a partial edit;`verify` reports`signed` /`UNSIGNED` .`audit checkpoint` emits the head and`audit verify --pin` binds the live log to a witnessed head, catching a truncation or rewrite\neven by the key holder when the pin is kept off-box. (Review verdicts were\nalready bound to a fingerprint of the exact diff; keying the log now covers\nthose records too.)`--audit`\n- \n2026-09-16 — v0.27: recorded egress ( `--net proxy` ). The agent's network is\nmediated: an empty namespace with no route out, and one path through a\nrecording, allowlisting proxy (`netproxy.py` ). Every connection is on the\nsession's`egress.jsonl` with destination and byte counts;`--net-allow` turns it into a lock (403 outside the list). Enforced by the kernel (empty\nnetns) with no slirp/passt/root — a front in the namespace passes client\nsockets to a back in the host namespace over SCM_RIGHTS. Workspace gains a\n\"recorded (proxy)\" network option and an allowlist field. Proven end to\nend on the kernel backend against a local upstream, plus an offline proxy\nsuite. This closes the network half of the trust-kernel \"complete\nmediation\" gap; connectors remain host-side.\n- \n2026-09-16 — v0.28: connectors brought inside the record. A connector acts outside the transaction and cannot be rolled back, so — like a commit — each call is now bound into the keyed audit chain: `connector.call` names the\ncall by a fingerprint of (server, tool, arguments),`connector.decision` carries that fingerprint and, when a person approved, WHO approved (the\nsigned-in account), and`connector.result` records the result's hash and\nbyte size. An approval therefore authorizes one exact call, verifiably, and\nthe whole connector effect is tamper-evident under the audit key. This is\nthe last piece of the trust-kernel chain of custody: task → tool call →\ndiff → verdict → commit, and now external actions too.\n- \n2026-09-16 — v0.29: an off-box witness for the audit head. A local key stops a forger who lacks it but not the key's holder; `overlord audit witness <url> --auto` sends a signed head (`{seq, hash}` + a MAC) to an append-only\nendpoint after each consequential act, and`audit verify --witness` checks\nthe live log still carries it — catching a truncation or rewrite below the\nwitnessed point even by the key holder, because the witness keeps the\nsequence they cannot retract.`checkpoint --send` , a doctor row. This makes\nthe last trust-kernel boundary — the local anchor — an operational setting\nrather than a caveat.\n/`/audit` : the containment audit as a preset, authorized and scoped so\nthe model takes it as assigned work. The rail footer stacks its controls.\nFrom the first audit: A8 picked the first`upperdir` in the mount table,\na WSL2 host overlay rather than the session's, so it tested the wrong\nmount there; it now picks the overlay at the working folder.`doctor` inside a jail says it is inside one; state written from inside a jail\ngoes to the jail's`/tmp` , not the project. Settings → Sandbox now has a\nNetwork control (offline or host) independent of the jail.", "url": "https://wpnews.pro/news/overlord-a-trust-kernel-for-ai-agents", "canonical_source": "https://github.com/B1tR0n1n/overlord", "published_at": "2026-09-17 02:17:18+00:00", "updated_at": "2026-09-17 02:23:52.379766+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "ai-infrastructure", "developer-tools", "ai-tools"], "entities": ["Overlord", "Ubuntu 24.04", "WSL2", "Docker Desktop", "AppArmor", "overlayfs", "fuse-overlayfs", "strace"], "alternates": {"html": "https://wpnews.pro/news/overlord-a-trust-kernel-for-ai-agents", "markdown": "https://wpnews.pro/news/overlord-a-trust-kernel-for-ai-agents.md", "text": "https://wpnews.pro/news/overlord-a-trust-kernel-for-ai-agents.txt", "jsonld": "https://wpnews.pro/news/overlord-a-trust-kernel-for-ai-agents.jsonld"}}