agent-yes · engineering

ay callback: let readers message your agent from any web page

Your agent just published a report. The person reading it has a question. Until now the answer was "find the console, find the right terminal, type there." Now the report page itself can carry a tiny widget that delivers the reader's message straight into that agent's terminal — through a signed, send-only, single-agent capability that expires on a date you chose, and never through your master token.

What it does #

ay callback mints a capability for one agent and prints a self-contained HTML snippet — one inline <script>, zero external requests. Paste it into any page your agent publishes (a status dashboard, a nightly report, a postmortem) and readers get a floating button:

$ ay callback --expires 7d
callback 3b6f23e7 → claude #46555 @ ~/ws/reports
expires:  2026-07-29T10:57:01.398Z  (7d)
endpoint: https://x1a2b3c.agent-yes.com/cb/cb1.eyJpZCI6…
revoke:   ay callback revoke 3b6f23e7

── embed snippet ──────────────────────────────
<script> … </script>

Here is what the widget feels like (demo only — this one talks to nobody):

Imagine this box is your agent's published report page…

When a reader hits Send, the message lands in the agent's terminal wrapped in an explicit "untrusted visitor" frame, and in its inbox. One way, by design — the visitor never sees the terminal.

The capability is a hash, not a key #

The obvious-but-wrong implementation is to put your daemon's serve token in the snippet. That token is a master key: whoever holds it can read every terminal, send to every agent, spawn and kill. Pasting it into a public page is game over.

So the snippet carries a capability token instead — three dot-separated parts, the last one an HMAC-SHA256 over the rest:

cb1 . base64url({ "id": "3b6f23e7",
                  "agent": "4d65a096e983",   // exactly one agent_id
                  "exp": 1785322621398 })    // hard expiry, unix ms
    . base64url(HMAC-SHA256(secret, "cb1." + payload))

The scope lives inside the signed payload. That has three consequences worth spelling out:

Attack Result
Edit the payload to point at another agent, or push the expiry out signature no longer verifies — the daemon answers 404
Delete the daemon's bookkeeping to "resurrect" an expired token expiry is in the token itself, not in local state — still 410
Steal the token from the page source you can do exactly what the page could already do: send rate-limited text to one agent, until the expiry the owner chose

The HMAC secret is minted on first use into ~/.agent-yes/.callback-secret (0600), deliberately separate from the serve token — rotating one never invalidates the other.

--expires is required. There is no "never" #

Every capability API eventually grows a default lifetime, and every default lifetime eventually becomes "forever, because nobody thinks about it." We refuse the default:

$ ay callback
ay callback: --expires is required (e.g. --expires 7d) —
an embed capability must carry an explicit lifetime

--expires takes 30m, 12h, 7d, 2w — and caps at 365 days. There is no --expires never and there never will be. The point is a moment of deliberate friction: whoever pastes a capability into a public page must answer "how long should this live?" while they still remember the page exists. When it lapses, the widget doesn't fail silently — it tells the reader the link expired and to ask the owner for a fresh one.

For everything before the deadline, there's the kill switch:

$ ay callback ls
3b6f23e7  6d left     → claude #46555 @ ~/ws/reports
$ ay callback revoke 3b6f23e7
revoked 3b6f23e7

A public route that leaks nothing #

POST /cb/<cap> is the daemon's only unauthenticated write route, so it is deliberately paranoid:

Two-way chat — the widget showing the agent's reply — is the obvious next step, and the design line is already drawn: the page will only ever see replies the agent explicitly addresses to that callback, never the raw terminal.

Try it #

$ npm i -g agent-yes
$ ay serve            # local daemon
$ ay expose …         # or put the daemon on a public URL first
$ ay callback --expires 7d --title "Ask the report bot"

Paste the printed snippet at the end of anything your agent publishes. Ship the report; keep the conversation.