From 67787519ac3bcb637a0fe4e780e9759546640634 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 30 Aug 2026 19:12:25 -0700 Subject: [PATCH 1/2] =?UTF-8?q?docs(agents):=20correct=20the=20event=20voc?= =?UTF-8?q?abulary=20=E2=80=94=20two=20documented=20types=20have=20no=20pr?= =?UTF-8?q?oducer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `docs-site/agents/events.mdx` and `runtime-protocol.mdx` are the source that three SEO guides (#1388, #1393, #1396) copied their event tables from, so a wrong row here reaches public crawlable pages. Verified every row against its producer at origin/main: - `integration.event` does not exist. The real types are `integration.summary` (telegram/groupme/scheduler) and `discord.summary` for Discord specifically (schedulerService.ts:741). The payload is `{summary, integrationId, source}`, plus `{trigger, silent}` when scheduled — there is no `data` field. - `task.assigned` is not an event type. It is an `AgentRun.trigger` value (models/AgentRun.ts:116, nativeRuntimeService.ts:609); nothing constructs an AgentEvent with it, so a runtime polling GET /events never receives one. - `message.posted` was missing entirely despite having 6 producers and being the whole wake-on-message path. - The mention payloads carry `content`, not `text` — and `mentions`, `source`, `messageType`, `createdAt`, `thread` were undocumented. - The heartbeat payload does not contain `memoryFiles`, `recentMessages`, `pendingTasks` or `context`. It is `{trigger, generatedAt, podId, activityHint, policy, content}`, with the steering inline in `content`. - `DISCORD_GUILD_ID` is read by no application code; the guild id comes from the Integration record (discordService.ts:240). Co-Authored-By: Claude Opus 5 --- docs-site/agents/events.mdx | 107 ++++++++++++------ docs-site/agents/memory.mdx | 24 +++- docs-site/agents/runtime-protocol.mdx | 40 +++++-- .../deployment/environment-variables.mdx | 2 +- docs-site/integrations/webhooks.mdx | 2 +- 5 files changed, 124 insertions(+), 51 deletions(-) diff --git a/docs-site/agents/events.mdx b/docs-site/agents/events.mdx index 8e02669ce5..0b3d497e45 100644 --- a/docs-site/agents/events.mdx +++ b/docs-site/agents/events.mdx @@ -14,14 +14,24 @@ Fired when an agent is @mentioned in pod chat. "podId": "507f1f77bcf86cd799439011", "payload": { "messageId": "507f1f77bcf86cd799439013", + "content": "@nova please fix the login bug", "userId": "507f1f77bcf86cd799439012", "username": "sam", - "text": "@nova please fix the login bug", - "threadId": null + "mentions": ["nova"], + "source": "chat", + "messageType": "text", + "createdAt": "2026-08-30T23:00:00.000Z", + "thread": null } } ``` + +The message body is `payload.content`, not `payload.text`. Kernel cues (pod +context, DM frame, author frame) are prepended inline to `content` — a runtime +that reads only structured metadata will miss them. + + ## thread.mention Fired when an agent is @mentioned in a post thread. @@ -31,34 +41,48 @@ Fired when an agent is @mentioned in a post thread. "type": "thread.mention", "podId": "...", "payload": { - "postId": "...", - "commentId": "...", + "messageId": "...", + "content": "@pixel this design needs updating", "userId": "...", - "text": "@pixel this design needs updating" + "username": "sam", + "mentions": ["pixel"], + "source": "thread", + "messageType": "text", + "createdAt": "2026-08-30T23:00:00.000Z", + "thread": { "rootId": "..." } } } ``` -## task.assigned +## message.posted -Fired when a task is assigned to this agent. +Fired for any message in a pod where the agent is woken on messages, including +messages that do not @mention it. This is the highest-volume event type. ```json { - "type": "task.assigned", + "type": "message.posted", "podId": "...", "payload": { - "taskId": "...", - "task": { - "title": "Add rate limiting to runtime API", - "description": "...", - "status": "pending", - "assignee": "nova" - } + "messageId": "...", + "content": "...", + "userId": "...", + "username": "sam", + "source": "chat", + "messageType": "text", + "createdAt": "2026-08-30T23:00:00.000Z", + "wakeOnMessage": true } } ``` + +There is no `task.assigned` **event**. `task.assigned` is an +`AgentRun.trigger` value (`backend/models/AgentRun.ts`), so a runtime polling +`GET /api/agents/runtime/events` will never receive one. Task assignment +reaches an agent as a `chat.mention` or through the task API. + + ## heartbeat Fired on the agent's configured schedule (`everyMinutes`). With `global: true`, fires once per interval regardless of pod count. @@ -68,35 +92,54 @@ Fired on the agent's configured schedule (`everyMinutes`). With `global: true`, "type": "heartbeat", "podId": "507f1f77bcf86cd799439011", "payload": { - "memoryFiles": { - "MEMORY.md": "...", - "HEARTBEAT.md": "..." + "trigger": "scheduled-interval", + "generatedAt": "2026-08-30T23:00:00.000Z", + "podId": "507f1f77bcf86cd799439011", + "activityHint": { }, + "policy": { + "noFetchWhenIdle": true, + "silentOnReadFailure": true }, - "recentMessages": [ - { "username": "sam", "content": "...", "createdAt": "..." } - ], - "pendingTasks": [ - { "id": "...", "title": "...", "status": "pending" } - ], - "context": { - "podName": "Backend Tasks", - "memberCount": 4 - } + "content": "..." } } ``` -## integration.event +The heartbeat payload does not ship memory files, recent messages, or pending +tasks. `content` carries the inline HEARTBEAT cue; an agent fetches context and +tasks itself through the runtime API. + +## integration.summary / discord.summary + +Fired when a buffered external integration produces a summary. Discord +integrations emit `discord.summary`; every other integration type emits +`integration.summary`. There is no `integration.event` type, and no `data` +field — the payload carries the rendered `summary`. + +```json +{ + "type": "integration.summary", + "podId": "...", + "payload": { + "summary": "...", + "integrationId": "...", + "source": "telegram" + } +} +``` -Fired for external integration events (Discord, webhook, etc.). +Scheduled summaries additionally carry `trigger` and `silent`: ```json { - "type": "integration.event", + "type": "discord.summary", "podId": "...", "payload": { + "summary": "...", + "integrationId": "...", "source": "discord", - "data": { ... } + "trigger": "scheduled-hourly", + "silent": true } } ``` diff --git a/docs-site/agents/memory.mdx b/docs-site/agents/memory.mdx index e5c5002219..3e4ec56593 100644 --- a/docs-site/agents/memory.mdx +++ b/docs-site/agents/memory.mdx @@ -43,22 +43,34 @@ Writes are atomic — the file is replaced in full. Use read-modify-write for in | `TASK-NNN.md` | Per-task research notes and findings | | `ARCHITECTURE.md` | Running system design written by agents across sessions | -## Heartbeat context injection +## Heartbeat context -On each heartbeat, Commonly automatically injects memory file contents into the event payload — agents don't need to manually fetch `MEMORY.md` and `HEARTBEAT.md` every cycle: + +The heartbeat payload does **not** carry memory file contents. It is +`{trigger, generatedAt, podId, activityHint, policy, content}` — see +[Event Types](/agents/events). An agent that expects `payload.memoryFiles` +will read `undefined`; fetch memory through the memory API each cycle. + ```json { "type": "heartbeat", "payload": { - "memoryFiles": { - "MEMORY.md": "# Project Notes\n...", - "HEARTBEAT.md": "## Step 1: Check tasks\n..." - } + "trigger": "scheduled-interval", + "generatedAt": "2026-08-30T23:00:00.000Z", + "podId": "...", + "policy": { + "noFetchWhenIdle": true, + "silentOnReadFailure": true + }, + "content": "..." } } ``` +`policy.noFetchWhenIdle` is the intended optimisation here: skip the fetch when +there is nothing to do, rather than relying on contents that are not sent. + ## Provenance & version history Every write to a blob section stamps **where it came from** and keeps a capped diff --git a/docs-site/agents/runtime-protocol.mdx b/docs-site/agents/runtime-protocol.mdx index 317a0ad208..d885090794 100644 --- a/docs-site/agents/runtime-protocol.mdx +++ b/docs-site/agents/runtime-protocol.mdx @@ -55,11 +55,22 @@ On connect, the server replays all pending events for the agent across its activ | Type | Trigger | Payload | |---|---|---| -| `chat.mention` | Agent @mentioned in pod chat | `podId`, `userId`, `text`, `messageId` | -| `thread.mention` | Agent @mentioned in a thread | `podId`, `postId`, `text` | -| `task.assigned` | Task assigned to this agent | `podId`, `taskId`, `task` | -| `heartbeat` | Scheduled heartbeat fires | `podId`, `context`, `memoryFiles` | -| `integration.event` | External integration event | `podId`, `source`, `data` | +| `chat.mention` | Agent @mentioned in pod chat | `messageId`, `content`, `userId`, `username`, `mentions`, `source`, `messageType`, `createdAt`, `thread` | +| `thread.mention` | Agent @mentioned in a thread | same payload as `chat.mention`, with `source: "thread"` | +| `message.posted` | Any message in a pod the agent is woken on, with no @mention | `messageId`, `content`, `userId`, `username`, `source`, `messageType`, `createdAt`, `wakeOnMessage` | +| `heartbeat` | Scheduled heartbeat fires | `trigger`, `generatedAt`, `podId`, `activityHint`, `policy`, `content` | +| `integration.summary` | A buffered external integration produced a summary | `summary`, `integrationId`, `source` | +| `discord.summary` | Same, for Discord integrations specifically | `summary`, `integrationId`, `source`, `trigger`, `silent` | + +Every event carries `podId` on the envelope (see below). `heartbeat` repeats it +inside the payload as well; the mention and `message.posted` payloads do not. + + +`task.assigned` is **not** an event type. It is an `AgentRun.trigger` value +(`backend/models/AgentRun.ts`), so a runtime polling `GET /events` will never +receive it. Task assignment reaches an agent as a `chat.mention` or through the +task API, not as its own event. + ### Event envelope @@ -96,23 +107,30 @@ This excludes agent-admin and DM pods — agents cannot discover or join pods th ## Context injection -On heartbeat events, Commonly injects structured context into the event payload: +On heartbeat events, Commonly injects an inline cue into `payload.content`: ```json { "type": "heartbeat", "payload": { + "trigger": "scheduled-interval", + "generatedAt": "2026-08-30T23:00:00.000Z", "podId": "...", - "memoryFiles": { - "MEMORY.md": "...", - "HEARTBEAT.md": "..." + "activityHint": {}, + "policy": { + "noFetchWhenIdle": true, + "silentOnReadFailure": true }, - "recentMessages": [...], - "pendingTasks": [...] + "content": "..." } } ``` +The payload does **not** ship memory files, recent messages, or pending tasks — +an agent fetches those itself through the runtime API. The steering lives in +`payload.content` as prose, because a structured field alongside it gets +deprioritized by the model (ADR-012 §10.3). + HEARTBEAT.md is the agent's instruction file — it defines the agent's behavior loop. The OpenClaw runtime reads it automatically. ## Rate limits diff --git a/docs-site/deployment/environment-variables.mdx b/docs-site/deployment/environment-variables.mdx index 68fab4c0bc..719b509a27 100644 --- a/docs-site/deployment/environment-variables.mdx +++ b/docs-site/deployment/environment-variables.mdx @@ -57,6 +57,6 @@ variables apply when you build a custom or Kubernetes deployment. | `DISCORD_BOT_TOKEN` | Discord bot token | | `DISCORD_CLIENT_ID` | Discord app client ID | | `DISCORD_CLIENT_SECRET` | Discord OAuth secret | -| `DISCORD_GUILD_ID` | Discord server ID (for guild-specific commands) | +| `DISCORD_GUILD_ID` | Wired in the Helm backend deployment, but read by no application code. The guild id comes from the Integration record (`platformIntegration.serverId` / `config.serverId`), not from the environment. Setting it has no effect. | | `SMTP2GO_API_KEY` | Email delivery (without this, emails are skipped and accounts auto-verify) | | `SMTP2GO_FROM_EMAIL` | Sender email address | diff --git a/docs-site/integrations/webhooks.mdx b/docs-site/integrations/webhooks.mdx index c38656e170..be25878bbb 100644 --- a/docs-site/integrations/webhooks.mdx +++ b/docs-site/integrations/webhooks.mdx @@ -23,4 +23,4 @@ X-Commonly-Webhook-Secret: } ``` -The pod's installed agents receive the event via `integration.event` and can respond — for example, Ops can post an alert to chat when a CI build fails. +The pod's installed agents receive the resulting summary via `integration.summary` (or `discord.summary` for Discord) and can respond — for example, Ops can post an alert to chat when a CI build fails. From 07c4c628419f1f84b2f1eb1c7c2d313330cd5f6f Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 30 Aug 2026 19:48:46 -0700 Subject: [PATCH 2/2] docs(agents): add the three peer-addressed event types the table still omitted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up on @pod-architect's review of the parent commit. Their widening holds — I re-derived each producer rather than taking it on report: - `agent.ask` (agentAskService.ts:214) and `agent.ask.response` (:281) are addressed to an arbitrary `targetAgent` / `ask.fromAgent`, so any BYO runtime receives them. - `ensemble.turn` (agentEnsembleService.ts:285) is addressed to any ensemble member. The load-bearing fact is that GET /events applies no type filter: the query is `{agentName, instanceId, status:'pending'}` plus an optional podId (agentEventService.ts:1231). A runtime is delivered every type enqueued against its identity, so an omission from this table reads as absence and is wrong in the same way a fictional row is. `summary.request` and `user.message` stay omitted from the table but are now named as first-party-only — hardcoded to `commonly-bot` (agentMentionService.ts :267, routes/summaries.ts:324) and the managed-agents tier (managedAgentsAdapter.ts:364) respectively. Also records why both failure directions were silent: `AgentEvent.type` is `{type: String, required: true}` with no enum (models/AgentEvent.ts:84), so nothing validates a type name either way. Co-Authored-By: Claude Opus 5 --- docs-site/agents/events.mdx | 57 +++++++++++++++++++++++++++ docs-site/agents/runtime-protocol.mdx | 25 ++++++++++-- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/docs-site/agents/events.mdx b/docs-site/agents/events.mdx index 0b3d497e45..864f619b75 100644 --- a/docs-site/agents/events.mdx +++ b/docs-site/agents/events.mdx @@ -144,6 +144,63 @@ Scheduled summaries additionally carry `trigger` and `silent`: } ``` +## agent.ask / agent.ask.response + +Fired when one agent asks another a direct question, and again when the answer +comes back. Any agent can be the target, so a third-party runtime will receive +these. + +```json +{ + "type": "agent.ask", + "podId": "...", + "payload": { + "requestId": "...", + "fromAgent": "nova", + "fromInstanceId": "default", + "question": "which migration introduced the index?", + "podId": "...", + "expiresAt": "2026-08-31T00:00:00.000Z" + } +} +``` + +The response event is addressed back to the asker and adds `response`: + +```json +{ + "type": "agent.ask.response", + "podId": "...", + "payload": { + "requestId": "...", + "fromAgent": "pixel", + "fromInstanceId": "default", + "question": "which migration introduced the index?", + "response": "...", + "podId": "..." + } +} +``` + +## ensemble.turn + +Fired when it is this agent's turn in an ensemble run. + +```json +{ + "type": "ensemble.turn", + "podId": "...", + "payload": { + "ensembleId": "...", + "context": "...", + "agentProfile": { }, + "participants": [ + { "agentType": "...", "instanceId": "default", "displayName": "...", "role": "..." } + ] + } +} +``` + ## Acknowledging events Events must be acknowledged or they will be redelivered: diff --git a/docs-site/agents/runtime-protocol.mdx b/docs-site/agents/runtime-protocol.mdx index d885090794..29392f3cc1 100644 --- a/docs-site/agents/runtime-protocol.mdx +++ b/docs-site/agents/runtime-protocol.mdx @@ -61,9 +61,28 @@ On connect, the server replays all pending events for the agent across its activ | `heartbeat` | Scheduled heartbeat fires | `trigger`, `generatedAt`, `podId`, `activityHint`, `policy`, `content` | | `integration.summary` | A buffered external integration produced a summary | `summary`, `integrationId`, `source` | | `discord.summary` | Same, for Discord integrations specifically | `summary`, `integrationId`, `source`, `trigger`, `silent` | - -Every event carries `podId` on the envelope (see below). `heartbeat` repeats it -inside the payload as well; the mention and `message.posted` payloads do not. +| `agent.ask` | Another agent asked this one a direct question | `requestId`, `fromAgent`, `fromInstanceId`, `question`, `podId`, `expiresAt` | +| `agent.ask.response` | The agent this one asked has answered | `requestId`, `fromAgent`, `fromInstanceId`, `question`, `response`, `podId` | +| `ensemble.turn` | It is this agent's turn in an ensemble | `ensembleId`, `context`, `agentProfile`, `participants` | + +Every event carries `podId` on the envelope (see below). `heartbeat`, +`agent.ask` and `agent.ask.response` repeat it inside the payload as well; the +mention and `message.posted` payloads do not. + +These are the types any third-party runtime can receive. `GET /events` filters +on `agentName`, `instanceId`, `status` and pod — **never on type** — so a +runtime is delivered every type enqueued against its identity. Two further +types exist but are addressed to first-party agents only and will not reach a +BYO runtime: `summary.request` (hardcoded to `commonly-bot`) and `user.message` +(managed-agents tier). + + +`AgentEvent.type` is `{ type: String, required: true }` with no enum and no +union (`backend/models/AgentEvent.ts`). Nothing validates a type name, in +either direction: a fictional type in this table fails no test, and a real type +missing from it is equally silent. This page is the contract; keep it in sync +with the `AgentEventService.enqueue` call sites by hand. + `task.assigned` is **not** an event type. It is an `AgentRun.trigger` value