Agent-First Files v0.1.0
The first release of Agent-First Files — one directory, read-only, in front of a person who is not at the machine holding it. A fixed root, refusals that cannot be told apart from absences, and a watch that costs nothing for the parts of the tree nobody is looking at.
An agent working on your machine already has ls, a file reader and a grep.
What it has no way to do is show you what is in a directory when you are not
there.
So it improvises. It flattens a tree into a chat message, or it writes a file server — with a port, a path traversal bug, and no idea what a symlink is. That improvisation is the thing this spore replaces, and it replaces only that: one root, read-only, over a contract, with a page attached.
The root is the boundary, and it is fixed first
One process, one root, fixed before anything is served. Every path is relative to it, and the upper bound on what a leaked URL can show is the root itself.
--root is required and has no default. A convenient “current directory”
becomes a home directory the first time somebody runs it after cd ~, and a
default that is sometimes catastrophic is not a default worth having.
Three rules keep a path inside the root, and each is there because of a specific way the obvious version fails:
- comparison is by path component, never by string prefix —
/srv/rootis a prefix of/srv/root-evilas text and is not one as a path; - containment is checked after canonicalization, so an intermediate symlink cannot smuggle a path out before the last component is examined;
- an excluded path, a path outside the root, and a path that was never there all answer identically. A refusal you can tell apart from an absence is an oracle for what exists.
A symlink whose target stays inside the root is followed, because a source tree
full of them has to remain browsable. One that leaves is listed, named, and
never opened — you should be able to see that something is there without it
being reachable. --no-symlinks follows none at all.
What the root does not promise
This is stated in the README, in the skill and here, because a boundary that oversells itself is worse than one that is honest about its edge.
Those three rules are checks on a resolved path, and the file is opened by that path a moment later. In between, anything that can write inside the root can replace a directory with a link pointing out of it, and the open lands on the other end. A file read notices afterwards — the opened file’s identity is compared with the resolved one, and a substitution is refused — but a directory listing has no equivalent check, and on Windows the identity comparison is not implemented at all.
Closing this properly means doing the whole walk against an open directory
handle: openat2 with RESOLVE_BENEATH on Linux, a component-at-a-time
openat loop on other unixes, handle-relative opens on Windows. Until that is
in place, read the boundary as: the root is the upper bound on what a leaked
URL can name, and it holds against anything that cannot write inside the root.
In practice: serve a source tree, a document directory, a build you own. Do not serve a directory an untrusted process writes into — an upload target, a shared scratch directory, someone else’s workspace.
It stays true while you watch it
A tree left open on a phone would otherwise become a slowly aging picture of a workspace. The window keeps a change stream open and re-reads exactly the directories that changed.
Watching follows the same rule the tree does: a directory is watched exactly
while somebody has it open. Nothing is recursive. A recursive watch over a
repository registers one watch per directory on Linux, which a node_modules
exhausts outright, and it makes the process do work for parts of the tree nobody
has looked at. Collapse a directory and it stops costing anything.
Two faces, one projection
The same projection is available to a program and to a person.
For programs there is a bearer-protected loopback HTTP API with a committed OpenAPI 3.2 contract and standalone schemas — listing, text reads and byte reads, under an explicit ceiling on any single read.
For people there is a trusted local window, hosted by Agent-First UI, whose page never renders workspace content as markup. What is in your files is data; it does not become part of the page.
Nothing here writes. There is no write path, and file contents are not redacted at any boundary — what you would see in an editor is what you see here.
Getting it
Homebrew and Scoop packages are not published for this spore yet.
$ cargo install agent-first-files --locked