A safe little home for your coding agents
Claude Code, Codex and T3 Code on a rented server, in a Docker container. The agents get sudo and a whole Linux machine; the container keeps them away from the server, your other devices and your backups.
gh and a comfy zshThe files: nickypro/.vps-dotfiles › agent-box. Its README has every detail; this page is the tour.
How it fits together
Your devices only talk to the server. It passes traffic to the container over its loopback address (127.0.0.1), so the container adds no new doors to the internet. Backups leave from the server, never the container.
T3 via Tailscale, HTTPS or SSH
/srv/agent-box
Backup job + its keys
Your home folder, data and logins live on the host, so make rebuild can replace the container without losing anything.
What keeps it safe
Assume that sooner or later an agent follows an instruction it shouldn’t. The setup tries to make that boring.
An agent goes off the rails
Capped memory, CPU and processes, and no Docker socket (that would be root on the server). The network guard keeps it to the internet.
Strangers on the internet
No new open ports. SSH is keys only, through the server. T3 sits behind Tailscale, a certificate or a password, plus pairing, or behind SSH alone.
Losing files
Files live on the host. Hourly encrypted backups go to Backblaze, locked for 14 days, with the job and its keys out of the agents’ sight.
A lost laptop or phone
Delete its key from authorized_keys, revoke its certificate, or drop it from your tailnet.
The network guard
A small nftables firewall on the host, in its own table so Docker and ufw leave it alone. The internet works; an attacker’s usual next steps don’t.
Allowed
Blocked
What it doesn’t protect
Every agent can read everything in the box: code, ~/.env, logins. Give it credentials you’d be fine losing: a GitHub token for just its repos, API keys with a spending limit. A container also shares the server’s kernel: use an isolated agent for work you trust less, and a VM for people you don’t.
Pick how you reach T3
T3’s web UI is a terminal with sudo, so who can reach it matters. Pick with ACCESS in config.mk; to switch, change it and rerun sudo make host-config.
tailscale
RecommendedOnly your own devices, over Tailscale, a free private network between your machines.
ssh
Experimental ♥ Author’s pickNo web UI. T3’s app connects over the same SSH as ssh agent-box and pairs itself. Experimental, but the author’s favourite: the least to set up, and no web page to find.
mtls
Client certificatesA public address like code.example.com, but the browser must show a certificate you issued (“mutual TLS”).
public
Last resortA public address with a generated password, rate limited, with fail2ban banning anyone who keeps guessing.
Set it up, step by step
About an hour. The commands are in the dropdowns; this guide uses sudo make … throughout.
- 1
Prepare the server on the server
Ubuntu or Debian with 4 GB of RAM or more, and Docker Engine with its compose plugin. Then a few tools, a firewall, key-only SSH and some swap.
Commands
sudo apt install -y git make fail2ban nftables jq sudo ufw allow OpenSSH sudo ufw allow 80,443/tcp # only for the mtls or public modes sudo ufw enable # keys only (check your key logs you in first) printf 'PasswordAuthentication no\nKbdInteractiveAuthentication no\n' \ | sudo tee /etc/ssh/sshd_config.d/10-keys-only.conf sudo systemctl reload ssh # swap, so a busy agent has room sudo fallocate -l 8G /swapfile && sudo chmod 600 /swapfile sudo mkswap /swapfile && sudo swapon /swapfile echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab - 2
Get the files on the server
Clone the repo, make the storage and list your SSH keys. For a mode other than
tailscale, setACCESSinconfig.mknow (andDOMAIN, for mtls or public).Commands
git clone https://github.com/nickypro/.vps-dotfiles cd .vps-dotfiles/agent-box sudo mkdir -p /srv/agent-box/{home,data,cells} sudo chown 1000:1000 /srv/agent-box/{home,data} # uid 1000 = the dev user inside cp authorized_keys.example authorized_keys nano authorized_keys # your SSH public keys, one per lineMemory and CPU caps are in
docker-compose.yml; the defaults suit a 4-core, 8 GB server. - 3
Build it and fence it in on the server
About 10 minutes. Then
net-testshould show the cloud metadata service and the host’s SSH blocked, and the internet and DNS open.Commands
sudo make rebuild # build the image and start the container sudo make guard-link # the firewall that keeps the container off the host sudo make net-test # what the container can and can't reach sudo make doctor # checks everything that has broken before - 4
Turn on the web UI (or don’t) on the server
Open the one for your mode.
tailscale recommended
curl -fsSL https://tailscale.com/install.sh | sh sudo tailscale up sudo make host-config # runs tailscale serve: T3 on https, your tailnet only- In Tailscale’s admin console, turn on MagicDNS and HTTPS Certificates (DNS page), and disable key expiry for this machine.
- Machine names show up in public certificate logs, so pick a dull one.
- Never
tailscale funnel: it publishes to the whole internet. - If other people share your tailnet, add an Access controls rule so only you reach this machine.
ssh no web UI, experimental
Set
ACCESS = ssh. Nothing to install; this only removes what another mode left serving T3. Skipmake pair: the app pairs itself in step 7.sudo make host-config # no nginx site, no tailscale servemtls client certificates
Set
ACCESS = mtlsandDOMAINinconfig.mk, put the domain innginx/agent-box-mtls.conf, and point its DNS at the server.sudo apt install -y nginx certbot python3-certbot-nginx sudo certbot certonly --nginx -d code.example.com sudo make ca # your own certificate authority, once sudo make cert NAME_ARG=laptop # makes laptop.p12 and prints its password sudo make web-passwd USER_ARG=me # optional: a password for devices without a cert sudo make host-configImport the
.p12on each device, then delete the server’s copies. Keep the CA key (/etc/nginx/agent-box/ca.key) in exactly one place.public password only
Set
ACCESS = publicandDOMAIN, put the domain innginx/agent-box-public.conf, and point DNS at the server.sudo apt install -y nginx certbot python3-certbot-nginx sudo certbot certonly --nginx -d code.example.com sudo make web-passwd USER_ARG=me # prints a generated password sudo make host-config - 5
Connect from your laptop on your laptop
Add two hosts to
~/.ssh/config, thenssh agent-box. The box only listens on the server’s loopback, so you jump through the server.The config
Host myvps HostName your.server.address User you Host agent-box HostName 127.0.0.1 Port 2222 User dev HostKeyAlias agent-box ProxyJump myvps ForwardAgent noHostKeyAliasfiles the box’s key under its own name, so it doesn’t clash with anything else on port 2222.ForwardAgent no: every agent has sudo, so a forwarded agent would let any of them log in as you elsewhere. The box refuses it too.
- 6
Log the agents in in the box
Log in to
claude,codexandghonce; logins survive rebuilds. For GitHub, prefer a fine-grained token for just the agents’ repos. API keys go in withmake secret.Commands, and more Claude accounts
ssh agent-box claude # log in to Claude Code codex login gh auth login # or: gh auth login --with-token, with a fine-grained token sudo make claude-account ACCOUNT=work # on the server: a second Claude login sudo make claude-account ACCOUNT=main LABEL=Me # give your first one a name tooEach extra account gets its own name and colour in T3, its own login and history, and a
claude-workcommand. They share your notes and skills in~/.claude, and every agent starts withbox-notes.md, which explains the box to it. - 7
Open T3 any device
In a browser (tailscale, mtls or public):
sudo make pair LABEL=phoneprints a pairing link, good for 30 minutes, to open on that device.In the T3 desktop app (any mode): add
agent-boxas an SSH connection. It finds the running server and pairs itself, and every device sees the same threads.On Android (experimental): an unofficial T3 build with SSH, by the box’s author and proposed upstream. On an iPhone there’s no SSH app yet, so use a browser mode.
Setting up the Android app
It makes its own key (Settings › SSH). Add that to
authorized_keysnext to the Makefile, and to~/.ssh/authorized_keyson the server, limited to the jump:restrict,port-forwarding,permitopen="127.0.0.1:2222",command="/bin/false" ssh-ed25519 AAAA… t3code-androidPaste the two
Hostblocks from step 5 into the app, then Add environment › Or connect over SSH ›agent-box. Check the host keys it shows withssh-keygen -lfon the server:/etc/ssh/ssh_host_ed25519_key.pub, thenssh_host_keys/ssh_host_ed25519_key.pubnext to the Makefile. - 8
Turn on backups Backblaze + server
Optional: about 20 minutes, mostly in Backblaze’s web UI. How it works.
The bucket in Backblaze’s web UI
- Sign up for B2. The signup form is an iframe that ad blockers can hide; if the page looks empty, turn the blocker off for it.
- Create a bucket: Private, with Object Lock enabled. No default retention; Kopia sets it on each file.
- Lifecycle settings, custom rule:
{"fileNamePrefix":"","daysFromHidingToDeleting":15}. Pruned files are only hidden; this deletes them 15 days later so they stop costing money. - App keys: add one for this bucket only, with read and write access. Note the keyID and applicationKey (shown once) and the bucket’s endpoint,
s3.<region>.backblazeb2.com.
The repository as root on the server
Install kopia, then in a root shell (
sudo -i):mkdir -p /root/backup && chmod 700 /root/backup cat > /root/backup/b2.env <<'EOF' B2_KEY_ID=<keyID> B2_APP_KEY=<applicationKey> KOPIA_PASSWORD=<long and random: openssl rand -hex 32> # Optional alerts, see below: HC_URL=https://hc-ping.com/<uuid> EOF chmod 600 /root/backup/b2.env set -a; . /root/backup/b2.env; set +a export AWS_ACCESS_KEY_ID="$B2_KEY_ID" AWS_SECRET_ACCESS_KEY="$B2_APP_KEY" kopia repository create s3 --bucket=<bucket> --endpoint=s3.<region>.backblazeb2.com --region=<region> \ --retention-mode=COMPLIANCE --retention-period=14d --description="agent-box backup" kopia maintenance set --extend-object-locks true kopia policy set --global --keep-latest 10 --keep-hourly 48 --keep-daily 30 --keep-weekly 8 --keep-monthly 12 --keep-annual 2 \ --compression zstd-fastest --add-ignore ".cache/" --add-ignore "node_modules/" --add-ignore "__pycache__/" --add-ignore ".Trash*/" kopia policy set /root --add-ignore "/backup/" --add-ignore "/.config/kopia/" kopia policy set "root@$(hostname):/agent-box-container" \ --add-ignore "/tmp/" --add-ignore "/var/tmp/" --add-ignore "/var/cache/" --add-ignore "/root/.npm/" kopia policy set /etc --add-ignore "/nginx/agent-box/" # mtls: keep the CA key single-copy cd /path/to/.vps-dotfiles/agent-box make backup-link # script + systemd units, hourly timer on make backup-now # the first run can take hoursPut the repository password in your password manager now. It’s the only way to decrypt the backups, and
b2.envisn’t backed up.For
HC_URL, a free healthchecks.io check (1-hour period, a few hours’ grace) emails you when backups fail or stop, or a disk passes 90%.
Isolated agents
Every agent in the box is you: dev, with sudo, your logins and every project. For work you trust less (a stranger’s repo, a scraper, someone else’s project), make an isolated agent, a “cell”: its own Linux user and folder, as two more providers in the same T3.
~/.env
T3’s own tools
Linux user
/cells/web, plus folders you share
its own logins + keys
the internet; local ports 20000–29999
Make one
1Create it
On the server: sudo make cell CELL=web. That adds the user, its folders and its two providers in T3.
2Give it a login
Its own, never yours: cell web claude auth login inside the box, or a limited API key with sudo make secret KEY=ANTHROPIC_API_KEY CELL=web.
3Use it in T3
Add the project /cells/web/work and pick Cell web for its threads. Your own providers refuse to start there, on purpose.
Sharing folders, and the other cell commands
A shared folder appears at /cells/web/shared/; the cell sees it and nothing around it.
sudo make cell-share CELL=web DIR=/data/papers # read-only
sudo make cell-share CELL=web DIR=/data/out RW=1 # read-write
sudo make cell-unshare CELL=web DIR=papers
sudo make cells # cells, their logins, what's shared
cell web import ~/projects/thing # inside the box: give it its own copy
cell web # inside the box: a shell as the cell
What a cell can and can’t do
/tmp, where your agents leave files~/.env, T3’s settingsyesno; it has its ownThe traps it closes
Another user is the easy part. The work is in the places where your account would still run something the cell wrote:
T3’s own tools
With them an agent can start threads on your unisolated providers. The guard blocks cells from every port in the box, and cells won’t start without it.
Git
A repo can make git run programs, and T3 runs git constantly. In a cell’s folder git runs as the cell; failing that, git refuses it as “dubious ownership”.
Your agents in its folder
A project can run commands as an agent starts (Claude’s hooks), so your own Claude and Codex refuse to start in /cells.
You
T3’s terminal in a cell’s folder is still your shell, and the prompt says so. cell web gives you the cell’s.
What it doesn’t do
It’s the same container: same kernel, memory cap and internet. Cells can reach each other on ports 20000–29999, and T3 can’t make worktrees for their projects (use Local). Claude’s and Codex’s own sandboxes don’t work in Docker; cells do that job here.
A real browser, a desktop, Synthesizer V, keys
Optional, baked into the image: set EXTRAS = desktop wine scraping (or some of them) in config.mk, then sudo make rebuild. All three add about 4 GB.
A desktop in your browser EXTRAS=desktop
For sites that block headless browsers, logins, or watching what an agent does: a private desktop with sound, in your own browser. Agents drive its Chromium; you see every click.
How to use it
desktop start # inside the box: prints a link to open
desktop browser # Chromium on it, for agents (port 9222)
claude mcp add browser -s user -- npx -y @playwright/mcp@latest --cdp-endpoint http://127.0.0.1:9222
desktop stop # gives the memory back
- Private. Its one port is on 127.0.0.1, reached through SSH, and only opens with the two random tokens in the link.
- Plain browsing needs no desktop:
claude mcp add playwright -s user -- npx -y @playwright/mcp@latest --headless --no-sandbox --executable-path /usr/bin/chromium. Cells can too. - Not a VM, because most VPSes lack
/dev/kvm. Chromium runs without its own sandbox here, so don’t log in to anything you wouldn’t hand the agents.
Synthesizer V Studio 2 and Kasane Teto EXTRAS="desktop wine"
Synthesizer V 2 has no Linux version, so it runs under Wine on the desktop above, following mito551/synthv2-linux. Agents write the song (MIDI, or an .svp project, which is JSON) and click Render.
Setup and caveats
/opt/agent-box/extras/synthv/setup # ~10 minutes, ~2.5 GB
synthv # starts it on the desktop
- Log in once. If the dialog then spins forever, press Escape; under Wine it doesn’t close itself.
- One device at a time. The licence moves to whichever machine logged in last.
- Teto’s 7-day trial sings but can’t export, so you listen through the desktop’s sound.
Scraping tools EXTRAS=scraping
In /opt/scrape (Python at /opt/scrape/bin/python), command-line tools on PATH: curl_cffi, trafilatura, yt-dlp, Camoufox, Playwright and markitdown.
What each one does
curl_cffiPlain HTTP that looks like a real Chrome to anti-bot checks (impersonate="chrome").
trafilatura -u URLA page’s main text, without menus, ads or cookie banners.
yt-dlpVideo, audio and subtitles from most sites, with ffmpeg.
camoufox fetchCamoufox: Firefox built to pass bot detection. Run fetch once per user.
playwrightBrowser automation from Python; connect_over_cdp drives the desktop’s browser.
markitdownPDFs, Word files and slides turned into Markdown for the agents.
Many sites block data-centre addresses whatever the browser. For a home connection, use an SSH SOCKS proxy through a machine at home: ssh -N -D 127.0.0.1:1080 home, then --proxy socks5://127.0.0.1:1080.
camofox-browser, a stealth browser for agents
camofox-browser is a Camoufox server for agents: each page comes back as a tidy outline with stable references to click, through its own MCP. By default it listens on every interface and sends crash reports; these settings stop both and require a key:
echo "CAMOFOX_ACCESS_KEY=$(openssl rand -hex 16)" >> ~/.env # the key, kept for restarts
tmux new -d -s camofox 'set -a; . ~/.env; CAMOFOX_BIND_HOST=127.0.0.1 CAMOFOX_CRASH_REPORT_ENABLED=false npx -y @askjo/camofox-browser'
. ~/.env && claude mcp add camofox -s user -e CAMOFOX_BASE_URL=http://127.0.0.1:9377 \
-e CAMOFOX_ACCESS_KEY="$CAMOFOX_ACCESS_KEY" -- npx -y @askjo/camofox-browser-mcp
API keys
One command asks for a key without showing it, so it stays out of your shell history and process list:
For your agents
sudo make secret KEY=OPENAI_API_KEY
Saved in ~/.env, read by your shells and by the agents T3 starts (after sudo make t3-restart).
For one cell
sudo make secret KEY=OPENAI_API_KEY CELL=web
In that cell’s own ~/.env. Cells never see yours.
Anything an agent can use, it can read, and a web page can talk it into leaking it. Use separate keys for the box and each cell, each with a spending limit.
Day to day
sudo make statusContainer, T3, web access and fail2ban at a glance.
sudo make doctorChecks everything that has broken before, and says how to fix it.
sudo make updateLatest Claude Code, Codex and T3, installed in place, then T3 restarts. Quick.
sudo make rebuildFresh base image (security fixes) and latest CLIs, then a new container. Nothing in your home folder or /data is lost.
More commands: T3, devices, keys, accounts, cells, backups
sudo make t3-restartRestarts just T3. SSH sessions stay up.
sudo make pair LABEL=tabletA pairing link for one more device.
sudo make cert NAME_ARG=tabletmtls: a client certificate for a new device.
sudo make cert-revoke NAME_ARG=tabletmtls: refuse that certificate from now on. make client-cert-list shows which are revoked.
sudo make backup-statusIs a backup running, when’s the next, the latest snapshots.
sudo make net-testWhat the container can reach, and what a cell can. Run it after any firewall change.
sudo make secret KEY=NAMESave an API key for the agents, typed hidden. Add CELL=web for one cell’s own.
sudo make claude-account ACCOUNT=workOne more Claude login, as its own provider in T3. make claude-accounts shows who’s logged in where.
sudo make cell CELL=webA new isolated agent. make cells lists them, make cell-rm removes one (its files stay).
sudo make cell-share CELL=web DIR=…Show a cell one of your folders, read-only unless RW=1. cell-unshare takes it back.
To give or take away SSH access, edit authorized_keys next to the Makefile; no rebuild needed. make help lists everything.
Backups, in plain words
Every hour
The host snapshots your files, its settings, and whatever was installed inside the container. Caches and node_modules are skipped.
Kopia
Splits files into chunks, stores each chunk once, and encrypts everything. An hourly run only uploads what changed.
Backblaze B2
Object Lock in compliance mode: no one can delete a file until its 14-day lock runs out. Kopia keeps extending the locks on files still in use.
How far back you can go
Snapshots kept at any moment (each mark is one), on a stretched time scale. Every snapshot is complete on its own.
Show as a table
| Kept | How many | Reaches back |
|---|---|---|
| Latest | 10 | 10 hours |
| Hourly | 48 | 2 days |
| Daily | 30 | 30 days |
| Weekly | 8 | 8 weeks |
| Monthly | 12 | 12 months |
| Yearly | 2 | 2 years |
What if someone takes over the server and deletes the backups?
They delete everything. B2 only hides locked files.
healthchecks.io emails you: the hourly backups stopped arriving.
Unhide the files in B2’s web UI (“Show all versions”) and restore.
The locks run out and B2’s cleanup rule removes them for good.
That’s why the alert matters: the lock buys you two weeks, but only if you notice. The job and its keys live on the host, so agents in the box can’t do this in the first place.
Getting files back
sudo -i
kopia snapshot list --all # find a snapshot ID
kopia ls -l <snapshot-id>/home/.claude # browse it
kopia restore <snapshot-id>/home/some/folder /tmp/restored
kopia mount <snapshot-id> /mnt/kopia & # or browse it as a folder
On a new server: install kopia, recreate b2.env with a new B2 key and the same repository password, kopia repository connect s3 to the same bucket, then kopia maintenance set --owner=me so the locks keep being extended. The README has the exact commands.
When things go wrong
The web UI shows “502 Bad Gateway”
T3 isn’t listening where Docker can reach it. The usual cause: the T3 server in tmux died and the T3 desktop app started its own private one in its place. Run sudo make doctor, then sudo make t3-restart.
Agents started from T3’s app don’t see my API keys
The T3 server in tmux had died, so the app started a server of its own. Your threads are all there, but that server doesn’t load ~/.env or the isolated agents’ guards. sudo make doctor spots it; sudo make t3-restart puts the box’s own server back.
The web UI loads but stays empty
T3 runs over a WebSocket. If you wrote your own nginx config, copy the Upgrade/Connection lines from the ones in nginx/.
The build fails at the npm step, or T3’s terminals don’t open
Since version 0.1, the t3 npm package only downloads T3’s real program into ~/.t3/runtime the first time it runs, terminals included. An older Dockerfile that checked for T3’s node-pty folder now fails to build: pull the latest files. If terminals still don’t open, sudo make doctor checks T3’s runtime; sudo make t3-restart fetches it again.
A cell says “the network guard isn’t on”
Cells refuse to start without their firewall rules, because without them a cell could reach T3’s own tools. Run sudo systemctl restart agent-box-guard (the first time: sudo make guard-link), then sudo make net-test.
“This folder belongs to the isolated agent”
You picked one of your own providers in a cell’s project, and it refused, as it should. Pick the cell’s provider (“Cell web”) for that thread, or in a shell run cell web claude.
A cell’s dev server can’t be reached
Cells can only use local ports 20000–29999 (every other port in the box is blocked for them), so npm run dev -- --port 20000. The note in each cell’s own instructions file tells its agents the same.
ssh agent-box says “Permission denied (publickey)”
Your key isn’t in authorized_keys next to the Makefile. That’s the only place the box reads keys from; ~/.ssh/authorized_keys inside it is ignored on purpose.
SSH warns “REMOTE HOST IDENTIFICATION HAS CHANGED”
It shouldn’t, even after a rebuild, because the container’s host keys are kept in ssh_host_keys/. If you see it, find out why (was that folder deleted?) before you accept the new key.
make net-test says DNS is BROKEN
Your provider’s DNS server is in a range the guard blocks (some clouds put it at 169.254.169.253 or in 10.0.0.0/8). Add dns: [1.1.1.1, 9.9.9.9] to the service in docker-compose.yml and run sudo make up.
A device says the .p12 password is wrong
Some older devices can’t read OpenSSL 3’s newer encryption. Export the same .key and .crt again with openssl pkcs12 -export -legacy ….
The backup says “repository is not connected”
Kopia finds its settings through $HOME. The service file sets HOME=/root; for commands by hand, use a root login shell (sudo -i).
I closed port 22 and now I can’t get in
Use your provider’s web console. The README’s section on moving SSH onto Tailscale says to test both ssh myvps and ssh agent-box over the tailnet before closing the port.
What changed in October 2026
All 15 changes this month
- Security
Keys come only from the host’s file. The box used to read
~/.ssh/authorized_keysinside the container too, so an agent could quietly add its own key, and removing one from the host file didn’t necessarily revoke it. - Security
No SSH agent forwarding. Refused by the box, and switched off in the suggested client config.
- Security
Client certificates can be revoked with
make cert-revoke. Before, a lost device’s certificate worked until it expired, or until you replaced the whole CA.make certalso refuses to reuse a name, which would lose the record needed to revoke the old one. - Security
Rebuilds are actually fresh.
make rebuildnow pulls the latest base image and CLIs; Docker’s cache used to keep months-old versions, and could even undo an in-place update. - Security
The network guard covers IPv6 private ranges too, in case IPv6 is ever turned on for the container.
- Usability
make updatereplaces a manual npm command that was missing the flag T3’s terminals need. - Usability
make pair LABEL=…, and notes on the T3 apps, shared tailnets, scoped credentials and DNS. - Security
Isolated agents.
make cellgives an agent its own Linux user and folder, picked from the same T3, with the git, tool and start-up traps closed (section 05). - Security
The guard re-applies itself every time the container starts. Before, a restart or recreate left it aimed at an old network until
make upran. - Usability
A desktop, a real browser, Synthesizer V and scraping tools, as optional extras (section 06).
- Usability
make secretsaves API keys without echoing them, and agents started by T3 now get your~/.envtoo, not just your shells. - Usability
More Claude accounts.
make claude-accountlogs in another account and adds it to T3 with its own name and colour; all accounts share your notes and skills. - Usability
Notes for the agents. Claude and Codex now start knowing how the box works, so they stop installing things that a rebuild wipes, or chasing the firewall as if it were a bug.
- Usability
Builds work with T3 0.1. The new
t3package no longer ships the folder the old Dockerfile checked, so a fresh build failed. - Usability
No web UI at all, if you like. With
ACCESS = ssh, T3’s app connects over SSH and pairs itself. Experimental, but the author’s favourite (section 03).