Packages
Package subscriptions and events
Official Kody doc
Use package subscriptions when a saved package should react to Kody-owned event topics. The saved package remains the top-level entity; subscriptions are nested manifest metadata and package runtime handlers.
Manifest shape
Declare subscriptions in package.json#kody.subscriptions as a record keyed by
event topic:
{
"name": "@scope/email-automation",
"exports": {
".": "./src/index.ts"
},
"kody": {
"id": "email-automation",
"description": "Automates stored inbound email.",
"subscriptions": {
"email.message.received": {
"handler": "./src/on-email-message-received.ts",
"description": "Process stored inbound mail."
}
}
}
}Each subscription definition supports:
handler(required): package-local module path for the event handler.description(optional): human-readable purpose for package detail and subscription listings.filters(optional): topic-specific metadata reserved for dispatchers.
Package checks normalize handler paths and build published bundle artifacts for
subscription handlers. Runtime dispatch invokes the handler through the normal
package execution path with package context, package-owned storage,
package-owned secrets, and kody:runtime.
Discovery
Use search for package subscription work, then call the built-in
packageSubscriptionsList capability to inspect the signed-in user's declared
subscriptions:
{
"topic": "email.message.received"
}The result lists package id, kody.id, package name, topic, handler,
description, and filters. Use this before debugging event dispatch, building
fan-out, or deciding whether a package already subscribes to a topic.
Synthetic dispatch
packageSubscriptionDispatch invokes one subscription handler on one
saved package over MCP. It is a platform-marked real-surface run with real side
effects. Use it immediately after publish to verify handler wiring without
waiting for production fan-out.
{
"kody_id": "email-automation",
"package_scope": "kody",
"topic": "email.message.received",
"params": {}
}For stored inbound mail, replay with email_message_id instead of params:
{
"kody_id": "email-automation",
"package_scope": "kody",
"topic": "email.message.received",
"email_message_id": "00000000000000000000000000000001"
}Pass exactly one of params or email_message_id. There is no caller
idempotency_key — the platform generates internal idempotency keys.
Final published and already_published results from
packagePublishExternalPush include test_hints.subscriptions[] with a starter
snippet per declared topic when subscriptions are present. For a dispatched
result, poll the workflow to completion before reading its final publish result.
Failed and non-fast-forward results have no test hints.
Handler guidance
- Platform markers. The platform sets top-level
synthetic: trueand, for stored-mail replay,replay_of. Real event dispatch strips caller-suppliedsyntheticandreplay_offrom handler envelopes. Run records agree with the handler payload. paramsoremail_message_id. Fixtureparamsmerge into the handler envelope before markers are added.email_message_idrebuilds the stored inbound email envelope from D1.- Treat synthetic identically to production. Handlers run the same code path unless a deliberately visible irreversible-side-effect guard says otherwise.
- Start minimal. Begin with
{}or the smallest object your handler accepts, then add fields until the smoke test covers the branches you care about. - Filters are not applied. Production dispatch for package-emitted topics
skips subscribers when
filtersdo not match the payload; synthetic dispatch always runs the named package. Put filter-matching fields insideparamswhen testing filter-dependent code paths. - Admin-only topics (
email.system-message.received,platform.feedback.submitted,community.activity.recorded,community.listing.published,status.incident.opened,fleet.package_error_rate.elevated,fleet.entitlement.crossed,auth.denial.burst,email.delivery.burst,status.incident.resolved,user.created,user.deleted,user.email_verification.failed,user.email_verification.stalled,user.email_outbound.paused,email.system-message.sent) gate production fan-out on admin role; synthetic dispatch still runs your handler directly for smoke testing. - Activity. Synthetic runs appear on the
subscriptionsurface. Handler failures do not emitrun.error.recorded(recursion guard).
Full call semantics and examples: Synthetic event dispatch.
Package-emitted topics (@scope/...)
Packages can define their own event topics and emit to them; every other package
saved by the same user that declares the topic in kody.subscriptions receives
the event. There is no cross-user delivery.
Declaring emitted topics
Declare topics in package.json#kody.emits. Topics must use the scoped form
@{username}/topic.name with a lower-dot-case body, and the scope must match
the emitting package's npm scope:
{
"name": "@kentcdodds/discord-gateway",
"kody": {
"id": "discord-gateway",
"description": "Discord gateway.",
"emits": {
"@kentcdodds/discord.message.created": {
"description": "A Discord message was created.",
"payloadSchema": {
"type": "object",
"properties": {
"messageId": { "type": "string", "minLength": 1 },
"channelId": { "type": "string" }
},
"required": ["messageId", "channelId"],
"additionalProperties": false
}
}
}
}
}payloadSchema is optional. When present it must be a JSON Schema subset with
root "type": "object"; supported keywords are type, description,
properties, required, additionalProperties (boolean), items, enum,
const, minLength, maxLength, minimum, maximum, minItems, and
maxItems. Unsupported keywords fail package checks at publish time so authors
never rely on silently ignored constraints. Declared schemas appear in package
search/detail projections so subscribers can discover payload shapes.
Emitting
Emit from any package runtime context (exports, subscription handlers,
package-owned jobs, apps, retrievers) with the events helper:
import { events } from 'kody:runtime'
await events.dispatch({
topic: '@kentcdodds/discord.message.created',
idempotencyKey: `discord:message-create:${message.id}`,
payload: { messageId: message.id, channelId: message.channelId },
})Rules:
- The topic must be declared in the emitting package's
kody.emits. idempotencyKeyis required; payloads must be JSON objects and are validated againstpayloadSchemawhen declared.- Payloads are capped at 64 KiB (canonical JSON). Store large data with
packageStorage()and emit a reference instead. events.dispatchis unavailable in ad hocexecuteruns — topics belong to packages, so emit from package code (or statically import a package export that dispatches).
Delivery semantics
Dispatch is asynchronous and durable: events.dispatch validates the event,
enqueues it on the kody-package-events-dispatch Queue (with DLQ), and returns
{ topic, source, idempotencyKey, status: "enqueued" } immediately. Emitters
never observe subscriber results or latency; check each subscriber's run records
for handler outcomes.
The Queue consumer resolves the emitting user's subscribed packages at delivery
time and invokes each subscription:@scope/topic handler with:
type PackageEventEnvelope = {
event: string
source: { type: 'package'; package_id: string; kody_id: string }
idempotency_key: string
payload: Record<string, unknown>
}- Per-subscriber invocations are exactly-once keyed on
(source package, subscriber package, topic, idempotencyKey), so Queue redelivery replays stored results instead of re-running handlers. - Infrastructure failures before handler code runs retry via the Queue (3
attempts, then the
kody-package-events-dispatch-dlqdead-letter queue). Terminal handler failures do not retry — a stored failed invocation replays rather than re-running — and stay visible in run records. - Event-driven chains carry a nested invocation depth budget (max 8 hops), so emit cycles between packages terminate.
- In environments without the Queue binding (local dev, preview) — or when an
enqueue fails — dispatch falls back to inline delivery with the same consumer
code path and reports
status: "delivered_inline"instead of"enqueued".
Filters on package-emitted topics
A subscription to a package-emitted topic may declare filters; every filter
key must be present in the event payload with an equal JSON value or the
subscriber is skipped:
{
"kody": {
"subscriptions": {
"@kentcdodds/discord.message.created": {
"handler": "./src/on-general-chat-message.ts",
"filters": { "channelId": "1470913684598423592" }
}
}
}
}Platform-owned topics (below) keep their existing behavior: their dispatchers
define whether and how filters apply.
email.message.received
Accepted stored inbound email dispatches email.message.received after Kody
stores the message and attachment metadata. Quarantined mail uses
email.message.quarantined instead.
Handlers receive a metadata-first payload:
type EmailMessageReceivedEvent = {
event: 'email.message.received'
message: {
id: string
inbox_id: string | null
from_address: string | null
envelope_from: string | null
to_addresses: Array<string>
cc_addresses: Array<string>
reply_to_addresses: Array<string>
subject: string | null
message_id_header: string | null
in_reply_to_header: string | null
references: Array<string>
processing_status: 'stored' | 'sent' | 'failed'
received_at: string | null
created_at: string
}
attachments: Array<{
id: string
filename: string | null
content_type: string | null
content_id: string | null
disposition: string | null
size: number
storage_kind: string
storage_key: string | null
created_at: string
}>
}Do not expect parsed bodies or attachment bytes in the event. Fetch full message
bodies, parsed headers beyond the event metadata, or attachment bytes only when
the handler needs them with emailMessageGet, emailAttachmentGet, or the
package runtime email helper.
email.message.quarantined
Quarantined stored inbound email dispatches email.message.quarantined instead
of email.message.received. The payload matches email.message.received with
event: 'email.message.quarantined'. Reclassifying a message later does not
retroactively dispatch either topic.
email.message.delivery.updated
Outbound Email Sending lifecycle changes dispatch
email.message.delivery.updated. The payload contains metadata for the owned
Kody message plus the provider event id, delivery status, terminal flag,
recipient, SMTP delivery fields, optional bounce/failure/rejection/complaint
details, and provider event timestamp.
Use this topic for delivery notifications and bounce or complaint workflows. Do
not resend on deferred: Cloudflare still has provider retries pending.
Provider event ids are stored idempotently, so duplicate Queue delivery does not
dispatch duplicate package invocations. Out-of-order events remain available in
delivery history but do not dispatch after a newer status.
email.system-message.received (admins)
Accepted mail stored in the operator-owned system inbox (kody@<apex>,
support@<apex>, and the other reserved system locals) dispatches
email.system-message.received to packages saved by users who hold the admin
role at dispatch time. Quarantined system-inbox mail is stored but never
dispatched. Non-admin subscribers never receive system mail.
The payload matches email.message.received (with
event: 'email.system-message.received') plus an admin_url string linking to
the stored message in the admin interface (/admin/system-email?messageId=...).
Handlers run as the admin package owner, so the user-scoped email capabilities
and the email runtime helper cannot read the system message — use the metadata
and admin_url for notifications, and the admin adminSystemEmailGet
capability for full contents.
email.system-message.sent (admins)
A successful adminSystemEmailSend / sendSystemEmail fans
email.system-message.sent to packages saved by users who hold the admin role
at dispatch time. This includes sends from the @kentcdodds/system-email
utility and raw capability calls. A non-admin package may declare the topic, but
it never receives the event. Role revocation stops delivery on the next send.
There is no Queue / DLQ for this topic. Dispatch is best-effort after the provider send already succeeded: a failed invoke is logged and does not fail the send or refund the daily cap.
Outbound system mail is not stored on the dedicated inbound system_email_*
graph (that graph refuses provider-message-id rows), so this topic carries the
sent correspondence itself — recipients, subject, text, and HTML — for admin
archive packages. It is the documented exception to metadata-only admin topics.
Handlers receive:
type SystemEmailSentEvent = {
event: 'email.system-message.sent'
from: string
to: Array<string>
subject: string
text: string | null
html: string | null
reply_to: string | null
provider_message_id: string | null
sent_at: string
}Idempotency keys include the topic, provider message id (or sent_at when the
provider omitted one), and subscriber package id. Deduplicate in package storage
on provider_message_id when a utility already recorded the same send.
platform.feedback.submitted (admins)
A successful, consent-gated metaPlatformFeedbackSubmit insert enqueues a
durable platform.feedback.submitted attempt. The Queue consumer dispatches to
packages saved by users who hold the admin role when the message is processed. A
non-admin package may declare the topic, but it never receives the event. Admin
roles are read fresh for every attempt, so revocation stops delivery on the next
processed submission.
Handlers receive the explicitly approved feedback and attributed submitter identity:
type PlatformFeedbackSubmittedEvent = {
event: 'platform.feedback.submitted'
content_warning: string
admin_url: string
feedback: {
id: string
category: 'friction' | 'bug' | 'experience' | 'suggestion' | 'other'
status: 'open'
created_at: string
summary_untrusted: string
details_untrusted: string
}
submitter: {
user_id: string
username: string | null
email: string | null
}
}summary_untrusted and details_untrusted are the exact feedback the user
explicitly approved. They remain user-authored untrusted data, and
content_warning tells handlers to treat them as feedback rather than
instructions. admin_url is built from the trusted deployment origin and links
to /admin/platform-feedback?feedbackId=<encoded id>, making it suitable for an
admin notifier. The event also includes the submitter's account user id,
username, and email snapshot stored with the submission. Retries never resolve
mutable live profile data, so an intervening account profile change cannot alter
the payload or its request hash. Rows without submitter snapshots retain null
username/email.
The event deliberately omits admin notes, reviewer fields, revision and update metadata, roles, plan, and unrelated account content. This narrow delivery exception applies only to the exact feedback the user approved after an agent showed the proposed summary and details and asked first. It does not grant package runtime general admin roles or general access to user data. Notification copies already delivered outside Kody cannot be recalled and may remain after Kody account deletion under the deployment operator's retention and deletion controls. Such copies contain only the exact approved feedback and attribution, never unrelated account content.
The feedback row is durable before Kody awaits the small Queue enqueue. Enqueue
failure is logged but does not change the successful MCP response, avoiding a
duplicate submission when a client retries. Queue bodies remain opaque
{ feedbackId } messages. After admin subscribers are discovered, lazy
parameter construction reloads the feedback immediately before any invocation.
If deletion removed the row, dispatch throws a typed permanent cancellation and
the Queue consumer acknowledges it without invoking or retrying. Other lookup,
discovery, or package-invocation wrapper infrastructure failures retry before
eventually routing exhausted messages to the DLQ. The same idempotency key makes
redelivery safe, but a stored failed invocation replays rather than
automatically rerunning; the DLQ is the recovery surface. Terminal handler
execution failures are isolated without preventing attempts for sibling
subscribers.
run.error.recorded
When a user-scoped Activity / run record finishes with status: 'error', Kody
dispatches run.error.recorded to packages saved by that same user that declare
the topic. Delivery is best-effort after a successful run-record Durable Object
write — there is no Queue / DLQ for this topic. Failures during subscriber
discovery or package-invocation infrastructure are logged and do not fail the
observed run.
Handlers receive a metadata-first payload:
type RunErrorRecordedEvent = {
event: 'run.error.recorded'
run: {
id: string
surface: string
name: string | null
package_id: string | null
kody_id: string | null
source_id: string | null
published_commit: string | null
storage_id: string | null
job_id: string | null
workflow_id: string | null
invocation_id: string | null
session_id: string | null
parent_run_id: string | null
started_at: string
finished_at: string | null
duration_ms: number | null
error_name: string | null
error_message: string | null
}
activity_url: string
}activity_url is built from the trusted deployment origin and links to
/account/activity/<runId>. The event deliberately omits log lines and the full
run metadata blob — fetch detail with runGet when needed. Error name and
message use the same truncation budget as the stored run record.
Recursion guard: runs whose surface is subscription never emit this event.
Subscription-handler failures themselves create run records; emitting again
would recurse. Successful runs and execute successes (which are not persisted)
never emit. Failed execute calls do persist and do emit.
Use this topic for notifier packages that email, write to Sheets, spawn an agent, or otherwise react when something in the user's account fails.
integration.auth.failed
When host-side OAuth token refresh fails with reconnectable caller state —
missing refresh token, provider HTTP 4xx / invalid_grant, missing secrets,
host-approval gaps, or invalid connection config — Kody dispatches
integration.auth.failed to packages saved by that same user that declare the
topic. Every classified attempt emits. The platform does not coalesce repeats;
notifier packages decide how often to ping, typically by pairing this topic with
integration.auth.succeeded and storing last-known health in package storage.
Provider HTTP 5xx and missing connections do not emit.
Delivery is best-effort after the refresh caller error is classified — there is no Queue / DLQ for this topic. Failures during subscriber discovery or package-invocation infrastructure are logged and do not change the refresh error the caller sees.
Handlers receive a metadata-first payload:
type IntegrationAuthFailedEvent = {
event: 'integration.auth.failed'
event_id: string
integration: {
name: string
lane: 'user' | 'platform'
account_label: string | null
description: string | null
provider: string | null
platform_app_slug: string | null
scopes: Array<string>
connected_at: string | null
token_refreshed_at: string | null
}
reason:
| 'missing_refresh_token'
| 'provider_rejected'
| 'missing_secret'
| 'host_not_approved'
| 'invalid_config'
provider: {
error: string | null
error_description: string | null
http_status: number | null
}
reconnect_url: string
account_url: string
occurred_at: string
}reconnect_url is built from the trusted deployment origin and links to
/connect/oauth?provider=<name>. When account_label looks like an email it
also adds loginHint so Google/OIDC can preselect that account. account_url
is the connection detail page (/account/integrations/<name>). The event
deliberately omits token values, secret values, client secrets, and secret
names. A short-lived access token that refreshes cleanly never emits. Successful
Google refreshes persist userinfo.email onto an empty account_label so later
reconnect pings can name the account.
Use this topic for notifier packages that post to Discord, email, or otherwise ask the owner to reconnect a dead grant.
integration.auth.succeeded
When host-side OAuth token refresh persists a new access token, or
/connect/oauth finishes saving tokens for a connection, Kody dispatches
integration.auth.succeeded to packages saved by that same user that declare
the topic. Every successful refresh and every successful connect persist emits.
Sequential attempts are not coalesced; concurrent in-flight refreshes of the
same connection share one attempt. The platform does not track working ↔ failed
itself; notifier packages store that edge in package storage so a later failure
can notify only on the working → failed transition.
Delivery is best-effort after the tokens are written. Failures during subscriber discovery or package-invocation infrastructure are logged and do not change the refresh result or the connect response.
Handlers receive a metadata-first payload:
type IntegrationAuthSucceededEvent = {
event: 'integration.auth.succeeded'
event_id: string
integration: {
name: string
lane: 'user' | 'platform'
account_label: string | null
description: string | null
provider: string | null
platform_app_slug: string | null
scopes: Array<string>
connected_at: string | null
token_refreshed_at: string | null
}
source: 'refresh' | 'oauth_connect'
account_url: string
occurred_at: string
}source is refresh for refreshIntegrationTokens and oauth_connect for the
/connect/oauth persist path. account_url is built from the trusted
deployment origin and links to /account/integrations/<name>. The event
deliberately omits token values, secret values, client secrets, and secret
names.
Use this topic with integration.auth.failed to flip stored health back to
working after a reconnect, or to send an all-clear.
mcp.server.disconnected / mcp.server.reconnected
When a saved, enabled outbound MCP server leaves ready and stays unavailable
after the hub's lightweight reconnect (two connectToServer + discover
attempts, no OAuth restart), Kody dispatches mcp.server.disconnected to
packages saved by that same user that declare the topic. When that down episode
later observes ready again, Kody dispatches mcp.server.reconnected with the
same server.episode_id.
Never-ready servers (still authenticating after add), disabled servers, and
in-flight connecting / connected / discovering states do not emit. Token
loss that parks in authenticating after a prior ready emits disconnected
without the lightweight retry — the user must reopen /account/mcp-servers.
mcpServerReconnect remains the explicit OAuth restart; listener packages
should not call it on every event.
Delivery is best-effort after the hub observes the transition — there is no Queue / DLQ for these topics. Failures during subscriber discovery or package-invocation infrastructure are logged and do not fail the MCP tool call or snapshot that noticed the change.
Handlers receive a metadata-first payload:
type McpServerConnectionEvent = {
event: 'mcp.server.disconnected' | 'mcp.server.reconnected'
event_id: string
server: {
id: string
name: string
state: string
previous_state: string
episode_id: string
}
observed_at: string
account_url: string
}account_url is built from the trusted deployment origin and links to
/account/mcp-servers/<id>. The event omits server URLs, OAuth tokens, bearer
headers, auth URLs, and discovered tool lists. Fetch live status with
mcpServerList when needed. Idempotency keys include the topic, episode id, and
subscriber package id, so one disconnected and one reconnected invoke per
episode.
Use these topics for notifier packages that post to Discord or otherwise tell
the owner an MCP server (for example home) dropped or came back. Do not scrape
run-error strings for connection health.
repo.pushed
When Cloudflare Artifacts reports commits pushed to a Kody-managed Artifacts
repo (plain repo, package, or job source), Kody dispatches repo.pushed to
packages saved by that same user that declare the topic. Delivery is durable via
the kody-artifacts-repo-events Queue (with DLQ). Session fork repos, session
workspace branch pushes (sessions/<id>), and publish git-notes
(refs/notes/commits) never emit. Opening a repo session git-pushes the session
ref, and delivering it as repo.pushed would let a handler that opens another
session loop. Publish attaches a metadata note after the source-branch push;
that second git update is not a content push. Events for other
ARTIFACTS_NAMESPACE values are ignored. Handlers that only care about default
branch content should still check push.ref.
Handlers receive a metadata-first payload:
type RepoPushedEvent = {
event: 'repo.pushed'
repo: {
source_id: string
repo_id: string
entity_kind: 'repo' | 'package' | 'job'
entity_id: string
name: string | null
kody_id: string | null
}
push: {
ref: string
before: string
after: string
total_commits_count: number
commits_truncated: boolean
commits: Array<{
id: string
message: string
message_truncated: boolean
timestamp: string
author: { name: string; email: string }
committer: { name: string; email: string }
parents: Array<string>
}>
}
artifacts: {
namespace: string
event_timestamp: string
event_subscription_id: string
}
}repo_id is the Artifacts repo name (also stored on entity_sources.repo_id).
name is the user-facing plain-repo name or package npm name when known;
kody_id is set for packages. For entity_kind: 'package' | 'job', a push
updates live HEAD but does not mean the package/job published commit advanced —
use publish / external-push / reconcile for activation.
Idempotency keys include the after commit, ref, and subscriber package id, so Queue redelivery is safe.
repo.created / repo.deleted
Account-level Artifacts create/delete events map to repo.created and
repo.deleted with the same repo entity block plus Artifacts metadata
(default_branch, description, Cloudflare cloudflare_repo_id). Same-user
fan-out and Queue delivery match repo.pushed. Unmatched deletes (D1 row
already gone) are acknowledged without retry.
package.codemod.applied
After a successful package codemod apply, Kody dispatches
package.codemod.applied to packages saved by the owning user of the
migrated package that declare the topic. Delivery follows the same best-effort
host dispatch path as run.error.recorded — there is no Queue / DLQ for this
topic. Failures during subscriber discovery or package-invocation infrastructure
are logged and do not fail the codemod apply.
Handlers receive a metadata-first payload:
type PackageCodemodSubscriptionEnvelope = {
event: 'package.codemod.applied'
codemod: {
id: string
description: string
}
package: {
package_id: string
kody_id: string
}
run: {
run_id: string
item_id: string
}
changed_paths: Array<string>
before_commit: string | null
after_commit: string | null
}changed_paths lists published-tree paths the codemod transform modified.
before_commit and after_commit are the package's published commit before and
after apply. The event deliberately omits file contents — fetch the current
published source with repo or package capabilities when a handler needs diffs or
full files. Community listing snapshots are unchanged by apply; only the owning
saved package advances. run.item_id is the apply ledger item id.
Use this topic for notifier packages that record migrations, ping owners, or trigger follow-up automation when platform codemods rewrite user package source.
package.codemod.reverted
After a successful package codemod revert, Kody dispatches
package.codemod.reverted to packages saved by the owning user of the
restored package that declare the topic. Delivery semantics match
package.codemod.applied and run.error.recorded.
Handlers receive the same envelope shape with
event: 'package.codemod.reverted':
type PackageCodemodSubscriptionEnvelope = {
event: 'package.codemod.reverted'
codemod: {
id: string
description: string
}
package: {
package_id: string
kody_id: string
}
run: {
run_id: string
item_id: string
}
changed_paths: Array<string>
before_commit: string | null
after_commit: string | null
}For revert, before_commit is the post-codemod published commit (the source
apply item's afterCommit) and after_commit is the restored pre-codemod
commit. changed_paths is copied from the source apply item (paths the codemod
originally changed), not recomputed at revert time. run.item_id is the new
revert-run ledger item id. Revert snapshots expire from KV after 90 days, so
revert and this event are unavailable once the snapshot is gone.
Use this topic when automation must react to an operator or user undoing a prior codemod apply.
community.activity.recorded (admins)
Successful community fork and rating writes enqueue a durable
community.activity.recorded attempt. The Queue consumer dispatches only to
packages saved by users who hold the admin role when the message is processed.
Non-admin declarations are inert, and role revocation applies to the next
attempt.
Handlers receive activity metadata only:
type CommunityActivityRecordedEvent = {
event: 'community.activity.recorded'
event_id: string
activity:
| {
id: string
kind: 'fork'
listing: { id: string; name: string; kody_id: string }
actor: { username: string | null }
occurred_at: string
}
| {
id: string
kind: 'rating'
listing: { id: string; name: string; kody_id: string }
actor: { username: string | null }
occurred_at: string
stars: number
adaptation_effort: number
}
}The event omits stable user ids, email, forked package/source ids, target kody
ids, rating notes, package source, secrets, and unrelated account content.
One-click installs and ordinary forks both appear as fork because they share
the same existing community_forks row shape. Rating records are upserts, so
the reloaded activity contains the latest scores.
Queue messages contain only { eventId, kind, activityId }. Dispatch reloads
the metadata projection after admin subscriber discovery. Missing activity is a
permanent cancellation; transient lookup, discovery, and package-invocation
infrastructure failures retry and can reach the dedicated DLQ. event_id
provides a distinct package-invocation idempotency key for every recorded write.
community.listing.published (admins)
The first successful community listing publish enqueues a durable
community.listing.published attempt. Republishes record listing_updated in
the activity timeline but do not enqueue this subscription topic. The Queue
consumer dispatches only to packages saved by users who hold the admin role when
the message is processed. Non-admin declarations are inert, and role revocation
applies to the next attempt.
Handlers receive listing metadata only:
type CommunityListingPublishedEvent = {
event: 'community.listing.published'
event_id: string
listing: {
id: string
name: string
kody_id: string
description: string | null
public_url: string
}
publisher: {
username: string | null
}
published_at: string
}public_url is the canonical shareable URL ({base}/@{username}/{kody_id}),
never /community/{listing_id}. The event omits stable user ids, email, package
source, secrets, and unrelated account content.
Queue messages contain only { eventId, listingId }. Dispatch reloads the
metadata projection after admin subscriber discovery. Missing, delisted, or
unpublished listings are a permanent cancellation; transient lookup, discovery,
and package-invocation infrastructure failures retry and can reach the dedicated
DLQ. event_id provides a distinct package-invocation idempotency key for every
first-publish enqueue. Enqueue failures are logged and never fail
communityPublish.
status.incident.opened / status.incident.resolved (admins)
When the isolated status worker opens or resolves a component incident, it
best-effort POSTs a metadata-only payload to the main worker
(POST /__maintenance/status-incidents, shared bearer
STATUS_INCIDENT_EVENT_SECRET). The main worker fans out immediately to
packages saved by users who hold the admin role at dispatch time. A non-admin
package may declare the topic, but it never receives the event. Role revocation
stops delivery on the next incident.
There is no Queue / DLQ for these topics. A missing secret, a down main worker,
or a failed invoke is logged and skipped. Packages that also reconcile
https://status.kody.codes/status.json can catch an incident that is still
open, or still listed in recent history, on the next sweep. An incident that
opens and resolves between polls can be missed. Probe recording never waits on
fan-out.
Handlers receive operator telemetry only:
type StatusIncidentOpenedEvent = {
event: 'status.incident.opened'
status_url: string
incident: {
component: string
detail: string | null
started_at: string
}
}
type StatusIncidentResolvedEvent = {
event: 'status.incident.resolved'
status_url: string
incident: {
component: string
detail: string | null
started_at: string
resolved_at: string
}
}status_url is the public status page (https://status.kody.codes).
component is a status-page card id such as app_db or app. detail is the
probe reason (timeout, error, …) or null. Timestamps are ISO-8601 UTC. The
event omits probe logs, health-check bodies, user identities, secrets, and
unrelated account content. Idempotency keys include the topic, component,
timestamps, and package id so a retried POST does not double-invoke.
fleet.package_error_rate.elevated (admins)
The hourly usage_aggregation lane queries Analytics Engine for anonymous fleet
totals of package_export, package_static_call, job_run, and
workflow_run. It compares the last completed hour to the hour before it, and
the last 24 hours to the 24 hours before that. When the combined error rate
rises past a volume floor, Kody writes a KV snapshot for /admin/insights and
fans fleet.package_error_rate.elevated to packages saved by users who hold the
admin role at dispatch time. A second query then groups recent-window errors by
owner. One account at ≥80% of those errors, or three accounts together at ≥80%,
is concentrated; a true multi-user spike stays fleet-wide. Concentrated pages
still fan out to admin packages — they name the owning accounts instead of
looking like a fleet outage. A non-admin package may declare the topic, but it
never receives the event. Role revocation stops delivery on the next elevation.
There is no Queue / DLQ for this topic. A missed invoke is logged and does not fail usage rollup aggregation. A six-hour cooldown suppresses repeat pages during a prolonged incident.
Handlers receive operator telemetry only:
type FleetPackageErrorRateElevatedEvent = {
event: 'fleet.package_error_rate.elevated'
event_id: string
status_url: string
insights_url: string
environment: string
observed_at: string
trigger: {
window: 'hour' | 'day'
reason: 'absolute_delta' | 'relative_factor' | 'from_zero'
recent: {
start: string
end: string
combined: { events: number; errors: number; rate: number | null }
by_metric: Array<{
metric:
'package_export' | 'package_static_call' | 'job_run' | 'workflow_run'
events: number
errors: number
rate: number | null
}>
}
previous: {
start: string
end: string
combined: { events: number; errors: number; rate: number | null }
by_metric: Array<{
metric:
'package_export' | 'package_static_call' | 'job_run' | 'workflow_run'
events: number
errors: number
rate: number | null
}>
}
}
by_metric: Array<{
metric:
'package_export' | 'package_static_call' | 'job_run' | 'workflow_run'
events: number
errors: number
rate: number | null
}>
concentration: {
kind: 'one_account' | 'few_accounts' | 'fleet'
recent_errors: number
owner_count: number
package_count: number
top_owner_share: number
owners: Array<{
username: string
error_share: number
packages: Array<{ kody_id: string }>
}>
} | null
}status_url is the public status page. insights_url is the operator insights
dashboard. Counts are fleet-wide and weighted by Analytics Engine
_sample_interval. concentration is present when the elevation query
succeeds. owners is populated only for one_account and few_accounts after
D1 resolves usernames and package kody ids. The event omits user ids, package
UUIDs, emails, error strings, logs, and unrelated account content. Idempotency
keys include the topic, event id, and subscriber package id.
Use this topic for notifier packages that enqueue a Kody-repo investigation request. Agent spawning stays on the scheduled sweep, not in the subscription handler. Do not treat this topic as permission to read another user's Activity or package source.
fleet.entitlement.crossed (admins)
The hourly usage_entitlement_alert lane sweeps the top ~15 active accounts
this UTC month and fans fleet.entitlement.crossed to packages saved by users
who hold the admin role at dispatch time. A non-admin package may declare the
topic, but it never receives the event. Role revocation stops delivery on the
next crossing.
One event fires per crossing of 80% (approaching) or 100% (reached) on a
specific entitlement, when a non-admin account first exceeds 24h of combined
execute / job / workflow runtime in the UTC month, when a non-admin account
first reaches a plan-aware unique Dynamic Worker cost threshold this UTC month
(Free $2, Standard $12, Pro $49; max and admin accounts do not page), or when
a non-admin account first hits 100% of execute_calls_per_day on three of the
last seven UTC days. Staying over the same threshold does not emit again. A
later drop below that threshold, then a climb back over it, is a new instance. A
same-hour jump to 100% emits reached only and claims the 80% crossing so a
later drop into the 80–99% band stays silent. Execute-cap days are recorded on
durable hit keys so a later drop below 100% the same day does not erase the
train.
There is no Queue / DLQ for this topic. A missed invoke is logged and does not fail the hourly sweep. Retry happens on the next hour if the crossing is still unclaimed.
Handlers receive operator telemetry only:
type FleetEntitlementCrossedEvent =
| {
event: 'fleet.entitlement.crossed'
kind: 'entitlement'
user: { id: string; username: string }
resource:
| 'saved_packages'
| 'scheduled_jobs'
| 'repo_sessions'
| 'email_sends_per_day'
| 'email_receives_per_day'
| 'stored_email_messages'
| 'secrets'
| 'concurrent_workflows'
| 'storage_bytes'
| 'execute_calls_per_day'
| 'outbound_fetches_per_day'
| 'job_runs_per_day'
label: string
threshold: 'approaching' | 'reached'
current: number
limit: number
percent_of_limit: number
insights_url: string
users_url: string
observed_at: string
}
| {
event: 'fleet.entitlement.crossed'
kind: 'runtime_duration'
user: { id: string; username: string }
total_duration_ms: number
threshold_ms: number
insights_url: string
users_url: string
observed_at: string
}
| {
event: 'fleet.entitlement.crossed'
kind: 'repeated_entitlement'
user: { id: string; username: string }
resource: 'execute_calls_per_day'
days_at_limit: number
window_days: 7
threshold_days: 3
insights_url: string
users_url: string
observed_at: string
}
| {
event: 'fleet.entitlement.crossed'
kind: 'dynamic_worker_cost'
user: { id: string; username: string }
unique_worker_days: number
estimated_gross_usd: number
threshold_usd: number
insights_url: string
users_url: string
observed_at: string
}user.id is the stable account user id. insights_url and users_url are
operator dashboards. Timestamps are ISO-8601 UTC. The event omits emails, plan
names, secrets, package source, and unrelated account content. Idempotency keys
include the topic, user id, crossing kind, threshold or UTC month, resource, UTC
day for *_per_day resources and repeated_entitlement, and subscriber package
id.
Use this topic for notifier packages that send an operator message (for example
Discord) when an account first crosses a plan limit, repeats an execute cap, or
crosses the unique-worker cost line. Filter on kind if a busy-day 80% crossing
is too noisy. Do not treat this topic as permission to read another user's
packages, secrets, or Activity.
User created and deleted (admins)
Password signup, social-login signup, and admin-created person accounts dispatch
user.created after the account row and default user role exist. Self-service
account deletion at /account dispatches user.deleted after the per-user
cascade finishes. Platform accounts (reserved official package owners) do not
emit user.created.
Production fan-out selects only packages whose owners hold the admin role at dispatch time. A non-admin package may declare the topic, but it never receives the event. Role revocation stops delivery on the next create or delete.
There is no Queue / DLQ for these topics. Dispatch is best-effort after the account change commits: a failed invoke is logged and does not fail signup, admin create, or account deletion.
Handlers receive a metadata-only identity snapshot:
type UserCreatedEvent = {
event: 'user.created'
user: {
id: string
username: string
email: string
}
source: 'signup' | 'oauth' | 'admin'
created_at: string
invite_code: string | null
attribution: {
utm_source: string | null
utm_medium: string | null
utm_campaign: string | null
utm_content: string | null
utm_term: string | null
landing_path: string | null
referrer: string | null
}
}
type UserDeletedEvent = {
event: 'user.deleted'
user: {
id: string
username: string
email: string
}
deleted_at: string
}user.id is the stable account user id. source is the create path that
committed. invite_code is the consumed, normalized invite code when signup
used one, otherwise null. attribution is first-touch marketing UTMs and
landing path/referrer persisted on the account at signup (all null when absent).
Timestamps are ISO-8601 UTC. The event omits passwords, roles, plan, secrets,
packages, and unrelated account content. Notification copies already delivered
outside Kody cannot be recalled after account deletion. Idempotency keys include
the topic, user id, timestamp, and package id.
user.email_verification.failed (admins)
The first terminal Cloudflare lifecycle event on a signup/verify send
(bounced, failed, rejected, or complained) fans
user.email_verification.failed to packages saved by users who hold the admin
role at dispatch time. A later replay of the same terminal state does not emit
again. A non-admin package may declare the topic, but it never receives the
event. Role revocation stops delivery on the next failure.
There is no Queue / DLQ for this topic. Dispatch is best-effort after the user row already carries the bounce: a failed invoke is logged and does not fail delivery-event processing.
Handlers receive a metadata-only operator snapshot:
type UserEmailVerificationFailedEvent = {
event: 'user.email_verification.failed'
user: {
id: string
username: string
email: string
}
status: 'bounced' | 'failed' | 'rejected' | 'complained'
class: 'sender_block' | 'other' | null
admin_user_url: string
occurred_at: string
}user.id is the stable account user id. class is sender_block for
Fastmail-style domain/IP blocks (RLR613, RLR813, blacklist language),
other for generic terminal failures, or null when the event is not
classified. admin_user_url is the operator page for that account. Timestamps
are ISO-8601 UTC. The event omits SMTP transcripts, verification tokens,
passwords, roles, plan, secrets, and unrelated account content. Idempotency keys
include the topic, user id, timestamp, and subscriber package id.
Use this topic for notifier packages that email or page an operator when
signup/verify mail bounces or otherwise fails at the provider. Silent drops that
stay accepted use user.email_verification.stalled. user.created still
fires for every new person account, including accounts that later verify
themselves. Do not treat this topic as permission to mark the account verified
or mint a link — call adminUserVerify from an admin session when ownership is
proven.
user.email_verification.stalled (admins)
The hourly email_verification_stall_alert lane lists unverified person
accounts whose latest signup/verify send is still accepted after 60 minutes
with no Cloudflare lifecycle event (delivered, bounced, failed,
rejected, or complained). Each matching send fans
user.email_verification.stalled to packages saved by users who hold the admin
role at dispatch time. The scan walks that derived set in pages of 50 using a KV
watermark so later sends are not starved behind the oldest unresolved rows. A
later hourly scan of the same accepted timestamp does not emit again. A resend
that stamps a new accepted time can emit again after another hour. A non-admin
package may declare the topic, but it never receives the event. Role revocation
stops delivery on the next scan.
There is no Queue / DLQ for this topic. Dispatch is best-effort after the user
row already carries accepted: a failed invoke is logged and does not fail the
hourly cron.
Handlers receive a metadata-only operator snapshot:
type UserEmailVerificationStalledEvent = {
event: 'user.email_verification.stalled'
user: {
id: string
username: string
email: string
}
status: 'accepted'
accepted_at: string
stall_after_minutes: number
admin_user_url: string
occurred_at: string
}user.id is the stable account user id. accepted_at is
users.email_verification_delivery_at for that send. stall_after_minutes is
the scan threshold (60). occurred_at is the scan time. admin_user_url is the
operator page for that account. Timestamps are ISO-8601 UTC. The event omits
SMTP transcripts, verification tokens, passwords, roles, plan, secrets, and
unrelated account content. Idempotency keys include the topic, user id, accepted
timestamp, and subscriber package id.
Use this topic for notifier packages that email or page an operator when a
signup is stranded without a bounce. SimpleLogin-style aliases can drop kody@
mail without a terminal Cloudflare event. Terminal failures still use
user.email_verification.failed. /admin/users and adminUserList accept
verification=stalled for the same derived set. Do not treat this topic as
permission to mark the account verified or mint a link — call adminUserVerify
from an admin session when ownership is proven.
user.email_outbound.paused (admins)
The delivery-queue abuse lane pauses outbound sending after one spam complaint
or five bounced sends in a UTC day, then fans user.email_outbound.paused to
packages saved by users who hold the admin role at dispatch time. A later replay
of the same pause write does not emit again. A non-admin package may declare the
topic, but it never receives the event. Role revocation stops delivery on the
next pause.
There is no Queue / DLQ for this topic. Dispatch is best-effort after the pause is already committed: a failed invoke is logged and does not fail delivery-event processing.
Handlers receive a metadata-only operator snapshot:
type UserEmailOutboundPausedEvent = {
event: 'user.email_outbound.paused'
user: {
id: string
username: string
email: string
}
reason: 'complained' | 'bounced'
bounce_threshold: number | null
admin_user_url: string
occurred_at: string
}user.id is the stable account user id. bounce_threshold is the daily bounce
count that triggered the pause (5) when reason is bounced, otherwise
null. admin_user_url is the operator page for that account. Timestamps are
ISO-8601 UTC. The event omits SMTP transcripts, message bodies, passwords,
roles, plan, secrets, and unrelated account content. Idempotency keys include
the topic, user id, timestamp, and subscriber package id.
Use this topic for notifier packages that email or page an operator when one
account's outbound sending is paused. Do not treat this topic as permission to
clear the pause — call the audited resume_email_outbound admin action after
review. Shared-domain pressure uses email.delivery.burst.
auth.denial.burst (admins)
The hourly auth_denial_alert lane counts MCP auth failures
(mcp_token_rejected, mcp_capability_denied) in the last 60 minutes. When the
count crosses 50, it fans auth.denial.burst to packages saved by users who
hold the admin role at dispatch time. A six-hour KV cooldown suppresses repeat
pages on the same sustained spike. A non-admin package may declare the topic,
but it never receives the event.
There is no Queue / DLQ for this topic. A missed invoke is logged and does not
fail the hourly cron. Audit rows and /admin/insights remain the browse
surface.
Handlers receive operator telemetry only:
type AuthDenialBurstEvent = {
event: 'auth.denial.burst'
count: number
threshold: number
window_minutes: number
insights_url: string
observed_at: string
}insights_url is the operator insights dashboard. The event omits user ids,
token ids, capability names, request bodies, and unrelated account content.
Idempotency keys include the topic, observed timestamp, and subscriber package
id.
Use this topic for notifier packages that page an operator when permission probing or a compromised account is likely. Do not treat this topic as permission to suspend an account.
email.delivery.burst (admins)
The hourly email_delivery_alert lane counts platform-wide Cloudflare Email
Sending outcomes of complained or bounced in the last 60 minutes. When the
count crosses 20, it fans email.delivery.burst to packages saved by users who
hold the admin role at dispatch time. A six-hour KV cooldown suppresses repeat
pages. A non-admin package may declare the topic, but it never receives the
event.
There is no Queue / DLQ for this topic. A missed invoke is logged and does not
fail the hourly cron. Thin email_delivery_alert_events rows and the Email
delivery health chart on /admin/insights remain the browse surface.
Handlers receive operator telemetry only:
type EmailDeliveryBurstEvent = {
event: 'email.delivery.burst'
count: number
threshold: number
window_minutes: number
insights_url: string
observed_at: string
}insights_url is the operator insights dashboard. The event omits user ids,
recipients, message bodies, SMTP transcripts, and unrelated account content.
Idempotency keys include the topic, observed timestamp, and subscriber package
id.
Use this topic for notifier packages that page an operator when the shared
sending domain is under platform-wide pressure. The per-user
user.email_outbound.paused topic still fires when one account is paused.