fix(copy): trigger copy claimed a push the transport does not do; retire "wake an agent"#784
Conversation
`triggers.wait` is a caller-driven short poll over the durable event log
(ADR-0106). Server-push was considered and REJECTED there — MCP's
server-initiated messages are at-most-once and drop events across a
disconnect — and is listed as a possible future latency hint, "never the
guarantee itself."
First-contact copy said otherwise. The dashboard's triggers page read "a
pushed trigger, not a poll"; /mcp/wake-an-agent's own frontmatter said "an
event-driven primitive, not polling" while line 63 of the same page correctly
called it a short poll; quickstart told readers to have an agent "block on
triggers.wait until a matching webhook lands." The deep pages (ADR-0106,
/mcp/trigger-semantics, the db + contract comments) were always honest — the
drift concentrated exactly where the mental model forms. The observable cost:
register a trigger, POST to the ingest URL, watch nothing happen, and start
debugging a working system.
Separately, the /product/agent-triggers visual showed
`triggers.create { endpointId, eventTypes: [...] }`. There is no eventTypes on
a trigger — it binds to ONE ENDPOINT and yields everything that endpoint
captures. The field belongs to subscriptions.create, and the input schema
strips unknown keys rather than rejecting them, so following the card produced
a silently UNFILTERED trigger with no error anywhere.
Copy:
- dashboard, docs, README, MCP tool descriptions, CLI help: say who drives the
cadence. `triggers.create`'s description now states outright that nothing
will call you and a client that only registers receives nothing — that text
is read by the model writing the client.
- /mcp/wake-an-agent: a callout for the reader whose POST did nothing, and the
flow diagram's last hop now originates at the agent, since it does.
- /mcp/trigger-semantics: new "one endpoint, everything it captures" section
documenting the absent filter and the silent strip.
- www card + product page: real arguments, per-endpoint scope.
Guards:
- scripts/trigger-copy-guard.mjs — phrases false in any context (negation
included), plus a positive obligation: pages introducing the primitive must
NAME the mechanism, so a page can't drop the lie and leave a vague claim.
Surfaces are DISCOVERED by content, not listed: the first cut of this guard
carried 13 hand-picked files and went green while quickstart.mdx lied.
- agent-trigger-card.test.tsx now validates shown ARGUMENTS against the
capability input schema. The old test pinned tool NAMES, which is why an
invented argument sailed through it.
Verified: guard RED on each defect before the fix (incl. re-introducing
eventTypes to confirm the new assertion fails with the right message); lint
chain + 652 script tests, www 553, web 1552, mcp 120, cli 839 all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012hee4wHe7WLq6xTafqNDRY
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
The verb was the dishonest part, not the noun. "Trigger" is what the API calls it (`triggers.*`), what the dashboard calls it, and what the www product page calls it; "wake" described a push that ADR-0106 explicitly rejected. Renaming the verb and keeping the noun leaves docs connected to the surface they document. /mcp/wake-an-agent → /mcp/agent-triggers /recipes/wake-an-agent → /recipes/agent-triggers Both old URLs are indexed, so docs.json gains a `redirects` block (permanent) rather than leaving every existing inbound link on a 404. The MCP page is rewritten from the ground truth rather than patched: it now opens with what a trigger IS — a durable subscription to one endpoint plus a cursor — and derives the three things worth caring about from that (can't miss an event, can't process one twice, can't be fooled). The two steps are numbered. The dashboard lede is shortened to the same shape and no longer needs the word "woken" at all. Verified: lint chain, docs-nav-guard (114 pages, links resolve), trigger-copy-guard (34 surfaces), web 1552, www 553. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012hee4wHe7WLq6xTafqNDRY
Follow-up: framing retired (founder call)
The verb was the dishonest part, not the noun — "trigger" is what The MCP page is rewritten from the ground truth rather than patched — it opens with what a trigger is (a durable subscription to one endpoint, plus a cursor) and derives the three properties that follow: can't miss an event, can't process one twice, can't be fooled. Steps are numbered. The dashboard lede shrank to the same shape and no longer needs the word "woken" at all. Re-verified: lint chain, |
The shorter /mcp/agent-triggers href narrowed the cell and left the column padding stale. Caught by CI's format-check rather than the pre-commit hook, whose lint-staged glob covers *.md but not *.mdx. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012hee4wHe7WLq6xTafqNDRY
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
What
triggers.waitis a caller-driven short poll over the durable event log (ADR-0106). Server-push was considered and rejected there — MCP's server-initiated messages are at-most-once and drop events across a disconnect — and survives only as a possible future latency hint, "never the guarantee itself."First-contact copy said the opposite. Found while a user registered a trigger, POSTed to their ingest URL, and watched nothing happen — then went debugging a working system against a mental model we handed them.
The two defects
1. "Push, not poll" (false). The dashboard triggers page read "a pushed trigger, not a poll."
/mcp/wake-an-agent's frontmatter said "an event-driven primitive, not polling" while line 63 of the same page correctly called it a short poll.quickstart.mdxtold readers to have an agent "block ontriggers.waituntil a matching webhook lands."The deep layers — ADR-0106,
/mcp/trigger-semantics, the db and contract comments (packages/db/src/agent-triggers.ts:4literally says "It does not push or 'wake' the agent") — were always honest. The drift concentrated in ledes, frontmatter descriptions, and nav blurbs: exactly where the mental model forms.2. Fabricated
eventTypes(worse). The/product/agent-triggersvisual showedtriggers.create { endpointId, eventTypes: ["issue.updated"] }. There is noeventTypeson a trigger — it binds to one endpoint and yields everything that endpoint captures. The field belongs tosubscriptions.create, and the input schema strips unknown keys rather than rejecting them. So a developer following the card would send a filter, have it silently dropped, and receive the endpoint's full stream believing it was filtered. Nothing errors, anywhere.Changes
Copy — dashboard, docs, README, MCP tool descriptions, CLI help now say who drives the cadence.
triggers.create's tool description states outright that nothing will call you and that a client which only registers receives nothing; that text is read by the model writing the client./mcp/wake-an-agentgets a callout aimed at the reader whose POST did nothing, and its flow diagram's last hop now originates at the agent, because it does./mcp/trigger-semanticsgets a new "one endpoint, everything it captures" section documenting the absent filter and the silent strip.Guards —
scripts/trigger-copy-guard.mjsbans phrases false in any context (negation-aware, so the honest "does NOT block until" still passes) and adds a positive obligation: pages introducing the primitive must name the mechanism, so a page can't drop the lie and leave a vague claim. Surfaces are discovered by content, not listed — the first cut of this guard carried 13 hand-picked files and went green whilequickstart.mdxlied.agent-trigger-card.test.tsxnow validates shown arguments against the capability input schema; the old test pinned tool names, which is why an invented argument sailed through for a year.Verification
Each guard confirmed RED on the real defect before the fix — including re-introducing
eventTypesto check the new assertion fails with the right message (triggers.create has no "eventTypes" input — it would be silently ignored).Green: full
lintchain, 652 script tests, www 553, web 1552, mcp 120, cli 839, 33/33 typecheck.Per the AGENTS.md human-UI-testing hard stop, this is all user-facing copy and I can't verify rendering. Please check:
/org/<slug>/triggers— the lede is longer than before and now contains an inline<code>; check it doesn't wrap badly, and the same for the revoke dialog.<Warning>on/mcp/wake-an-agentand<Note>on/mcp/trigger-semanticsrender correctly, and the rewritten mermaid diagram (now with a bidirectional agent↔log pair) lays out sensibly.🤖 Generated with Claude Code
https://claude.ai/code/session_012hee4wHe7WLq6xTafqNDRY