Browse your knowledge, not your folders. ontodag-fs presents an OntoDAG category lattice as a real, mountable filesystem, with file content stored on Ethereum Swarm via swarmfs.
It is a modern descendant of Gifford's Semantic File System (SOSP '91): directory names are interpreted as queries, not locations — but with a concept lattice instead of flat attributes, and content-addressed storage instead of a local disk.
$ odag-fs tree /
/
├── animal/
│ ├── dog/
│ │ └── rex.txt
│ ├── pet/
│ │ └── rex.txt
│ └── spider/
│ ├── document/
│ │ └── web-study.md
│ └── charlotte.txt
└── document/
├── spider/
│ └── web-study.md
├── DESIGN_DECISIONS.md
└── SPEC.mdThe same file appears under every path it belongs to — web-study.md is
both a spider thing and a document, so it lives at /animal/spider/document/,
/document/spider/, and every reordering of those. No copies, no symlinks:
one object, several true names.
The lattice can also carry typed values, and then paths can query them:
ls '/parcel/weight(..5kg)/.all' lists everything filed at 3 kg, 4.5 kg, … —
a virtual directory that exists for any well-formed bound without anyone
creating it, computed exactly (rational arithmetic, no floats, nothing
rounded). Friendly units resolve (weight(3kg) ≡ weight(3000g)); geohash
prefixes (/geo(u2)) and fits-inside sizes work the same way. See User
Guide §"Typed values in paths".
- Paths are queries.
/pet/dogmeans "everything that is a pet AND a dog". Order never matters:/dog/petis the same place. - Directories are concepts. Subdirectories are the categories that meaningfully refine what you're looking at.
- Files are classified objects. A file's identity is its Swarm content address; its name is just a display label.
- The ontology does the work. File something under
dogand it is automatically undermammal,animal,pet— subsumption is free. - References cannot dangle. Content addressing means a classification can never point at a file that "moved" — there is nowhere to move to. (Tag filesystems on top of paths fight this forever; here it is structurally impossible.)
$ pip install "ontodag[swarm]" ontodag-fs
$ odag-fs set store swarm:my-store # once — the same setting odag uses
$ odag-fs tree /
$ odag-fs cat /pet/dog/rex.txt
$ odag-fs # interactive: cd/ls/cat with a > prompt
$ odag-fs --as-of 8e637ecc6ad6 tree / # the store as it was (odag history lists roots)
$ pip install "swarmfs[fuse]" && odag-fs mount ~/mnt # + libfuse2; add --rw to file by cpNew here? Read the User Guide — a tutorial that takes you from an empty machine to a mounted, browsable ontology, with worked examples of every capability.
v0.1 — browse and file. Browsing (ls, tree, cat, info, FUSE mount)
is complete and tested, and --as-of ROOT browses any past version of a store
that keeps them (rs:/swarm:) — a version is a root, so this only hydrates
from a different one. Filing works through the fsspec surface:
fs.put_file(local, "/dessert/italian/tiramisu.md") stores the bytes on Swarm
and classifies them in one step, rm retracts a classification (an object left
with none waits in /.unfiled/), mv reclassifies or relabels, cp within
the view is an intent union, and cp /.swarm/<ref> /<concept>/<name> classifies
existing Swarm content without re-uploading. Bytes never move; identical
content filed twice is one object. odag-fs mount --rw does the same
from any shell or tool: cp report.pdf ~/mnt/finance/2026/ files it, rm
retracts, mv reclassifies. The lattice itself (creating categories) is
edited through OntoDAG's own API, never through the view — mkdir is
refused. See ROADMAP.md.
FUSE mount (fsspec.fuse — a deployment mode, not architecture)
│
OntoDAGFileSystem (this repo: fsspec AbstractFileSystem, stateless glue)
│ │
OntoDAG swarmfs
(classifier/index) (bytestore: fsspec backend for Swarm)
│ │
recordstore Bee node
(persistence of the DAG)
This repo owns no state: classifications live in OntoDAG, the DAG
persists through recordstore, bytes live on Swarm. OntoDAGFileSystem is a
pure fsspec backend, so besides
the CLI and FUSE you also get the whole fsspec ecosystem (pandas, pyarrow,
DuckDB, …) for free.
| Repo | Role |
|---|---|
| ontodag | the category DAG — index and classifier |
| swarmfs | fsspec backend for Swarm — the bytestore (REFERENCE, test-pinned) |
| recordstore | versioned key→record store over Swarm — DAG persistence (REFERENCE, test-pinned) |
| mdl-fca | MDL/FCA learning over the same DAG (upstream, not a dependency) |
- User Guide — tutorial, setup, worked examples
- Reference — definition-first lookup tables (exports,
options, path grammar, errors), pinned against the code by
tests/test_reference.py; the document to hand to an AI agent - SPEC.md — the precise v0/v0.1 contract, method by method
- DESIGN_DECISIONS.md — prior art and every decision, with reasoning
- ROADMAP.md — what's in scope now vs later
- CHANGELOG.md — what each release changed
$ git clone https://github.com/petfold/ontodag-fs && cd ontodag-fs
$ python3 -m venv .venv && .venv/bin/pip install -e ".[test]"
$ .venv/bin/pytest # 315 testsThe test suite runs entirely offline — no Bee node, no FUSE — and every test runs against both the in-memory reference index and the real OntoDAG adapter, so the two cannot drift apart. Path-resolution invariants are property-based (hypothesis); see SPEC §6 for the invariant list.