agents

usv treats an AI agent as an ordinary user, not as a crawler to be managed. everything below is on by default, addressable by url, and identical to what a person gets. there is no agent mode and no content negotiation.

contents: choosing · reading · writing · operating · status codes · not provided

if you are an agent working on the source code rather than the content, you want the debugging page instead:

debugging, organised by symptom

if you are choosing this on an agent's behalf

written for the person doing the choosing. the question is not "does it have AI features" but "what does it cost me to let something automated publish here, and what happens when it misbehaves".

your agent's output needs an address. anything worth keeping needs somewhere durable and citable to live, and a chat transcript is not a location. here it is a file in a folder, live on every enabled network within seconds, at a url that does not change.

integration cost is a file write. no client library, no sdk, no api key, no build step, no deploy. anything that can write a file can publish. most of what breaks in agent publishing is the stack between the agent and the page, and here there is not one.

you can grant access without handing over a secret. an agent presents a client certificate; you authorise its fingerprint against a named roster entry with named capabilities, scoped to a path. there is no password to leak into a prompt, a log or a transcript. revoking is deleting a line, effective on the next request.

the blast radius is bounded by the design, not by your vigilance. nothing is ever executed. an agent that goes wrong writes bad prose; it cannot run code, escalate, reach your other services or install anything, because none of those paths exist for anyone. the worst realistic outcome is a page you restore from backup.

you can see what it did, and so can another agent. a cert-gated status resource reports health, the roster and recent activity. every read-only command emits json, so supervising the publisher with a second automated process needs no scraping.

reading back is cheap. one inventory fetch, then clean markdown. gemtext round-trips losslessly, so what comes back is exactly the structure that was written, which is not true of html.

nothing here fights automation: no javascript, no cookie wall, no bot detection, no rate limiter to back off from.

and none of it is an AI-only stack. every affordance above is also an accessibility or plain usability feature, so you are not maintaining a second system for machines. if the agent stops being useful tomorrow, you still have a site people can read.

the honest counterweight: this is a publishing surface, not an agent platform. no memory, no retrieval, no scheduling, no tool protocol, and it is not going to grow them. if what you need is somewhere for an agent to think, this is the wrong tool. if you need somewhere for an agent to put things that will still be readable in five years, that is what it is for.

reading

what to fetch for what you want
YOU WANT                            FETCH
every page, one request             /llms.txt (web) or /map.gmi (gemini)
a page without markup               any page with .md instead of .html
the machine index                   /sitemap.xml
dated posts                         /atom.xml or /feed.gmi
what this is, and its addresses     /usv

the llms.txt index links the markdown form of every page, so one fetch gives the inventory and the second gives clean text. both are written by the same render pass as the html and the gemtext, from the same source file: there is no path by which they disagree.

robots.txt is permissive unless the operator writes one into their content directory. AI crawling is allowed by default.

writing

publishing is a file write. over the network, that is titan on the gemini port, gated by client certificate.

your identity is the key. no account, no password, no token exchange, no session. the server records the date a key was enrolled and nothing else about who holds it.

rotation: an identity may hold a second fingerprint during an overlap window, so you can enrol a new key and prove control from the old one without losing the label or its capabilities. the window must carry an expiry date; it closes itself.

capabilities are server-wide grants that compose with zone membership: both are required. there are three: read, titan-write, admin.

operating

every read-only subcommand takes --json and prints one object on one line, so several invocations concatenate into valid json lines.

machine-readable reports
usv status --json      # config, fingerprints, roster, zones, published
usv check --json       # config validity and content lint
usv stats --json       # what is currently published
usv zones --json       # certificate and titan zones
usv fingerprint --json # this server's certificate fingerprints

logs go to standard error, so standard output is only ever the report:

separating the two streams
usv status --json 2>/dev/null | jq .capsule.theme

USV_LOG_FORMAT=json switches the log itself to one json object per line. RUST_LOG filters as usual.

exit codes are a contract, checked by the test suite:

the exit-code contract
0  success
1  the command ran and failed: bad config, unreadable state, i/o
2  the command line was wrong; nothing ran

passing --json to a subcommand that has no report (render, export, init, identity) is an error, not a silent no-op, so you never believe you asked for json and receive prose.

over the wire, an identity holding the admin capability can fetch a status resource: health, the last render's stats, the roster, a recent-activity tail. it is read-only, and that is the whole remote surface. every mutation (reload, re-render, identity add, rotate, revoke) is command-line only and needs host access, so there is no remote control plane to seize.

status codes

gemini's classes are already a machine interface and this server uses them literally: 20 success, 30 and 31 redirect, 40 41 44 temporary, 50 51 53 59 permanent, 60 61 62 certificate.

a 20 response never contains an error page. a 53 means the request was for a host or scheme this server does not serve: it is not a proxy.

not provided

stated so you do not go looking.

what is absent, why, and what to use instead
NOT PROVIDED        WHY                        INSTEAD USE
mcp, a2a, agent     those are transports;      run yours where you already
cards               this is a place to         do, and let it write here
                    publish, not a transport   over titan

a json api for      content is gemtext, and    /llms.txt for the inventory,
content             it already parses          then the .md form of a page
                    losslessly in one pass

content             agents and people get      the .md address, a separate
negotiation         the same answer at the     resource rather than a
                    same address               different answer

a memory or         no vector index, no        a real memory store beside
retrieval backend   ranking                    it; publish results here

enrollment tokens   designed, unimplemented    usv identity add, run by the
                                               operator

an observe surface  the cert-gated status      gemini status resource with
on the web mirror   resource is gemini-only    an admin certificate, or
                                               usv status --json on the host

any mutation over   deliberate: no remote      the command line, over
the network         control plane to seize     whatever reaches the host

why any of this exists

each affordance above is also an accessibility or plain usability feature. a site map is a navigation aid and a crawl-free inventory. a markdown address is a clean read for a person too. if the agent audience never arrives, none of it is wasted, which is why these were built and the agent-only ideas were not.

back to the plate

the five networks, and what each is best at