Mounting a workspace ๐งต¶
A workspace is exposed as one FUSE mount per kind, all served by one process:
~/.ai-resources/
sources.txt # the layer list โ never masked by a mount
agents/ # mountpoint
skills/ # mountpoint
commands/ # mountpoint
scripts/ # mountpoint
The mountpoints are the kind directories, one level below the target, which is
why sources.txt stays readable and editable while the view is live.
Serving ๐¶
Establishes all four mounts together and blocks while serving โ the mount lives exactly as long as the process. Ctrl+C unmounts cleanly.
Re-runs itself in its own session and returns once the view is ready, so a
shell profile or a service manager that gets exit 0 knows the workspace is
readable right now โ not "probably, shortly".
Serving /home/you/.ai-resources from 3 sources:
1. ~/ai/personal
2. $WORK/platform
3. ~/ai/project
Serving in the background. Stop it with: airfs umount --target /home/you/.ai-resources
Mounting is all-or-nothing: if any kind cannot be mounted, the ones already established are released, and you are left with the state you started in.
-s declares the layers inline, creating the target and replacing its
sources.txt with exactly that list before serving. It is how a throwaway
workspace becomes one command instead of a file to author first โ and it is
destructive to the file it replaces, so read
declaring-layers.md
before pointing it at a workspace you care about.
What gets refused, and why ๐ง¶
| Refusal | Why |
|---|---|
<dir> is not empty |
Mounting over a populated directory hides its contents, and hidden files are a trap. Move them out โ or, if they are a leftover copy of what you are about to merge, delete them. |
<dir> is already served |
The target is mounted. A second mount would stack a view on a view; airfs umount first. |
<dir> is held by a stale mount; unmount it first |
A previous serving process died. See below. |
All three exit 2.
Inspecting ๐¶
target /home/you/.ai-resources
agents served, 0 entries
skills served, 3 entries
commands served, 1 entry
scripts served, 0 entries
Exit 0 when the target is fully served; exit 2 when it is not โ nothing
mounted, only some kinds mounted, or a mountpoint gone stale. Each of those is a
condition to act on, and each is named in the output, so a shell profile can
branch on the code and a human can read the reason:
There is no PID file and no runtime directory. airfs asks the kernel what is
mounted, every time โ the kernel is the only thing that actually knows, and a
second record of it could only ever disagree.
Stale mounts ๐ง¶
If the serving process is killed without a chance to clean up โ kill -9, an
OOM kill, a crash โ the kernel keeps listing the mount, but every access to it
fails with ENOTCONN. It looks mounted and serves nothing:
This is the failure worth being able to name, because from inside the directory
it presents as "my tooling stopped seeing anything" with no obvious cause.
airfs status distinguishes it from a live mount, and airfs umount recovers
it โ lazily if it is still busy.
Releasing ๐งน¶
Released /home/you/.ai-resources/agents
Released /home/you/.ai-resources/skills
Released /home/you/.ai-resources/commands
Released /home/you/.ai-resources/scripts
Releases every kind under the target, stale ones included. If nothing was
mounted it says so and exits 0 โ unmounting nothing is not a failure, which is
what makes it safe to put in a teardown script.
Freshness ๐¶
The view caches nothing: no content cache, no directory cache, and attribute and lookup timeouts are all zero. Every read goes through to the real file in its repository.
So the loop that matters just works:
- Edit a file in its repository โ the next read through the view sees it.
- Add a whole new entry to a layer โ it appears in the listing.
- Add a layer to
sources.txtโ this one does need a remount, since the layer list is resolved when serving starts.
The cost is that every operation is served live rather than from a cache. For directories the size of a resource collection this is not measurable, and being able to trust what you are reading is the entire point.
Read-only, enforced ๐ก๏ธ¶
Writes through the view are rejected by the kernel, not by convention:
Modes reported through the view have their write bits cleared, and files are reported as owned by you, so tools that check before writing get a consistent answer instead of a surprise. Symlinks in a layer are served as symlinks.
Edit in the repository that owns the resource. A write that succeeded through the view would bypass that repository's git history, review, and tests.
Running it at login ๐¶
airfs provides no supervision of its own beyond --detach โ keeping a mount
alive across reboots is the host's service manager's job. A user unit is the
whole integration:
# ~/.config/systemd/user/airfs.service
[Unit]
Description=airfs merged AI resource view
After=default.target
[Service]
Type=forking
ExecStart=%h/go/bin/airfs mount --detach
ExecStop=%h/go/bin/airfs umount
Restart=on-failure
[Install]
WantedBy=default.target
Type=forking fits because --detach returns only once the view is ready, so
systemd's "started" and yours mean the same thing.