🤖 Agents
An agent in a Rastrillo app always acts for a person. Paul makes a token for Claude, Claude calls the app's JSON API with it, and the app records each call as "Claude for Paul". An agent cannot act for anyone else, and it cannot act as itself.
Tokens are made for a named agent
A person makes a token on the app's token page and names the agent it is for: "Claude", "Codex", "Nightly import". The name is 1 to 40 letters, digits, spaces, hyphens and dots, and it is shown as typed.
The name is a label. The token's public ID is what tells two tokens apart, so two tokens both named "Claude" are still two tokens.
A token for a script with no AI in it is a deliberate choice: the person ticks "This token is for me, not an agent". Calls with that token act as the person, with no agent named.
See Tokens for the page itself.
Actor says who is calling
Every operation gets a rastrillo.Actor on Ctx.Actor:
| Field | Holds |
|---|---|
Subject |
the person: the session's subject, or the token owner's |
Agent |
the agent's name, or empty when the person acts directly |
Credential |
the token's public ID, or empty for a browser |
Access |
Read or Write: what the token allows, and Write for a browser |
Session |
the agent's Agent-Session header, when it sent one |
Harness |
the agent's Agent-Harness header, when it sent one |
Grant |
a connection service's grant: its ID, issuers, audience, teams and scopes; nil for a browser and a personal token |
Subject, Agent, Credential and Access come from the verified
token, never from the request. Session and Harness are what the
agent says about itself. They are recorded as its claims, and only for a
token made for an agent.
The zero Actor is the app itself: a job, a sidecar, a migration.
What the audit trail records
Actor.String() is the short form. eventlog stores it on every event
it appends:
| Who | String() |
|---|---|
| the app | app |
| a person in a browser | user:42 |
| a person's own token | user:42 token:k3Jx9QpL2mWa |
| an agent | agent:Claude for user:42 token:k3Jx9QpL2mWa |
Events written before this release say human or agent:<name>. If
your code compares an actor string with "human", change it.
Actor.LogAttrs() gives the same as slog attributes, plus
agent_session and agent_harness when they were sent. The API logs
them on every request.
Showing it on a page
In a template, {{actor $L .Actor}} writes "Paul" for a person, "Claude
for Paul" for an agent, and nothing for the app. It is plain text: style
it yourself.
Set ui.ActorName once at startup to turn a subject into a name.
Without it, the page shows the subject. In Go, rastrillo.ActorPhrase
builds the same words. See ui.
Coming later
Three later pieces build on the API: an MCP endpoint, sign-in for agents with OAuth, and a generated skill that describes the app to an agent.
The sidecar
You can run scheduled or delivery-driven work outside a request:
opts.Sidecar = func(ctx context.Context) (time.Time, error) {
// one pass; return when the next pass is due
}
Options.Sidecar plus the sidecar run argv speak the platform's
sidecar contract, and Options.NextDue answers the activator's
GET /api/next-due scheduled-wake poll.
A pass returning a zero next-due time means no scheduled work, and the loop sleeps for a minute, the family's polling floor.
A sidecar never dies on a pass error. The platform would only respawn it into the same failure, so it backs off and retries, capped at five minutes so a dead dependency cannot turn the loop into a busy-wait.
Read this page as markdown — exact, unstyled, and cheap for an agent to fetch.