The Semantic Filesystem is the view layer that lets a user, a legacy application, or a native Hyperion Capability ask for information the way a person actually thinks about it — "everything related to my vacation" — and receive a coherent, navigable result, without Hyperion maintaining a second copy of the data to make that possible. It sits directly on top of 09 — Knowledge Graph (which owns the graph model: nodes, edges, embeddings, reasoning-search) and 28 — Storage Engine (which owns the physical bytes, per 29 — Database Schema). The Semantic Filesystem owns neither the graph model nor the physical storage — it owns the translation between "a query" and "a folder," and between "a legacy path" and "a Semantic Object."
01 — Vision & Philosophy establishes
that the unit of stored information is a Semantic Object, not a file in a folder, and that
folders are a compatibility view, not the underlying model (see
02 — Core Architecture). That principle is easy to
state and hard to make real: the moment a real filesystem stops being the source of truth, every
legacy application that calls open(), every script that does ls, and every user habit built
over decades of "where did I save that?" needs an answer that does not feel broken. The
Semantic Filesystem is that answer. It must make "show me everything related to my vacation"
resolve to a live, correct assembly of photos, videos, emails, map pins, hotel bookings, messages,
expenses, calendar events, notes, and receipts — objects that may live in entirely different
Capabilities and have never been co-located by any human — while simultaneously making a folder a
user dragged three files into behave exactly as durably as it would on any traditional OS. Losing
either property breaks trust: lose the first and Hyperion is just a filesystem with a search box;
lose the second and a user's explicit organizational effort evaporates into "the AI reorganized my
files," which Design Invariant 1 (no silent authority) in
02 — Core Architecture forbids outright.
Legacy POSIX App Native Hyperion Capability / Agent
(Compatibility Layer, (Intent Engine, 05 / Agent
see 27-compatibility-layer.md) Runtime, see 11)
│ open("/Vacation/IMG_0231.jpg") │ query("everything related to my vacation")
▼ ▼
┌───────────────────────┐ ┌─────────────────────────────┐
│ POSIX Compat Shim │ │ Semantic Query API │
│ (path <-> object_id │ │ (NL/structured query in, │
│ resolution, FUSE- │ │ VirtualFolder out) │
│ class interface) │ │ │
└───────────┬───────────┘ └──────────────┬──────────────┘
│ │
└───────────────┬───────────────────────┘
▼
┌───────────────────────────────────┐
│ Query Resolver / │
│ Virtual Directory Materializer │
│ (graph traversal + vector search │
│ + rank/merge + ACL filter) │
└───────────────┬───────────────────┘
│
┌───────────────────┼───────────────────────┐
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ 09 — Knowledge Graph │ │ 28 — Storage Engine │
│ (edges, embeddings, │ ◄────────────► │ (blobs, metadata, ACL, │
│ reasoning-search) │ same tables, │ version chain, per 29) │
│ │ see 29-schema │ │
└─────────────────────────┘ └─────────────────────────┘
Both entry points — the POSIX shim and the native Semantic Query API — terminate in the same Query Resolver, so a legacy app browsing a synthesized directory and a native Capability running a structured query see a provably consistent view of the same objects; there is exactly one resolution path, not two.
VirtualFolder — the materialized result of a query, not a stored object:
{query_spec, member_object_ids: [ObjectID], materialized_at, ttl, sort_order, snapshot_token}.
The snapshot_token matters more than it looks: it is what lets a POSIX opendir()/readdir()
sequence see a stable listing for the lifetime of the handle even though the underlying graph may
change mid-traversal (§Failure Modes).
PathMapping cache — {synthesized_path <-> object_id}, persistent across sessions so that a
legacy app's cached path or inode number keeps resolving correctly rather than shuffling every
time the Query Resolver re-runs. Populated lazily as paths are synthesized, never precomputed for
the whole graph.
Collection — a genuine Semantic Object of object_type = 'collection' (see
29 — Database Schema) plus explicit member_of edges with
origin = 'explicit'. This is the entire mechanism by which a user-created folder is preserved:
it is not a special case in the Semantic Filesystem's code, it is an ordinary Semantic Object
with ordinary explicit edges, which means it survives graph GC, re-indexing, and AI-driven
re-organization exactly as durably as any other explicitly authored fact.
QuerySpec — the structured form a natural-language request like "everything related to my vacation" is compiled into by the Intent Engine: an anchor object or concept (the "Hawaii trip"), a hop-depth bound, optional type filters, and an optional embedding for semantic (rather than purely relational) matches.
Query resolution ("query-as-navigation"). A request — typed, spoken, or a synthesized path
under the POSIX shim's mount root — is resolved in four stages: (1) the
Intent Engine and Context Engine compile it into a
QuerySpec, resolving "my vacation" to a concrete anchor object via recent
Context Bundle state or a fuzzy name match; (2) the Query Resolver
issues a bounded-hop graph traversal from the anchor through
09 — Knowledge Graph (reusing exactly the recursive-CTE pattern shown in
29 — Database Schema) and, in parallel, a vector similarity
search for objects that are topically related but never explicitly linked (a journal entry that
mentions the trip but was never tagged); (3) results are merged, deduplicated by object_id, and
ranked; (4) the ranked set is wrapped into a VirtualFolder and, if a POSIX view was requested,
each member is given a synthesized path.
Path synthesis (POSIX compat shim). Deterministic, not creative: {object_type}/{date or title} with a collision suffix (-2, -3, ...) when two objects would otherwise synthesize the
same leaf name. The PathMapping cache is consulted first — an object that has been assigned a
path before keeps that path — so that stable identifiers legacy tooling relies on (inode number,
absolute path) do not drift between one ls and the next.
Write-back. A legacy app's write is buffered by the compat shim and only committed as a
Storage Engine Object Write Transaction on close()/fsync(), giving POSIX's usual
close-to-open consistency without ever exposing a half-written Semantic Object (the same
atomicity boundary as 28's OWT). What happens next depends on
where the write landed: a write into a real, user-created Collection folder (§Data Structures)
produces an explicit member_of edge, because the user's directory placement is itself an act of
organization worth preserving. A write into a virtual, query-materialized folder (e.g. an app
that insists on saving into "/Vacation/" where "Vacation" was synthesized by a live query, not
created by the user) does not fabricate a false explicit edge — the object is ingested normally,
Capabilities infer whatever relationships genuinely hold, and the PathMapping cache pins that
specific path to that specific object so the app's own bookkeeping keeps working even if the
object later drops out of the live query's result set.
Folder preservation. Creating a folder (mkdir under the compat shim, or "put these three
photos in a folder called Receipts") always creates a Collection Semantic Object plus explicit
edges — never a filesystem directory entry with no graph representation. This is what makes
"traditional folders remain available only for compatibility" true in both directions: the
compat shim can render it as a directory for legacy apps, and the Knowledge Graph can traverse it
as an ordinary explicit relationship, but there is only one underlying fact.
fs.query(capability_token, spec: QuerySpec) -> VirtualFolder
fs.materialize(capability_token, virtual_folder_id) -> [DirEntry]
fs.mkcollection(capability_token, name, parent?: CollectionID) -> CollectionObject
fs.add_to_collection(capability_token, object_id, collection_id)
fs.mount_posix(capability_token, root_spec) -> POSIX mountpoint (FUSE-class)
fs.resolve_path(capability_token, path) -> object_id
fs.write_back(capability_token, path, bytes) -> ObjectVersion
Every call takes the caller's capability token and is subject to the same per-object ACL check
enforced at the Storage Engine layer (see
28 — Storage Engine); the Semantic Filesystem
adds no privilege of its own and cannot be used to see an object the caller could not otherwise
read. Legacy applications reach this API exclusively through the POSIX shim mounted by the
27 — Compatibility Layer; native Capabilities and Agents
(see 11 — Agent Runtime) call fs.query directly.
function resolve_query(capability_token, spec):
anchor = intent_engine.resolve_anchor(spec.natural_language, context_bundle)
relational = knowledge_graph.traverse(anchor, max_hops=spec.hop_bound,
predicate_filter=spec.predicates)
semantic = storage_engine.vector_search(capability_token, spec.embedding, k=spec.k)
merged = rank_and_dedupe(relational + semantic)
visible = [o for o in merged if acl_check(capability_token, o)] # re-checked, never cached
return VirtualFolder(
query_spec=spec,
member_object_ids=[o.object_id for o in visible],
materialized_at=now(), ttl=spec.ttl or default_ttl,
snapshot_token=new_snapshot_token(),
)
function synthesize_paths(virtual_folder):
for object_id in virtual_folder.member_object_ids:
if path_mapping_cache.has(object_id):
yield path_mapping_cache.get(object_id)
else:
candidate = f"{type_of(object_id)}/{title_or_date(object_id)}"
path = disambiguate(candidate, path_mapping_cache)
path_mapping_cache.put(object_id, path)
yield path
function write_back(capability_token, path, bytes):
object_id = path_mapping_cache.resolve(path) or new_object_id()
txn = storage_engine.begin_txn(capability_token)
version = storage_engine.put_object(txn, ObjectWriteTxn(
object_id=object_id, blob_payload=bytes,
metadata_delta=infer_metadata(bytes),
))
storage_engine.commit_txn(txn)
parent = containing_folder(path)
if is_user_created_collection(parent):
knowledge_graph.add_edge(object_id, 'member_of', parent.collection_id, origin='explicit')
else:
path_mapping_cache.pin(path, object_id) # keep app's own bookkeeping stable
# no fabricated explicit edge; ordinary inference pipeline runs asynchronously
return version
Because the Query Resolver can surface an object that matches a query relationally or
semantically but that the caller is not permitted to see (a shared trip photo album where one
photo carries a stricter ACL), every result is re-checked against the caller's capability token
at materialization time, never assumed authorized because it appeared in a traversal — the same
per-row ACL enforcement described in
29 — Database Schema. The POSIX compat shim runs
legacy applications inside the Trust Boundary depth appropriate to that app
(see 03 — Kernel Architecture and
15 — Security Architecture) and must forward the originating
identity on every call rather than acting as an ambient-authority proxy — a compromised legacy
app can only ever see what its own token permits, regardless of what the shim process itself
could technically reach. Path synthesis is treated as untrusted input on the way back in:
write_back validates the resolved path against the caller's mount root to prevent a legacy
app's ../../ traversal from being translated into a graph write against an object outside its
grant. Because one object can legitimately appear under many synthesized paths at once (a photo
in both "Vacation" and "2026" and "Beach Photos"), the shim presents this as POSIX hard-link
semantics rather than as duplicated files, which is the correct semantic and avoids accidentally
implying multiple independent copies exist.
- A VirtualFolder's underlying graph changes mid-listing (a background Agent adds a new photo to
the trip while a legacy app is mid-
readdir()), producing a listing that would otherwise flap from one syscall to the next. - Two objects synthesize to the same leaf path and are not disambiguated deterministically across runs, breaking a legacy app's cached path.
- An ambiguous natural-language query ("my trip") resolves to the wrong anchor when the user has multiple candidate trips in context, returning a folder full of the wrong vacation.
- The compat shim crashes between buffering a write and committing it, risking data the legacy app believes was saved.
- The Query Resolver times out on an unusually large or deep graph (a power user with a decade of
photos), leaving a legacy
lshanging.
Every POSIX opendir() is bound to a snapshot_token (§Data Structures): the directory listing
served for the lifetime of that handle is frozen to the graph state at open time, matching the
stability legacy applications assume, with changes visible only on the next opendir() — this is
the direct mitigation for the first failure mode above. Path collisions are resolved
deterministically (stable suffix ordering keyed by object_id, not creation order) so that
repeated runs of the disambiguation algorithm produce the same mapping without needing to persist
every possible collision in advance. Ambiguous anchor resolution falls back to presenting the
candidates for disambiguation rather than silently guessing, consistent with Design Invariant 4
(every autonomous action explainable) in
02 — Core Architecture. Write-back uses the same
buffer-then-atomic-commit pattern as the Storage Engine's Object Write Transaction
(see 28), so a crash mid-write leaves either the old committed
version or nothing — never a half-written Semantic Object — and the legacy app's next stat()
reflects reality rather than a promise. A timed-out Query Resolver degrades to a partial,
clearly-labeled result set rather than blocking indefinitely, per Design Invariant 5 (degrade,
never fail closed on the user's goal).
VirtualFolders are cached with a TTL and invalidated incrementally: rather than re-running a full
traversal on every access, the Query Resolver subscribes to relevant object/edge changes via
31 — Event System and only re-materializes the specific folders whose
inputs actually changed. Because path synthesis and ACL checks are the only per-request-unique
work once a VirtualFolder is cached, a legacy ls-equivalent call is expected to return in the
low tens of milliseconds for realistic personal-scale graphs, in line with the responsiveness
targets in 36 — Performance Benchmarks. Embedding computation for
newly ingested objects is deferred to a background pipeline rather than performed inline on
write, so a large photo import does not block on semantic indexing before the bytes are durably
stored (see 28 — Storage Engine).
Presenting query results as dynamic, live-updating VirtualFolders is what makes "everything
related to my vacation" actually stay correct as new photos, emails, and receipts arrive — but it
is in tension with decades of POSIX applications that assume a directory tree only changes when
they change it. The snapshot_token mechanism (§Recovery) resolves this by trading strict
liveness for per-handle stability, which is the same trade-off traditional filesystems already
make for open file descriptors, just extended to directory handles. Keeping the graph as the only
source of truth, with no duplicated "compat filesystem" copy, avoids drift between what a native
query sees and what a legacy ls sees, at the cost of every legacy filesystem access paying for a
live query rather than a cheap directory-block read — mitigated, per §Performance Analysis, by
caching and incremental invalidation rather than by maintaining a second, independently-writable
tree. Finally, showing both a folder view and a semantic query bar simultaneously — rather than
picking one — is a deliberate concession to Universal Usability
(see 01 §5): a user who
never wants to think in graphs can still drag files into folders and get durable, correct
behavior, without that choice limiting a power user who wants to query by meaning instead.
The POSIX compat shim is validated against a standard POSIX filesystem compliance suite
(pjdfstest-class tests) to catch semantic drift from what legacy applications actually assume.
Property-based tests assert the folder-preservation invariant directly: for any sequence of
AI-driven re-organization, re-indexing, or GC events, every explicit member_of edge created by
a user-initiated mkcollection/add_to_collection call must still resolve after the sequence
completes. The natural-language-to-QuerySpec compiler is fuzzed with ambiguous and adversarial
phrasing to verify it degrades to a disambiguation prompt rather than a wrong silent guess.
Concurrency tests exercise simultaneous writes and live queries against the same region of the
graph to validate snapshot_token isolation. A golden end-to-end test encodes the vacation
scenario from this document's Motivation section directly: seed photos, hotel bookings, emails,
and calendar events per the fixture in
29 — Database Schema, issue the
query, and assert every expected object type is present in the resulting VirtualFolder. These
feed the shared harness in 35 — Testing Strategy.
Next: 11 — Agent Runtime.