AIOS v2: The Scope Is the Filesystem
A restructure log: what broke in the AIOS v1 tree at multi-scope scale, why identity-first beats type-first, and what shipped in v2.
TL;DR: Two weeks after I open-sourced AIOS, I rebuilt its directory structure in place. v1 organized notes by type — Atlas, Calendar, Efforts, the LYT ACE pattern — which is fine with one scope and quietly breaks at multi-scope scale, because “whose note is this?” gets answered by re-deriving membership from globs in four different places, and those four places can disagree. v2 makes scope the first path segment: one top-level directory per scope, everything inside it shaped the same way. The prefix is the invariant now — guard checks, ingest, leak audits all key on it instead of trusting a config file to agree with the folder tree. If you’re starting a vault today, start scope-first even with exactly one scope; the overhead is negligible and it makes a second scope free later instead of a redesign.
What v1 got right, and where it stopped working
The launch post described AIOS as an editorial memory system: raw session signal compounds into durable project notes, a Knowledge Map indexes them, an agent reads the index first instead of searching everything. That architecture hasn’t changed — it’s still the three-layer wiki design I wrote about before AIOS existed. What changed is the directory tree underneath it, and it’s worth being honest about why, because the failure mode is one anyone building a filesystem-as-memory system for an agent is going to hit.
v1 organized by note type, borrowed from Nick Milo’s LYT ACE pattern — Atlas (timeless), Calendar (time-based), Efforts (projects), Sources (curated inputs):
+/ inbox
AIOS/ core + scope content
Atlas/ timeless notes
Calendar/ time-based notes
Efforts/ projects
Sources/ curated inputs
x/ templates, archive
That’s a reasonable shape for a single person with a single context, which is most of what I described in the launch post — and the bootstrap interview still tells most people one scope is the right answer for them. But I run more than one scope myself, and at that scale the type-first tree stops answering the question that actually matters to a machine reading it: not “what kind of note is this,” but whose note is this.
The question v1 couldn’t answer from the path alone
In v1, a scope’s identity was smeared across four unrelated places:
AIOS/me-<scope>.md for identity,
Sources/<scope>/ for curated inputs,
AIOS/Projects/<scope>/ for project brains, and a glob
entry in AIOS/Systems/layers.tsv telling everything else
which paths counted as that scope. None of those four locations shared a
parent directory. Nothing about the path told you the scope — you had to
already know the manifest, or grep for it.
That produced three specific failures, documented in docs/structure-evolution.md:
- Every scope-aware mechanism re-derived membership from
globs. The write guard, the ingest script, the leak-audit
subagent — each one re-read
repo-layers.tsvand matched paths against it independently. The config and the actual folder layout could disagree, and nothing caught that until something broke. - Silent failures when a glob pointed at a directory that didn’t exist. A stale or mistyped entry in the manifest didn’t error — it just matched nothing, and a scope’s notes quietly stopped being classified as that scope’s notes.
- Adding a scope meant four coordinated edits. A new
me-<scope>.md, a newSources/<scope>/directory, a newAIOS/Projects/<scope>/directory, and a new glob row — miss one and the scope worked in three of the four subsystems and not the fourth, with no signal telling you which.
None of this showed up with one scope, which is exactly why it shipped that way and why the launch post’s advice to default to one scope still holds. It showed up the moment a second scope needed the same guarantees as the first, and the fix wasn’t a patch to the glob logic — it was admitting the type-first tree was answering the wrong question first.
v2: the scope is the filesystem
The rebuild, landed as restructure v2: scope-first tree,
makes scope the first path segment instead of an attribute you look
up:
+/ inbox
AIOS/ scope-neutral OS core
<scope>/ one dir per scope, e.g. main/
me.md
notes/ timeless, hand-written
content/ drafts, works-in-progress
projects/ AI-maintained project brains
sources/ immutable curated inputs
archive/ formerly x/
Calendar/ didn’t survive as a top-level bucket either —
it moved to AIOS/History/{days,reviews}, because time-based
notes are the agent’s memory surface, not a peer of Atlas and Efforts.
That’s a smaller call than the scope restructure, but it’s the same
instinct: stop filing things by what they superficially resemble and
file them by what actually reads them.
The type-based split didn’t disappear — notes/,
content/, projects/, sources/
still carry that distinction. It just moved inside the scope boundary
instead of living alongside it. A path’s first segment now names its
scope, full stop, and everything downstream of that segment is free to
keep whatever internal shape makes sense.
A path’s first segment names its scope — the prefix everything keys on, instead of a config file everything has to trust.
What changes when the prefix is the invariant
This is the part that’s worth the rebuild, and it’s mechanical rather than philosophical:
- Guard checks become prefix validation. The write guard used to match a path against a glob pulled from a config row. Now it checks whether the path starts with a known scope directory. There’s no config to drift out of sync with the folder tree, because the folder tree is the config.
- “Sources are immutable” becomes a shape rule, not a
per-scope rule. In v1, immutability was enforced per glob, once
per scope, and a new scope needed that rule written for it explicitly.
In v2 the rule is
*/sources/*— it applies to every scope automatically, including one that doesn’t exist yet. - Adding a scope is a
mkdirand one manifest row. Not four coordinated edits across unrelated directories — one new top-level folder (or a copy of the shippedmain/scaffold) and one glob entry pointing at it. AIOS/stays deliberately scope-neutral. The whole prefix invariant only holds if the OS core never itself looks like a scope. That’s a constraint on future changes toAIOS/, not just a description of the current tree.
The commit history behind this is left readable on purpose — v1’s LYT ACE layout is still there to diff against in git log, not squashed away, because the shape of what broke is as useful to see as the shape of the fix.
The quieter half: cleanup ported back from the vault
Alongside the restructure, a batch of fixes that had already proven themselves in my own running vault got ported back into the public template — smaller changes individually, but worth naming because they’re the kind of thing that only surfaces after a system runs unattended for a while:
aios-digest.shnow fingerprints its diffstat output, so identical digest blocks collapse into one instead of piling up duplicate entries. It also floors untracked-only churn and, critically, commits only the digest paths it wrote — so staged human work sitting in the vault never gets swept into an unattended commit alongside it.aios-nightly-ingest.shgained a heartbeat log and a.lastrunmarker, plus an atomicmkdir-based lock with a stale-lock reaper, so a scheduled 3am run can’t race a human firing/aios-ingestby hand mid-session.- The skill set consolidated from nine to three:
propagatenow covers what used to be separate cascade and harmonize skills,chronicleabsorbed three overlapping skills, andvault-mapabsorbed the builder and janitor skills that used to duplicate its work. Three new janitors replaced ad hoc cleanup:log-rollup(monthly log rotation, count-verified so nothing silently vanishes),km-rotate(Knowledge Map lines rotate back to summaries without dropping facts), andopen-flags(⚠️/OPEN markers get dispositioned on every ingest with a 30-day escalation instead of aging out unnoticed). - A new
publish-verifyagent runs a post-publish token grep and traces first-person claims back against the vault — an unsourced claim at publish time is treated as a fabrication risk, not a style nitpick.
None of that required the restructure. It shipped in the same pass because it was already vault-proven and the template was the honest place for it to land.
The general lesson, if you’re building something similar
If a filesystem’s primary reader is a machine and it has to answer “does this belong to X” for more than one X, don’t organize by content type and bolt scope on as metadata. Organize by scope first and let content type be a convention inside that boundary. Type-first taxonomies like LYT ACE are built for a single human browsing their own notes, where “whose is it” is never in question because the answer is always “mine.” The moment a second identity enters the picture, that assumption is the whole design, and everything downstream — access guards, leak audits, “what can I show this agent” — either keys on a path prefix or keys on a config file hoping it still matches the folder tree. Prefixes don’t drift. Config files do.
Try it
Everything above is in github.com/sublimecoder/aios,
including the full
structure-evolution writeup with the v1 tree, the v2 tree, and the
reasoning in more detail than fits here. If you’re setting one up fresh,
start scope-first even with one scope — main/ ships
pre-filled as exactly that scaffold, so there’s no extra step for the
common case, and a second scope later is a directory copy instead of a
rewrite.
If you’re past the point of running this for yourself and thinking about how a team standardizes shared context across an AI-native engineering org — where the scope boundaries actually need to sit, and where a real productivity system stops and tool sprawl starts — that’s the kind of design question I work through with clients in fractional engineering leadership engagements.