feat(seo): add custom agent HTTP API guide - #1409
Conversation
lilyshen0722
left a comment
There was a problem hiding this comment.
Gate: PASS at 1820f33d89c74d5ae391b626b781b6d0daeb9f5c, with one suggestion. I checked this as an API contract rather than as copy, because a public guide that teaches a wrong field is the same defect class as a documented event type with no producer — just with external readers.
The contract is accurate
| Guide teaches | Backend |
|---|---|
GET /api/agents/runtime/events |
router.get('/events') :447, mounted at /api/agents/runtime (server.ts:218) |
POST /api/agents/runtime/events/:id/ack |
router.post('/events/:id/ack') :1157 |
GET /api/agents/runtime/pods |
router.get('/pods') :2762 |
x-commonly-agent-token header |
agentRuntimeAuth.ts:45-50 — accepts Authorization: Bearer or this header |
Note it teaches the non-/bot variants, which is right for a third-party runtime — /bot/events exists alongside them and would have been the easy wrong answer.
payload.deliveryId is correct, and I expected it not to be. There are two emit sites with genuinely different shapes: agentEventService.ts:1178 puts deliveryId as a sibling of payload on the WebSocket push, while :1320 nests it inside enrichedPayload. The guide teaches polling, and the polling path is list(), which is the :1320 shape — so payload.deliveryId is right for this guide's audience. The sibling-level one is explicitly not a delivery; its own comment says the push is a wake signal and "a WS driver still fetches through list(), and that claim is what hands it the deliveryId to ack with."
Getting that right is the load-bearing detail in the whole guide, and "do not invent a deliveryId for an older event that has none" is exactly the correct instruction — acknowledge() logs a nonce-less ack as a pre-D6 driver rather than failing, so an invented one is worse than an absent one.
Also checked
- No infra identifiers — grepped the added content for registry hosts, project IDs, cluster names, ops accounts: clean.
guides.jsonparses, 22 entries, no duplicate keys, new slug present, and the generator test references it in 3 places.V2Login.test.tsx— the21 → 22bump is a real route-level assertion against the rendered index, not a self-counting one; it would catch a guide that fails to render.
Suggestion (non-blocking)
The guide never mentions x-commonly-client. The ack route reads it at :1121/:1178 to identify the acking consumer, and ADR-026 D6 Phase B enforcement is keyed on that name specifically so a migrated client can enforce while a parked one keeps working. An agent built from this guide sends nothing, so it lands in 'unknown'.
That is safe today — 'unknown' "is never enforced unless the operator sets true/*" — but it is the one thing in the flow where following the guide exactly leaves the reader in the bucket an operator is most likely to sweep. Since the guide already teaches the hard half (echoing the nonce), one line telling readers to declare a client name would make their agent eligible for enforcement rather than exempt-until-suddenly-not.
Good guide. Approving on substance; take the suggestion or leave it.
lilyshen0722
left a comment
There was a problem hiding this comment.
Re-gate at 2184f95ec8add388f8a6417c00eb402afaad9d65 — the + fix is correct; a wrong endpoint underneath it is not
Previous gate 1820f33d; analysed_sha == headRefOid asserted before reading. Delta is 3 insertions / 3 deletions in frontend/src/content/guides.json.
The fix in this delta is right and complete. Stray + characters — pasted in from a diff — were rendering as literal shell lines, so a reader copying the block would run + -H "Authorization: ...". Verified:
- the file still parses as JSON
- I swept every
codeblock in the file, not just the three touched: 0 remaining lines that begin with+
But one of the three URLs in these blocks does not exist. The third block teaches a custom-agent developer to post a message:
curl -X POST \
-H "Authorization: Bearer cm_agent_..." \
-d '{"content":"..."}' \
"https://api.commonly.me/api/pods/:podId/messages"There is no such route. Enumerated rather than assumed:
$ grep -rnE "router\.post\(\s*'[^']*pods/:podId/messages" backend/routes/
backend/routes/agentsRuntime.ts:1875:router.post('/pods/:podId/messages', agentRuntimeAuth, phase4RateLimit, ...)
That is the only one, and agentsRuntime mounts at /api/agents/runtime (server.ts:218). The two routers actually serving /api/pods — podRoutes (:194) and agentEnsembleRoutes (:248) — contain no route matching */messages at all. The correct URL is:
https://api.commonly.me/api/agents/runtime/pods/:podId/messages
/api/messages/:podId exists too (messages.ts:86) but takes auth, not agentRuntimeAuth — it is the human path, not the one a cm_agent_ token should use.
The other two URLs in this guide are correct, which is what makes this worth blocking on rather than noting: /api/registry/pods/:podId/agents/:agentName/runtime-tokens resolves (registry/agent-tokens.ts:56) and /api/agents/runtime/events?timeout=30 resolves (agentsRuntime.ts:447, agentRuntimeAuth, so the cm_agent_ bearer is right). Two working examples on either side make the broken one read as trustworthy, and the audience is a developer wiring up an agent who has no way to check — they will get a 404 with a valid token and reasonably suspect their token.
This is the AX-audit pattern the repo already tracks: a surface teaching a false model to a consumer who cannot verify it. It belongs in the guide's own subject matter.
Verdict
BLOCK — one-character-class fix: add /agents/runtime to that URL. Everything else in this delta is clean, and I'd clear it the moment that lands.
Not blocking, worth a glance while you're in there: it's worth grepping the guide's remaining URLs against the router mounts as a set, since this one survived at least one prior review of mine. I checked the three in the changed blocks and the two unchanged ones I quote above; I did not enumerate every URL in the file.
Summary
Verification