/OMNIFS
omnifs lets you access the world through your filesystem. APIs and data sources become files and directories you can ls, cat, grep, pipe, and hand to an agent.
Reads land state on your machine. The first touch fetches an object once. The host stores it durably, renders every file from it, answers warm reads locally, and owns auth, caching, and egress for every provider. New systems attach as sandboxed Wasm providers built with the omnifs SDK.
Reads land state on your machine. The first touch fetches an object once. The host stores it durably, renders every file from it, answers warm reads locally, and owns auth, caching, and egress for every provider. New systems attach as sandboxed Wasm providers built with the omnifs SDK.
One namespace to access everything.
APIs made systems and platforms programmable, but they did not give us a shared substrate. In fact, they encouraged fragmentation.
Each service exposes its own API, schema, auth model, terminology, error table, pagination rules, rate limits. Often, each requires a dedicated SDK.
Conversely, the filesystem is the most durable interface in computing. Every program, agent, language masters disk and file I/O. Semantics are strict and universally understood, and /filesystem/paths offer hierarchical addressing.
const octokit = new Octokit({ auth })
const { data } = await octokit.rest
.issues.get({ owner, repo, issue_number })
console.log(data.title)
// + install the sdk
// + implement the auth flow
// + paginate, retry, rate-limit
// + repeat for every other vendor How does omnifs relate to MCP? MCP gives models tools to call. omnifs gives them external state as paths. A path can be traversed, grepped, diffed, cached, piped to any other process, and read again after the session ends.
Paths are the unified interface.
/omnifs mounts systems, services, data sources (really, anything), onto the local filesystem. Instead of teaching humans, agents, and programs how to deal with every API, they can all rely on the same stable interface we've known and loved for decades: files and directories.
Every provider translates its service into directories, files, fields, rendered views, and searchable trees.
The common layer is not a universal schema. The common layer is the filesystem: paths that humans, scripts, agents, caches, and traces can all operate on without learning the upstream API.
One fetch becomes a directory.
Behind every path sits an object. The first read fetches the canonical upstream payload once, stores it durably, and teaches the host every path it answers. item.json is those bytes verbatim. The other leaves render from them on your machine.
Freshness is mechanical. Mutable objects revalidate with the upstream validator, immutable versions stay trusted, and provider events invalidate exactly the paths they touch. State leaves the cache by fact instead of timer.
Color marks the moments the network is touched. Every gray read was answered from your machine. An agent that re-reads its working set pays the upstream once, and the state it saw is on disk, addressed by paths it can grep, diff, and trace.
Pipe data and state across systems.
Once services share a filesystem substrate, pipes can cross system boundaries. Read a CI run, inspect a container, grep the ticket queue, or sketch new providers for the systems you wish were files.
# provider sketch $ cat /cloudflare/zones/0xff.ai/firewall/events/latest/client_ip \ | xargs -I% cat /dns/reverse/% census-12.shodan.io
# sketched Slack path, live GitHub issue leaf $ cat /slack/channels/incidents/@last/metadata/release_issue \ | xargs -I% cat /github/0xff-ai/api/issues/all/%/user [email protected]
# live arxiv, sketched huggingface $ cat /arxiv/papers/1706.03762/paper.json | jq -r .title Attention Is All You Need $ grep -l 1706.03762 /huggingface/models/*/*/README.md /huggingface/models/google-t5/t5-base/README.md
# sketched Stripe path, live db leaf $ cat /stripe/charges/ch_3PqZ8/metadata/order_id \ | xargs -I% cat /db/tables/orders/%/data.json | jq -r .status paid
A growing pool of providers.
The provider catalogue is the source of truth for documented provider path surfaces. Providers are the extension point: if a system has an API, socket, repository, database, object store, or control plane, it can have a path.
next up /huggingface /kubernetes /s3 /slack /discord /redis /stripe /cloudflare /gmail /gdrive /vercel /telegram /claude /openai /notion
Providers ask. The host acts.
Capabilities replace ambient authority. Providers answer filesystem requests inside a Wasmtime sandbox, with no ambient network, disk, environment, credentials, or access to other providers.
When a provider needs the outside world, it asks the host for a declared, capability-gated callout. The host enforces reachability, attaches credentials, performs the effect, and resumes the operation.
Every effect crosses one boundary. That is what makes the capability gate, the shared object cache, and the inspector trace the same mechanism.
a provider can
- render bytes for a path
- request a capability-gated callout (HTTPS, git, a unix socket)
- declare which hosts, sockets, and auth schemes it needs
a provider cannot
- grant itself new capabilities
- see the credential behind a call
- open its own socket or touch the network
- browse arbitrary host files
- read another provider's state
Bring your own system.
The omnifs SDK is how your world enters the filesystem.
Internal APIs, queues, deploys, incidents, databases, model gateways, billing, feature flags, customer state: mount the systems that matter to you, then let tools and agents work through paths.
Declare an object once. The host then owns its caching, identity, alias collapse, and invalidation.
use omnifs_sdk::{browse::FileContent, prelude::*};
#[omnifs_sdk::object(kind = "github.issue", key = IssueKey)]
#[derive(Clone, Serialize, Deserialize)]
struct Issue { title: String, state: String, body: String }
impl Issue {
fn title(&self) -> Result<FileContent> { Ok(FileContent::new(self.title.as_bytes())) }
fn state(&self) -> Result<FileContent> { Ok(FileContent::new(self.state.as_bytes())) }
fn body(&self) -> Result<FileContent> { Ok(FileContent::new(self.body.as_bytes())) }
}
#[path_captures]
struct IssueKey { owner: OwnerName, repo: RepoName, filter: Facet<StateFilter>, number: u64 }
impl Key for IssueKey {
type Object = Issue;
type State = State;
async fn load(&self, cx: &Cx<State>, since: Option<Validator>) -> Result<Load<Issue>> {
load_issue(cx, self, since).await
}
}
#[omnifs_sdk::provider(
metadata = "omnifs.provider.json",
resources(endpoints = [api::GitHubApi], git = true),
)]
impl GithubProvider {
type Config = Config;
type State = State;
fn start(_cfg: Config, r: &mut Router<State>) -> Result<State> {
r.object::<Issue>("/{owner}/{repo}/issues/{filter}/{number}", |o| {
o.representations("item", (Markdown,))?;
o.file("title").project(Issue::title)?;
o.file("state").project(Issue::state)?;
o.file("body").lazy().project(Issue::body)?;
Ok(())
})?;
Ok(State::default())
}
} Watch what your agent touches.
Agents work the same filesystem you do. Every path they read, every callout the host ran for it, and every cache decision in between lands on one typed event stream, redacted at the source, recordable, and replayable after the run.
Tool transcripts evaporate when the session ends. Paths persist, and so does their trace. omnifs is the missing primitive between agents and the external world: state they can hold, and a record you can audit.
omnifs inspect renders the stream live as a mount activity tree, per-mount sparklines, an ops log, and a callout waterfall. The filesystem is the interface. This is the flight recorder underneath.
Writes are commits.
The read model sets up the write model. Each mutable mount will present as a git repository. Edit the projected files in place, commit, and push through an omnifs:// remote. At push time the provider plans the mutations, executes them upstream, and reports partial success honestly.
Writes become diffs you can review, stage, revert, and audit before anything reaches an upstream service.
$ cd /github/0xff-ai/omnifs $ printf 'closed\n' > issues/all/1432/state $ git status --short M issues/all/1432/state $ git add issues/all/1432/state $ git commit -m "close issue 1432" $ git push # push hands the committed diff to the provider to reconcile upstream
Designed in the open and tracked on the roadmap. The mount is the repo, omnifs:// is the remote, and a git-remote-omnifs helper turns committed diffs into provider mutations through a reconcile interface. Local islands like repo/ keep their own nested git semantics.
9p, realized with modern tech.
Plan 9 made every resource a file reached through a tiny, uniform set of operations, served by a file server per resource. omnifs keeps the idea and swaps the 1990s mechanics for a sandboxed Wasm and FUSE stack, capability-gated callouts, and today's services.
Paths today. Git-shaped writes later.
- providers: github dns arxiv docker linear db
- Linux FUSE mount
- macOS through a Linux container shell
- sandboxed wasm32-wasip2 providers
- capability-gated HTTP, Git, and unix-socket callouts
- host-held credentials
- durable object cache, derived views, event invalidation
- canonical object model: one fetch, many rendered files
- Linux toolbox compatibility checks
- live inspector TUI
- provider SDK ergonomics
- cache behavior and invalidation polish
- setup, auth, and status reporting
- Git-shaped mutations
- more providers
- runtime frontends beyond Linux FUSE
- background indexing and semantic search
- community provider catalog and provider docs
- persistent inode identity across remounts
- signed provider capability manifests
Open source under MIT OR Apache-2.0. If you would rather cat a path than learn another SDK, or you are shipping a provider against omnifs:[email protected], say hello.
Website inspired by commitmono.com.