@cursor/july 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/dist/bin/agent-serve.js +31 -2
  2. package/dist/channels/slack/channel-watch.d.ts +6 -1
  3. package/dist/channels/slack/channel-watch.d.ts.map +1 -1
  4. package/dist/channels/slack/channel-watch.js +22 -1
  5. package/dist/channels/slack/cursor-account.d.ts +4 -0
  6. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  7. package/dist/channels/slack/cursor-account.js +12 -2
  8. package/dist/channels/slack/defaults.d.ts +10 -5
  9. package/dist/channels/slack/defaults.d.ts.map +1 -1
  10. package/dist/channels/slack/defaults.js +39 -17
  11. package/dist/channels/slack/dispatch.d.ts +18 -0
  12. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  13. package/dist/channels/slack/dispatch.js +141 -38
  14. package/dist/channels/slack/inbound.d.ts +13 -3
  15. package/dist/channels/slack/inbound.d.ts.map +1 -1
  16. package/dist/channels/slack/inbound.js +17 -9
  17. package/dist/channels/slack/index.d.ts +4 -0
  18. package/dist/channels/slack/index.d.ts.map +1 -1
  19. package/dist/channels/slack/index.js +4 -0
  20. package/dist/channels/slack/live-delivery.d.ts.map +1 -1
  21. package/dist/channels/slack/live-delivery.js +1 -0
  22. package/dist/channels/slack/manifest.d.ts.map +1 -1
  23. package/dist/channels/slack/manifest.js +3 -0
  24. package/dist/channels/slack/nudge-carrier.d.ts +19 -0
  25. package/dist/channels/slack/nudge-carrier.d.ts.map +1 -0
  26. package/dist/channels/slack/nudge-carrier.js +166 -0
  27. package/dist/channels/slack/nudge-store.d.ts +61 -0
  28. package/dist/channels/slack/nudge-store.d.ts.map +1 -0
  29. package/dist/channels/slack/nudge-store.js +266 -0
  30. package/dist/channels/slack/post-update-delivery.js +318 -0
  31. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  32. package/dist/channels/slack/slack-channel.js +74 -25
  33. package/dist/channels/slack/stop.d.ts +14 -0
  34. package/dist/channels/slack/stop.d.ts.map +1 -0
  35. package/dist/channels/slack/stop.js +22 -0
  36. package/dist/channels/slack/thread-context.d.ts +81 -0
  37. package/dist/channels/slack/thread-context.d.ts.map +1 -0
  38. package/dist/channels/slack/thread-context.js +470 -0
  39. package/dist/channels/slack/types.d.ts +27 -0
  40. package/dist/channels/slack/types.d.ts.map +1 -1
  41. package/dist/docs/404.html +2 -2
  42. package/dist/docs/ab.html +4 -4
  43. package/dist/docs/assets/{app.COTN7wgo.js → app.Ciosf24M.js} +1 -1
  44. package/dist/docs/assets/chunks/@localSearchIndexroot.D154Detj.js +1 -0
  45. package/dist/docs/assets/chunks/{VPLocalSearchBox.BW3TBdT0.js → VPLocalSearchBox.BcRDly7n.js} +1 -1
  46. package/dist/docs/assets/chunks/{theme.BEJW0vE7.js → theme.CMpbjdfx.js} +2 -2
  47. package/dist/docs/assets/{deployment.md.BtfEsc9S.js → deployment.md.DC5-ImcM.js} +1 -1
  48. package/dist/docs/assets/{example-agents_approval-buddy.md.8R5phXb5.js → example-agents_approval-buddy.md.DFGBYLcc.js} +2 -2
  49. package/dist/docs/assets/{example-agents_approval-buddy.md.8R5phXb5.lean.js → example-agents_approval-buddy.md.DFGBYLcc.lean.js} +1 -1
  50. package/dist/docs/assets/example-agents_index.md.BPXjUtRB.js +2 -0
  51. package/dist/docs/assets/example-agents_index.md.BPXjUtRB.lean.js +1 -0
  52. package/dist/docs/assets/example-agents_oncall.md.BG_sUMly.js +10 -0
  53. package/dist/docs/assets/example-agents_oncall.md.BG_sUMly.lean.js +1 -0
  54. package/dist/docs/assets/{example-agents_security-reviewer.md.D2rtwDTO.js → example-agents_security-reviewer.md.CE9v-aKm.js} +2 -2
  55. package/dist/docs/assets/{example-agents_security-reviewer.md.D2rtwDTO.lean.js → example-agents_security-reviewer.md.CE9v-aKm.lean.js} +1 -1
  56. package/dist/docs/assets/{guides_slack.md.CCwqHvSV.js → guides_slack.md.FWale6Ip.js} +4 -3
  57. package/dist/docs/assets/{guides_slack.md.CCwqHvSV.lean.js → guides_slack.md.FWale6Ip.lean.js} +1 -1
  58. package/dist/docs/assets/{reference_sessions.md.DdjWabnN.js → reference_sessions.md.DLd6mvbv.js} +2 -2
  59. package/dist/docs/assets/{reference_sessions.md.DdjWabnN.lean.js → reference_sessions.md.DLd6mvbv.lean.js} +1 -1
  60. package/dist/docs/assets/{storage.md.CVnInNiN.js → storage.md.DRTdnFvd.js} +9 -2
  61. package/dist/docs/assets/{storage.md.CVnInNiN.lean.js → storage.md.DRTdnFvd.lean.js} +1 -1
  62. package/dist/docs/building-with-agents.html +4 -4
  63. package/dist/docs/concepts.html +4 -4
  64. package/dist/docs/deployment.html +6 -6
  65. package/dist/docs/evals.html +4 -4
  66. package/dist/docs/example-agents/approval-buddy.html +5 -5
  67. package/dist/docs/example-agents/benny.html +5 -5
  68. package/dist/docs/example-agents/bugbot.html +5 -5
  69. package/dist/docs/example-agents/codebase-wiki.html +4 -4
  70. package/dist/docs/example-agents/codeowners-review.html +4 -4
  71. package/dist/docs/example-agents/concierge.html +4 -4
  72. package/dist/docs/example-agents/fsd.html +4 -4
  73. package/dist/docs/example-agents/index.html +6 -6
  74. package/dist/docs/example-agents/knowledge-base.html +4 -4
  75. package/dist/docs/example-agents/oncall.html +34 -0
  76. package/dist/docs/example-agents/security-reviewer.html +6 -6
  77. package/dist/docs/example-agents/slack-agent.html +4 -4
  78. package/dist/docs/example-agents/weather-agent.html +4 -4
  79. package/dist/docs/guides/agent-to-agent.html +4 -4
  80. package/dist/docs/guides/cloud-runtime.html +4 -4
  81. package/dist/docs/guides/github.html +4 -4
  82. package/dist/docs/guides/human-in-the-loop.html +4 -4
  83. package/dist/docs/guides/slack.html +7 -6
  84. package/dist/docs/guides/webhooks.html +4 -4
  85. package/dist/docs/hashmap.json +1 -1
  86. package/dist/docs/hillclimbing.html +4 -4
  87. package/dist/docs/index.html +4 -4
  88. package/dist/docs/quickstart.html +4 -4
  89. package/dist/docs/reference/agent-config.html +4 -4
  90. package/dist/docs/reference/channels.html +4 -4
  91. package/dist/docs/reference/cli.html +4 -4
  92. package/dist/docs/reference/connections.html +4 -4
  93. package/dist/docs/reference/hooks.html +4 -4
  94. package/dist/docs/reference/http-api.html +4 -4
  95. package/dist/docs/reference/instructions.html +4 -4
  96. package/dist/docs/reference/playground.html +4 -4
  97. package/dist/docs/reference/project-layout.html +4 -4
  98. package/dist/docs/reference/schedules.html +4 -4
  99. package/dist/docs/reference/sessions.html +5 -5
  100. package/dist/docs/reference/skills.html +4 -4
  101. package/dist/docs/reference/subagents.html +4 -4
  102. package/dist/docs/reference/tools.html +4 -4
  103. package/dist/docs/scaffolding-agents.html +4 -4
  104. package/dist/docs/storage.html +13 -6
  105. package/dist/docs/troubleshooting.html +4 -4
  106. package/dist/internal/cli-deploy.d.ts.map +1 -1
  107. package/dist/internal/cli-deploy.js +45 -18
  108. package/dist/internal/cli-mcp.d.ts +64 -0
  109. package/dist/internal/cli-mcp.d.ts.map +1 -0
  110. package/dist/internal/cli-mcp.js +570 -0
  111. package/dist/internal/deploy-client.d.ts +7 -1
  112. package/dist/internal/deploy-client.d.ts.map +1 -1
  113. package/dist/internal/deploy-client.js +3 -0
  114. package/dist/internal/deploy-source.d.ts +3 -0
  115. package/dist/internal/deploy-source.d.ts.map +1 -1
  116. package/dist/internal/deploy-source.js +25 -7
  117. package/dist/internal/discovery.js +1 -1
  118. package/dist/internal/distribution.d.ts.map +1 -1
  119. package/dist/internal/distribution.js +3 -0
  120. package/dist/internal/host-kv.d.ts +17 -0
  121. package/dist/internal/host-kv.d.ts.map +1 -0
  122. package/dist/internal/host-kv.js +77 -0
  123. package/dist/internal/hosting.d.ts +12 -2
  124. package/dist/internal/hosting.d.ts.map +1 -1
  125. package/dist/internal/hosting.js +21 -4
  126. package/dist/internal/json-dir-store.js +100 -0
  127. package/dist/internal/logs-client.d.ts +16 -2
  128. package/dist/internal/logs-client.d.ts.map +1 -1
  129. package/dist/internal/logs-client.js +51 -16
  130. package/dist/internal/server.d.ts.map +1 -1
  131. package/dist/internal/server.js +8 -0
  132. package/dist/internal/session-engine.d.ts +42 -7
  133. package/dist/internal/session-engine.d.ts.map +1 -1
  134. package/dist/internal/session-engine.js +276 -66
  135. package/dist/internal/storage-coordinator.d.ts +18 -1
  136. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  137. package/dist/internal/storage-coordinator.js +43 -0
  138. package/dist/internal/workspace.d.ts +19 -0
  139. package/dist/internal/workspace.d.ts.map +1 -1
  140. package/dist/internal/workspace.js +47 -13
  141. package/dist/playground/assets/{index-72vCOBWO.js → index-C0-QEG4O.js} +42 -42
  142. package/dist/playground/assets/index-z3JY9O2x.css +1 -0
  143. package/dist/playground/index.html +2 -2
  144. package/dist/storage-backends/cursor-hosted.d.ts +65 -0
  145. package/dist/storage-backends/cursor-hosted.d.ts.map +1 -0
  146. package/dist/storage-backends/cursor-hosted.js +203 -0
  147. package/dist/storage-backends/file-kv.d.ts +42 -0
  148. package/dist/storage-backends/file-kv.d.ts.map +1 -0
  149. package/dist/storage-backends/file-kv.js +190 -0
  150. package/dist/storage-backends/postgres-kv.d.ts +82 -0
  151. package/dist/storage-backends/postgres-kv.d.ts.map +1 -0
  152. package/dist/storage-backends/postgres-kv.js +185 -0
  153. package/dist/storage.d.ts +10 -1
  154. package/dist/storage.d.ts.map +1 -1
  155. package/dist/storage.js +10 -1
  156. package/dist/types.d.ts +79 -0
  157. package/dist/types.d.ts.map +1 -1
  158. package/docs/.vitepress/cache/deps/package.json +3 -0
  159. package/docs/deployment.md +7 -3
  160. package/docs/example-agents/approval-buddy.md +1 -1
  161. package/docs/example-agents/index.md +15 -10
  162. package/docs/example-agents/oncall.md +216 -0
  163. package/docs/example-agents/security-reviewer.md +5 -5
  164. package/docs/guides/slack.md +11 -2
  165. package/docs/reference/sessions.md +13 -4
  166. package/docs/storage.md +41 -0
  167. package/package.json +22 -1
  168. package/skills/framework-map/SKILL.md +6 -4
  169. package/src/bin/agent-serve.ts +32 -2
  170. package/src/channels/slack/channel-watch.ts +28 -1
  171. package/src/channels/slack/cursor-account.ts +20 -0
  172. package/src/channels/slack/defaults.ts +51 -20
  173. package/src/channels/slack/dispatch.ts +215 -40
  174. package/src/channels/slack/inbound.ts +30 -10
  175. package/src/channels/slack/index.ts +4 -0
  176. package/src/channels/slack/live-delivery.ts +1 -0
  177. package/src/channels/slack/manifest.ts +3 -0
  178. package/src/channels/slack/nudge-carrier.ts +182 -0
  179. package/src/channels/slack/nudge-store.ts +308 -0
  180. package/src/channels/slack/slack-channel.ts +106 -31
  181. package/src/channels/slack/stop.ts +25 -0
  182. package/src/channels/slack/thread-context.ts +572 -0
  183. package/src/channels/slack/types.ts +27 -0
  184. package/src/internal/cli-deploy.ts +58 -20
  185. package/src/internal/cli-mcp.ts +708 -0
  186. package/src/internal/deploy-client.ts +11 -0
  187. package/src/internal/deploy-source.ts +24 -6
  188. package/src/internal/discovery.ts +1 -1
  189. package/src/internal/distribution.ts +3 -0
  190. package/src/internal/host-kv.ts +85 -0
  191. package/src/internal/hosting.ts +24 -4
  192. package/src/internal/logs-client.ts +67 -21
  193. package/src/internal/server.ts +31 -0
  194. package/src/internal/session-engine.ts +379 -61
  195. package/src/internal/storage-coordinator.ts +41 -0
  196. package/src/internal/workspace.ts +48 -11
  197. package/src/storage-backends/cursor-hosted.ts +246 -0
  198. package/src/storage-backends/file-kv.ts +221 -0
  199. package/src/storage-backends/postgres-kv.ts +253 -0
  200. package/src/storage.ts +11 -1
  201. package/src/types.ts +83 -0
  202. package/dist/docs/assets/chunks/@localSearchIndexroot.B7UcKvIn.js +0 -1
  203. package/dist/docs/assets/example-agents_index.md.CRqJlnIf.js +0 -2
  204. package/dist/docs/assets/example-agents_index.md.CRqJlnIf.lean.js +0 -1
  205. package/dist/internal/persistence-coordinator.d.ts +0 -127
  206. package/dist/internal/persistence-coordinator.d.ts.map +0 -1
  207. package/dist/persistence.d.ts +0 -184
  208. package/dist/persistence.d.ts.map +0 -1
  209. package/dist/playground/assets/index-BjnMwYoR.css +0 -1
  210. /package/dist/docs/assets/{deployment.md.BtfEsc9S.lean.js → deployment.md.DC5-ImcM.lean.js} +0 -0
@@ -0,0 +1,10 @@
1
+ import{_ as t,c as a,o as s,ag as i}from"./chunks/framework.CAZyNGu9.js";const k=JSON.parse('{"title":"Investigate every alert in its own Slack thread","description":"Watch a bot-fed alerts channel, react when the agent locks in, coalesce thread chatter behind a quiet window, and let the agent schedule its own re-checks.","frontmatter":{"title":"Investigate every alert in its own Slack thread","description":"Watch a bot-fed alerts channel, react when the agent locks in, coalesce thread chatter behind a quiet window, and let the agent schedule its own re-checks."},"headers":[],"relativePath":"example-agents/oncall.md","filePath":"example-agents/oncall.md"}'),n={name:"example-agents/oncall.md"};function l(o,e,h,r,d,c){return s(),a("div",null,[...e[0]||(e[0]=[i(`<h1 id="investigate-every-alert-in-its-own-slack-thread" tabindex="-1">Investigate every alert in its own Slack thread <a class="header-anchor" href="#investigate-every-alert-in-its-own-slack-thread" aria-label="Permalink to &quot;Investigate every alert in its own Slack thread&quot;">​</a></h1><p>This agent is an on-call teammate. Alert feeds post into an alerts channel as bots. Each new alert dispatches an investigation session pinned to that post&#39;s thread: the agent reacts 👀 the moment it locks in, investigates immediately, and posts brief findings backed by evidence it observed. Replies in the thread reach it only after the thread has been quiet for about a minute, and reminder tools let it wake itself later to re-check a baseline or confirm an alert cleared.</p><p>Use this example when alerts land in Slack and you want one thread-scoped investigation per alert, with an agent that paces its own engagement instead of answering every message.</p><p><a href="./../../examples/oncall/">Browse the current alert-investigator source.</a></p><h2 id="follow-an-alert" tabindex="-1">Follow an alert <a class="header-anchor" href="#follow-an-alert" aria-label="Permalink to &quot;Follow an alert&quot;">​</a></h2><ol><li>An alert feed (Alertmanager, PagerDuty, Datadog) posts a new top-level message in the watched alerts channel.</li><li>The channel watch accepts it. <code>includeBotPosts</code> lets bot authors through; the agent&#39;s own posts always stay dropped.</li><li>The handler reacts 👀 on the alert post and sets &quot;Investigating…&quot; typing. The reaction is the lock-in signal: this alert has an owner.</li><li>Agentkit creates a session keyed to the alert&#39;s thread and dispatches immediately. New alerts get no debounce.</li><li>The agent reads the alert, gathers evidence, and posts findings to the thread once it has a hypothesis.</li><li>People discuss in the thread. Replies buffer per thread and dispatch as one coalesced follow-up after roughly a minute of quiet.</li><li>The agent arms reminders for anything that needs time and posts interim updates when new evidence changes the picture.</li></ol><p>Mentions and DMs skip the watch entirely and behave like ordinary chat.</p><h2 id="map-the-files" tabindex="-1">Map the files <a class="header-anchor" href="#map-the-files" aria-label="Permalink to &quot;Map the files&quot;">​</a></h2><table tabindex="0"><thead><tr><th>File</th><th>Purpose</th></tr></thead><tbody><tr><td><a href="../../examples/oncall/agent/agent.ts"><code>agent/agent.ts</code></a></td><td>Names the agent and keeps harness workspaces outside any monorepo checkout.</td></tr><tr><td><a href="./../../examples/oncall/agent/instructions.html"><code>agent/instructions.md</code></a></td><td>Engagement rules, the investigation loop, and the message discipline.</td></tr><tr><td><a href="../../examples/oncall/agent/channels/slack-app.ts"><code>agent/channels/slack-app.ts</code></a></td><td>Dedicated Socket Mode app: watch configuration and handler wiring.</td></tr><tr><td><a href="../../examples/oncall/agent/lib/alert-watch.ts"><code>agent/lib/alert-watch.ts</code></a></td><td>The engagement policy: lock in on new alerts, coalesce replies.</td></tr><tr><td><a href="../../examples/oncall/agent/lib/thread-debounce.ts"><code>agent/lib/thread-debounce.ts</code></a></td><td>Per-thread quiet window.</td></tr><tr><td><a href="../../examples/oncall/agent/lib/alerts.ts"><code>agent/lib/alerts.ts</code></a></td><td>Dispatch classification, prompt building, and thread addressing.</td></tr><tr><td><a href="../../examples/oncall/agent/lib/slack-api.ts"><code>agent/lib/slack-api.ts</code></a></td><td>Reactions and thread posts on this agent&#39;s own token pair.</td></tr><tr><td><a href="../../examples/oncall/agent/tools/reminders_create.ts"><code>agent/tools/reminders_create.ts</code></a></td><td>Self-scheduled wakes bound to the thread (plus <code>reminders_list</code> and <code>reminders_cancel</code>).</td></tr><tr><td><a href="../../examples/oncall/agent/tools/post_thread_update.ts"><code>agent/tools/post_thread_update.ts</code></a></td><td>Interim updates to the thread mid-turn.</td></tr><tr><td><a href="../../examples/oncall/evals/smoke.eval.ts"><code>evals/smoke.eval.ts</code></a></td><td>Checks identity and the reminder-tool route.</td></tr></tbody></table><h2 id="let-bot-posts-through-the-watch" tabindex="-1">Let bot posts through the watch <a class="header-anchor" href="#let-bot-posts-through-the-watch" aria-label="Permalink to &quot;Let bot posts through the watch&quot;">​</a></h2><p>Channel watching drops bot-authored posts by default so two agents can never feed each other. Alert channels invert the assumption: the posts worth watching come from bots. <code>channelPosts.includeBotPosts</code> opts in per channel:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">engagement</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
2
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> channelPosts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: {</span></span>
3
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> allow</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;#alerts&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
4
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> posts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;all&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
5
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> includeBotPosts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
6
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
7
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">},</span></span></code></pre></div><p>Loop safety survives the opt-in. The pack matches the watching app&#39;s own posts by the <code>bot_id</code> and bot user id from <code>auth.test</code> and drops them, so the agent&#39;s findings never re-dispatch it. Posts that mention the bot stay on the mention path.</p><p><code>posts: &quot;all&quot;</code> also delivers thread replies. The handler, not the pack, decides their pace.</p><h2 id="pace-the-engagement" tabindex="-1">Pace the engagement <a class="header-anchor" href="#pace-the-engagement" aria-label="Permalink to &quot;Pace the engagement&quot;">​</a></h2><p>The example runs two rhythms:</p><ul><li>A new alert dispatches immediately.</li><li>Thread replies produce one engagement per lull.</li></ul><p>The pack&#39;s <code>debounceMs</code> is per message; it exists to let edits settle. This agent needs a per-thread window instead, so the handler owns it (<a href="../../examples/oncall/agent/lib/thread-debounce.ts"><code>lib/thread-debounce.ts</code></a>). Every reply restarts a 60-second timer keyed by thread. Superseded waiters resolve <code>null</code> and the handler returns <code>null</code> for them. When the thread goes quiet, the newest waiter receives the whole batch and dispatches one follow-up that lists every message with mentionable attribution.</p><p>Two details make the window matter. A follow-up that arrives while a turn runs preempts that turn (latest message wins), so engaging per message would keep cancelling the investigation. And @mentions bypass the window through Slack&#39;s mention path, so a person who needs the agent now still gets it now.</p><h2 id="schedule-your-own-re-checks" tabindex="-1">Schedule your own re-checks <a class="header-anchor" href="#schedule-your-own-re-checks" aria-label="Permalink to &quot;Schedule your own re-checks&quot;">​</a></h2><p>Investigations rarely finish in one pass. A baseline comparison needs 20 minutes of data. An alert that cleared may re-fire. The example hands the model three tools over <code>host.reminders</code>:</p><ul><li><code>reminders_create</code> arms a one-shot (<code>delay: &quot;20m&quot;</code>) or recurring (<code>every: &quot;30m&quot;</code> with a plain-language stop condition) wake bound to the thread&#39;s conversation.</li><li><code>reminders_list</code> shows the thread&#39;s standing watches.</li><li><code>reminders_cancel</code> disarms one, and refuses ids that belong to another thread&#39;s conversation.</li></ul><p>When a reminder fires, its prompt returns to the same session as a follow-up turn, and the reply lands in the alert thread. The instructions keep wake prompts generic (re-read live state instead of replaying stale numbers) and wake replies to one line, for example &quot;re-checked p99 on api-gateway: 120ms, back at baseline, cancelling the watch.&quot;</p><p>Keep these tool filenames if you copy the design: the framework&#39;s reminder fire prompt tells the model to call <code>reminders_cancel</code> by name when a stop condition is set.</p><h2 id="alert-people-mid-investigation" tabindex="-1">Alert people mid-investigation <a class="header-anchor" href="#alert-people-mid-investigation" aria-label="Permalink to &quot;Alert people mid-investigation&quot;">​</a></h2><p>The final reply of each turn posts to the thread on its own. <code>post_thread_update</code> covers evidence that shouldn&#39;t wait for the turn to finish: it posts a one-or-two-sentence update through the agent&#39;s token, with <code>&lt;@USERID&gt;</code> mentions for the people who need to act. The instructions restrict it to changes in hypothesis, severity, or blast radius. Progress narration doesn&#39;t qualify.</p><h2 id="connect-the-slack-app" tabindex="-1">Connect the Slack app <a class="header-anchor" href="#connect-the-slack-app" aria-label="Permalink to &quot;Connect the Slack app&quot;">​</a></h2><p>Channel watching is Socket Mode only, so this example uses a dedicated app:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> slack</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> init</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> examples/oncall</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --name</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;Oncall&quot;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --channel-posts</span></span>
8
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> slack</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> doctor</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --prefix</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> ONCALL</span></span></code></pre></div><p>The generated manifest subscribes to <code>message.channels</code> and <code>message.groups</code> and includes <code>reactions:write</code> for the lock-in reaction. Install the app, export <code>ONCALL_SLACK_BOT_TOKEN</code> and <code>ONCALL_SLACK_APP_TOKEN</code>, and invite the bot to each watched channel.</p><p><code>ONCALL_ALERTS_CHANNELS</code> sets the watch list as comma-separated ids or <code>#names</code>. It defaults to <code>#alerts</code>.</p><p>Wire observability MCP servers under <code>agent/mcp-connections/</code> so evidence gathering reaches your logs, metrics, and dashboards. The example ships none; without them the agent works from the alert text, its links, and the thread.</p><h2 id="validate-and-start-the-server" tabindex="-1">Validate and start the server <a class="header-anchor" href="#validate-and-start-the-server" aria-label="Permalink to &quot;Validate and start the server&quot;">​</a></h2><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> validate</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> examples/oncall</span></span>
9
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> info</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> examples/oncall</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --json</span></span>
10
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> examples/oncall</span></span></code></pre></div><p>The info output lists four server tools and the watched channel on the <code>slack-app</code> channel. Missing tokens leave that channel idle without stopping the server.</p><p>In dev mode, reminder timers don&#39;t auto-fire. List and fire them by hand through the dev routes described in <a href="./../reference/schedules.html#dispatch-and-dev-mode">Schedules and reminders</a>.</p><h2 id="test-the-policy-without-slack" tabindex="-1">Test the policy without Slack <a class="header-anchor" href="#test-the-policy-without-slack" aria-label="Permalink to &quot;Test the policy without Slack&quot;">​</a></h2><p>The engagement policy is plain code with unit tests:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">pnpm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> exec</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> vitest</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> examples/oncall</span></span></code></pre></div><p>The integration test drives a synthetic Events API delivery through the real parse, watch, and dispatch plumbing. It asserts a bot alert dispatches pinned to its thread after the lock-in reaction, the agent&#39;s own posts never loop, and replies coalesce behind the quiet window.</p><p>The smoke eval spends a model turn:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> eval</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> examples/oncall</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> smoke</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --json</span></span></code></pre></div><p>It checks identity and the reminder-tool route lexically. It doesn&#39;t prove Slack delivery or reaction behavior; the unit tests cover the dispatch side, and a live check needs the dedicated app connected.</p><h2 id="build-an-alert-investigator" tabindex="-1">Build an alert investigator <a class="header-anchor" href="#build-an-alert-investigator" aria-label="Permalink to &quot;Build an alert investigator&quot;">​</a></h2><p>Use this structure when a bot feed should drive thread-scoped work:</p><ol><li>Watch the feed channel with <code>includeBotPosts: true</code> and a narrow allowlist.</li><li>Acknowledge on the triggering post before dispatching, so people see ownership without opening the thread.</li><li>Dispatch new items immediately; coalesce thread chatter behind a per-thread quiet window.</li><li>Give the agent reminder tools for anything that needs time, and make cancel discipline part of the instructions.</li><li>Keep every posted message brief and tied to evidence the agent saw.</li></ol><h2 id="where-to-go-next" tabindex="-1">Where to go next <a class="header-anchor" href="#where-to-go-next" aria-label="Permalink to &quot;Where to go next&quot;">​</a></h2><ul><li><a href="./../guides/slack.html">Slack</a></li><li><a href="./../reference/schedules.html">Schedules and reminders</a></li><li><a href="./../reference/tools.html">Tools</a></li><li><a href="./benny.html">Playbook router</a> for the human-post variant of channel watching</li></ul>`,48)])])}const g=t(n,[["render",l]]);export{k as __pageData,g as default};
@@ -0,0 +1 @@
1
+ import{_ as t,c as a,o as s,ag as i}from"./chunks/framework.CAZyNGu9.js";const k=JSON.parse('{"title":"Investigate every alert in its own Slack thread","description":"Watch a bot-fed alerts channel, react when the agent locks in, coalesce thread chatter behind a quiet window, and let the agent schedule its own re-checks.","frontmatter":{"title":"Investigate every alert in its own Slack thread","description":"Watch a bot-fed alerts channel, react when the agent locks in, coalesce thread chatter behind a quiet window, and let the agent schedule its own re-checks."},"headers":[],"relativePath":"example-agents/oncall.md","filePath":"example-agents/oncall.md"}'),n={name:"example-agents/oncall.md"};function l(o,e,h,r,d,c){return s(),a("div",null,[...e[0]||(e[0]=[i("",48)])])}const g=t(n,[["render",l]]);export{k as __pageData,g as default};
@@ -1,4 +1,4 @@
1
- import{_ as s,c as t,o as a,ag as i}from"./chunks/framework.CAZyNGu9.js";const u=JSON.parse('{"title":"Run staged security reviews from GitHub events","description":"Orchestrate parallel reviewer agents, streamed triage, deterministic stages, live playground progress, durable sessions, and severity A/B metrics.","frontmatter":{"title":"Run staged security reviews from GitHub events","description":"Orchestrate parallel reviewer agents, streamed triage, deterministic stages, live playground progress, durable sessions, and severity A/B metrics."},"headers":[],"relativePath":"example-agents/security-reviewer.md","filePath":"example-agents/security-reviewer.md"}'),r={name:"example-agents/security-reviewer.md"};function n(l,e,o,h,d,p){return a(),t("div",null,[...e[0]||(e[0]=[i(`<h1 id="run-staged-security-reviews-from-github-events" tabindex="-1">Run staged security reviews from GitHub events <a class="header-anchor" href="#run-staged-security-reviews-from-github-events" aria-label="Permalink to &quot;Run staged security reviews from GitHub events&quot;">​</a></h1><p>Security Reviewer turns a pull request into a staged host-side review. One tool prepares the diff and selects modules. A second fans out specialized reviewers and triages candidates as they arrive. A third deduplicates the confirmed findings, writes artifacts, and may publish a GitHub review.</p><p>Use this example when the workflow needs several model workers, but the host must own orchestration, progress, artifacts, and the final write.</p><p><a href="./../../examples/security-reviewer/">Browse the Security Reviewer source.</a></p><h2 id="run-a-three-stage-host-pipeline" tabindex="-1">Run a three-stage host pipeline <a class="header-anchor" href="#run-a-three-stage-host-pipeline" aria-label="Permalink to &quot;Run a three-stage host pipeline&quot;">​</a></h2><p>Security Reviewer is a pipeline, not one long agent turn:</p><table tabindex="0"><thead><tr><th>Stage</th><th>Tool</th><th>Result</th></tr></thead><tbody><tr><td>Prepare</td><td><code>prepare_review</code></td><td>Fetch metadata and diff, create a <code>runId</code>, and select security modules.</td></tr><tr><td>Review and triage</td><td><code>run_reviewers</code></td><td>Run module reviewers in parallel and start triage as each candidate arrives.</td></tr><tr><td>Finalize</td><td><code>finalize_review</code></td><td>Apply thresholds, deduplicate findings, write artifacts, and optionally post a review.</td></tr></tbody></table><p><code>run_triage</code> remains available as a compatibility stage. In the normal flow, triage has already completed inside <code>run_reviewers</code>, so it reports existing results. If candidates exist without triage output, it starts triage workers and writes their state.</p><p>The configured root agent chooses and sequences tools in chat. The review workers use a model selected by the host pipeline. They are created programmatically with the agent SDK, not discovered from <code>agent/subagents/</code>.</p><h2 id="follow-a-github-wake" tabindex="-1">Follow a GitHub wake <a class="header-anchor" href="#follow-a-github-wake" aria-label="Permalink to &quot;Follow a GitHub wake&quot;">​</a></h2><ol><li>A non-draft <code>pull_request.opened</code> or <code>pull_request.synchronize</code> event arrives for an allowlisted repository.</li><li>The GitHub channel returns a host <code>{ task }</code>, so the webhook gets a 202 response before the long review starts.</li><li>Host code tries to post a pending commit status and creates a playground session.</li><li>The root model sends one acknowledgement. It doesn&#39;t run review tools on this path.</li><li>The task calls <code>prepare_review</code>, <code>run_reviewers</code>, and <code>finalize_review</code> deterministically inside that session.</li><li>Reviewer candidates stream into duplicate gating and triage.</li><li>Finalization writes artifacts and tries to post the GitHub review.</li><li>The host tries to set a success commit status when no findings remain, a failure status when findings remain, or an error status when the pipeline throws.</li><li>The final response is appended to the session as an assistant message.</li></ol><p>The session records each stage as a normal tool event, even though host code selected the tools.</p><p>Review and status posting are best-effort. The channel chooses its final status from the finding count even when the review posting result says <code>posted: false</code>.</p><h2 id="map-the-framework-features" tabindex="-1">Map the framework features <a class="header-anchor" href="#map-the-framework-features" aria-label="Permalink to &quot;Map the framework features&quot;">​</a></h2><table tabindex="0"><thead><tr><th>Capability</th><th>Source</th><th>Role</th></tr></thead><tbody><tr><td>Root agent</td><td><a href="../../examples/security-reviewer/agent/agent.ts"><code>agent/agent.ts</code></a>, <a href="./../../examples/security-reviewer/agent/instructions.html"><code>agent/instructions.md</code></a></td><td>Configure local chat and explain the three-stage contract.</td></tr><tr><td>Server tools</td><td><a href="./../../examples/security-reviewer/agent/tools/"><code>agent/tools/</code></a></td><td>Expose each review stage to chat and host orchestration.</td></tr><tr><td>GitHub channel</td><td><a href="../../examples/security-reviewer/agent/channels/github.ts"><code>agent/channels/github.ts</code></a></td><td>Filter wakes, run background tasks, and publish status.</td></tr><tr><td>Progress channel</td><td><a href="../../examples/security-reviewer/agent/channels/asr-progress.ts"><code>agent/channels/asr-progress.ts</code></a></td><td>Serve live reviewer and triage state by <code>runId</code>.</td></tr><tr><td>Playground renderer</td><td><a href="./../../examples/security-reviewer/agent/playground/tools/run_reviewers.tsx.html"><code>agent/playground/tools/run_reviewers.tsx</code></a></td><td>Replace the generic tool chip with live module rows.</td></tr><tr><td>SDK review pipeline</td><td><a href="./../../examples/security-reviewer/agent/lib/asr/"><code>agent/lib/asr/</code></a>, <a href="../../examples/security-reviewer/agent/lib/review-stages.ts"><code>review-stages.ts</code></a></td><td>Select modules, call model workers, triage, deduplicate, and write artifacts.</td></tr><tr><td>Storage</td><td><a href="../../examples/security-reviewer/agent/storage.ts"><code>agent/storage.ts</code></a></td><td>Persist framework sessions through a custom adapter with lazy restore.</td></tr><tr><td>A/B</td><td><a href="../../examples/security-reviewer/agent/ab.ts"><code>agent/ab.ts</code></a>, <a href="../../examples/security-reviewer/agent/ab.config.ts"><code>agent/ab.config.ts</code></a></td><td>Compare all-severity versus high-only GitHub comments.</td></tr><tr><td>Eval</td><td><a href="./../../examples/security-reviewer/evals/"><code>evals/</code></a></td><td>Check stage-tool presence against a pinned sample.</td></tr></tbody></table><p>There is no Slack channel, authored skill, discovered subagent, MCP connection, schedule, reminder, hook, tool approval, or cloud runtime.</p><h2 id="prepare-the-host" tabindex="-1">Prepare the host <a class="header-anchor" href="#prepare-the-host" aria-label="Permalink to &quot;Prepare the host&quot;">​</a></h2><p>You need:</p><ul><li>Node 22.13 or newer.</li><li>An agent-runtime credential for the root turn and review workers.</li><li>GitHub read access for preparation.</li><li>GitHub write access for webhook-driven reviews and commit statuses.</li></ul><p>The pipeline exposes settings for:</p><ul><li>the worker model,</li><li>reviewer and triage parallelism,</li><li>reviewer, triage, duplicate-gate, and final-dedupe timeouts, and</li><li>prior-comment loading.</li></ul><p>The active names live beside the orchestration in <a href="../../examples/security-reviewer/agent/lib/review-stages.ts"><code>review-stages.ts</code></a>.</p><h2 id="validate-the-discovered-agent" tabindex="-1">Validate the discovered agent <a class="header-anchor" href="#validate-the-discovered-agent" aria-label="Permalink to &quot;Validate the discovered agent&quot;">​</a></h2><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> validate</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> examples/security-reviewer</span></span>
1
+ import{_ as s,c as t,o as a,ag as i}from"./chunks/framework.CAZyNGu9.js";const u=JSON.parse('{"title":"Run staged security reviews from GitHub events","description":"Orchestrate parallel reviewer agents, streamed triage, deterministic stages, live playground progress, durable sessions, and severity A/B metrics.","frontmatter":{"title":"Run staged security reviews from GitHub events","description":"Orchestrate parallel reviewer agents, streamed triage, deterministic stages, live playground progress, durable sessions, and severity A/B metrics."},"headers":[],"relativePath":"example-agents/security-reviewer.md","filePath":"example-agents/security-reviewer.md"}'),r={name:"example-agents/security-reviewer.md"};function n(l,e,o,d,h,p){return a(),t("div",null,[...e[0]||(e[0]=[i(`<h1 id="run-staged-security-reviews-from-github-events" tabindex="-1">Run staged security reviews from GitHub events <a class="header-anchor" href="#run-staged-security-reviews-from-github-events" aria-label="Permalink to &quot;Run staged security reviews from GitHub events&quot;">​</a></h1><p>Security Reviewer turns a pull request into a staged host-side review. One tool prepares the diff and selects modules. A second fans out specialized reviewers and triages candidates as they arrive. A third deduplicates the confirmed findings, writes artifacts, and may publish a GitHub review.</p><p>Use this example when the workflow needs several model workers, but the host must own orchestration, progress, artifacts, and the final write.</p><p><a href="./../../examples/security-reviewer/">Browse the Security Reviewer source.</a></p><h2 id="run-a-three-stage-host-pipeline" tabindex="-1">Run a three-stage host pipeline <a class="header-anchor" href="#run-a-three-stage-host-pipeline" aria-label="Permalink to &quot;Run a three-stage host pipeline&quot;">​</a></h2><p>Security Reviewer is a pipeline, not one long agent turn:</p><table tabindex="0"><thead><tr><th>Stage</th><th>Tool</th><th>Result</th></tr></thead><tbody><tr><td>Prepare</td><td><code>prepare_review</code></td><td>Fetch metadata and diff, create a <code>runId</code>, and select security modules.</td></tr><tr><td>Review and triage</td><td><code>run_reviewers</code></td><td>Run module reviewers in parallel and start triage as each candidate arrives.</td></tr><tr><td>Finalize</td><td><code>finalize_review</code></td><td>Apply thresholds, deduplicate findings, write artifacts, and optionally post a review.</td></tr></tbody></table><p><code>run_triage</code> remains available as a compatibility stage. In the normal flow, triage has already completed inside <code>run_reviewers</code>, so it reports existing results. If candidates exist without triage output, it starts triage workers and writes their state.</p><p>The configured root agent chooses and sequences tools in chat. The review workers use a model selected by the host pipeline. They are created programmatically with the agent SDK, not discovered from <code>agent/subagents/</code>.</p><h2 id="follow-a-github-wake" tabindex="-1">Follow a GitHub wake <a class="header-anchor" href="#follow-a-github-wake" aria-label="Permalink to &quot;Follow a GitHub wake&quot;">​</a></h2><ol><li>A non-draft <code>pull_request.opened</code> or <code>pull_request.synchronize</code> event arrives for an allowlisted repository.</li><li>The GitHub channel returns a host <code>{ task }</code>, so the webhook gets a 202 response before the long review starts.</li><li>Host code tries to post a pending commit status and creates a playground session.</li><li>The root model sends one acknowledgement. It doesn&#39;t run review tools on this path.</li><li>The task calls <code>prepare_review</code>, <code>run_reviewers</code>, and <code>finalize_review</code> deterministically inside that session.</li><li>Reviewer candidates stream into duplicate gating and triage.</li><li>Finalization writes artifacts and tries to post the GitHub review.</li><li>The host tries to set a success commit status when no findings remain, a failure status when findings remain, or an error status when the pipeline throws.</li><li>The final response is appended to the session as an assistant message.</li></ol><p>The session records each stage as a normal tool event, even though host code selected the tools.</p><p>Review and status posting are best-effort. The channel chooses its final status from the finding count even when the review posting result says <code>posted: false</code>.</p><h2 id="map-the-framework-features" tabindex="-1">Map the framework features <a class="header-anchor" href="#map-the-framework-features" aria-label="Permalink to &quot;Map the framework features&quot;">​</a></h2><table tabindex="0"><thead><tr><th>Capability</th><th>Source</th><th>Role</th></tr></thead><tbody><tr><td>Root agent</td><td><a href="../../examples/security-reviewer/agent/agent.ts"><code>agent/agent.ts</code></a>, <a href="./../../examples/security-reviewer/agent/instructions.html"><code>agent/instructions.md</code></a></td><td>Configure local chat and explain the three-stage contract.</td></tr><tr><td>Server tools</td><td><a href="./../../examples/security-reviewer/agent/tools/"><code>agent/tools/</code></a></td><td>Expose each review stage to chat and host orchestration.</td></tr><tr><td>GitHub channel</td><td><a href="../../examples/security-reviewer/agent/channels/github.ts"><code>agent/channels/github.ts</code></a></td><td>Filter wakes, run background tasks, and publish status.</td></tr><tr><td>Progress channel</td><td><a href="../../examples/security-reviewer/agent/channels/asr-progress.ts"><code>agent/channels/asr-progress.ts</code></a></td><td>Serve live reviewer and triage state by <code>runId</code>.</td></tr><tr><td>Playground renderer</td><td><a href="./../../examples/security-reviewer/agent/playground/tools/run_reviewers.tsx.html"><code>agent/playground/tools/run_reviewers.tsx</code></a></td><td>Replace the generic tool chip with live module rows.</td></tr><tr><td>SDK review pipeline</td><td><a href="./../../examples/security-reviewer/agent/lib/asr/"><code>agent/lib/asr/</code></a>, <a href="../../examples/security-reviewer/agent/lib/review-stages.ts"><code>review-stages.ts</code></a></td><td>Select modules, call model workers, triage, deduplicate, and write artifacts.</td></tr><tr><td>Storage</td><td><a href="../../examples/security-reviewer/agent/storage.ts"><code>agent/storage.ts</code></a></td><td>Persist framework sessions with <code>cursorHostedStorage</code> (lazy restore).</td></tr><tr><td>A/B</td><td><a href="../../examples/security-reviewer/agent/ab.ts"><code>agent/ab.ts</code></a>, <a href="../../examples/security-reviewer/agent/ab.config.ts"><code>agent/ab.config.ts</code></a></td><td>Compare all-severity versus high-only GitHub comments.</td></tr><tr><td>Eval</td><td><a href="./../../examples/security-reviewer/evals/"><code>evals/</code></a></td><td>Check stage-tool presence against a pinned sample.</td></tr></tbody></table><p>There is no Slack channel, authored skill, discovered subagent, MCP connection, schedule, reminder, hook, tool approval, or cloud runtime.</p><h2 id="prepare-the-host" tabindex="-1">Prepare the host <a class="header-anchor" href="#prepare-the-host" aria-label="Permalink to &quot;Prepare the host&quot;">​</a></h2><p>You need:</p><ul><li>Node 22.13 or newer.</li><li>An agent-runtime credential for the root turn and review workers.</li><li>GitHub read access for preparation.</li><li>GitHub write access for webhook-driven reviews and commit statuses.</li></ul><p>The pipeline exposes settings for:</p><ul><li>the worker model,</li><li>reviewer and triage parallelism,</li><li>reviewer, triage, duplicate-gate, and final-dedupe timeouts, and</li><li>prior-comment loading.</li></ul><p>The active names live beside the orchestration in <a href="../../examples/security-reviewer/agent/lib/review-stages.ts"><code>review-stages.ts</code></a>.</p><h2 id="validate-the-discovered-agent" tabindex="-1">Validate the discovered agent <a class="header-anchor" href="#validate-the-discovered-agent" aria-label="Permalink to &quot;Validate the discovered agent&quot;">​</a></h2><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> validate</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> examples/security-reviewer</span></span>
2
2
  <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> info</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> examples/security-reviewer</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --json</span></span>
3
3
  <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> eval</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> examples/security-reviewer</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --list</span></span></code></pre></div><p>The manifest should show four server tools, two authored channels, one storage definition, and one A/B experiment. The eval listing should show one case.</p><h2 id="know-the-chat-path-s-write-boundary" tabindex="-1">Know the chat path&#39;s write boundary <a class="header-anchor" href="#know-the-chat-path-s-write-boundary" aria-label="Permalink to &quot;Know the chat path&#39;s write boundary&quot;">​</a></h2><p>In chat, the root instructions ask the model to use this order:</p><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>prepare_review -&gt; run_reviewers -&gt; finalize_review</span></span></code></pre></div><p>They also ask the model to set <code>postComment: true</code> only on request. This is prompt policy, not a deterministic safety gate. The model chooses tool arguments, and <code>finalize_review</code> has no human approval. Use the direct stage calls below when a no-post proof must be enforced.</p><h2 id="call-stages-directly-without-publishing" tabindex="-1">Call stages directly without publishing <a class="header-anchor" href="#call-stages-directly-without-publishing" aria-label="Permalink to &quot;Call stages directly without publishing&quot;">​</a></h2><p>Call each stage and pass <code>postComment: false</code> yourself:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> call</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> prepare_review</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \\</span></span>
4
4
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> examples/security-reviewer</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \\</span></span>
@@ -10,7 +10,7 @@ import{_ as s,c as t,o as a,ag as i}from"./chunks/framework.CAZyNGu9.js";const u
10
10
  <span class="line"></span>
11
11
  <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> call</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> finalize_review</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \\</span></span>
12
12
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> examples/security-reviewer</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \\</span></span>
13
- <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --input</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;{&quot;runId&quot;:&quot;&lt;run-id&gt;&quot;,&quot;postComment&quot;:false}&#39;</span></span></code></pre></div><p>Review state lives under the project&#39;s run-artifact directory, so later stages can open the prepared <code>runId</code>.</p><div class="caution custom-block github-alert"><p class="custom-block-title">CAUTION</p><p><code>finalize_review</code> with <code>postComment: true</code> writes to GitHub. The webhook path always requests that write. Chat instructions alone don&#39;t prevent it.</p></div><h2 id="watch-parallel-work-in-the-playground" tabindex="-1">Watch parallel work in the playground <a class="header-anchor" href="#watch-parallel-work-in-the-playground" aria-label="Permalink to &quot;Watch parallel work in the playground&quot;">​</a></h2><p>Run the dev server:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> examples/security-reviewer</span></span></code></pre></div><p>Open the printed playground and start a review. The custom <code>run_reviewers</code> renderer polls the progress channel&#39;s <code>GET /:runId</code> route.</p><p>It refreshes every 500 ms while the stage runs. Each row shows a reviewer module&#39;s state, candidates, reviewed areas, and failure. A second section shows triage jobs and confirmed or rejected counts.</p><p>This is an authored playground extension. Agentkit discovers it by the tool name, so the generic <code>run_reviewers</code> chip becomes a domain-specific view without changing the framework playground.</p><h2 id="fan-out-reviewers-while-triage-starts" tabindex="-1">Fan out reviewers while triage starts <a class="header-anchor" href="#fan-out-reviewers-while-triage-starts" aria-label="Permalink to &quot;Fan out reviewers while triage starts&quot;">​</a></h2><p>Module selection uses repository and path rules. The current module set covers:</p><ul><li>agent tooling trust boundaries,</li><li>privileged service RPCs,</li><li>product-specific security risks,</li><li>dependency and supply-chain changes,</li><li>deployment and infrastructure code,</li><li>filesystem and workspace boundaries,</li><li>privacy, and</li><li>general security review.</li></ul><p>Selected modules may run more than once. Candidates pass through a duplicate gate, then bounded triage. Reviewer or triage failures can produce partial results. A final dedupe failure stops finalization.</p><p>The pipeline writes JSONL journals as work completes. Final artifacts include the review bundle, patch, reviewer outputs, candidates, triage decisions, findings, accounting, and audit events.</p><h2 id="separate-session-storage-from-review-artifacts" tabindex="-1">Separate session storage from review artifacts <a class="header-anchor" href="#separate-session-storage-from-review-artifacts" aria-label="Permalink to &quot;Separate session storage from review artifacts&quot;">​</a></h2><p><code>defineStorage</code> sends agentkit session and event records to a custom durable store. Security Reviewer sets <code>restore: &quot;off&quot;</code> so startup doesn&#39;t load old review sessions in bulk. A continuation lookup can still fetch a needed session.</p><p>The staged review files are separate from session storage. Session-store durability doesn&#39;t preserve those files. All stages for one <code>runId</code> must see the same filesystem.</p><p>This split is useful when conversation history needs shared durability but large review artifacts belong on attached storage or an object store.</p><h2 id="compare-live-comment-variants" tabindex="-1">Compare live comment variants <a class="header-anchor" href="#compare-live-comment-variants" aria-label="Permalink to &quot;Compare live comment variants&quot;">​</a></h2><p>The comment-severity experiment uses sticky session assignment with a 5% holdout:</p><ul><li><code>control</code> posts every finding.</li><li><code>treatment</code> posts only high and critical findings.</li></ul><p>Finalization enforces the comment filter. The treatment also adds an instruction overlay asking chat and playground summaries to lead with high and critical findings. Full artifacts, <code>finalResponse</code>, finding counts, and status still include every finding. Stage-tool counters appear in the playground A/B view. Local sample and snapshot files persist under <code>.agent-serve/</code>.</p><p>When a treatment session has only low or medium findings, the filtered review body currently says no vulnerabilities were found even though artifacts and status retain findings. Account for that mismatch before using this experiment as a publishing policy.</p><p>Eval sessions skip A/B enrollment.</p><h2 id="test-the-github-channel-carefully" tabindex="-1">Test the GitHub channel carefully <a class="header-anchor" href="#test-the-github-channel-carefully" aria-label="Permalink to &quot;Test the GitHub channel carefully&quot;">​</a></h2><p>The channel currently accepts two configured repositories. It wakes on <code>opened</code> and <code>synchronize</code>, skips drafts, and requests <code>pr-write</code> access.</p><p>Inspect its event surface:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> github</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> events</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \\</span></span>
13
+ <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --input</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;{&quot;runId&quot;:&quot;&lt;run-id&gt;&quot;,&quot;postComment&quot;:false}&#39;</span></span></code></pre></div><p>Review state lives under the project&#39;s run-artifact directory, so later stages can open the prepared <code>runId</code>.</p><div class="caution custom-block github-alert"><p class="custom-block-title">CAUTION</p><p><code>finalize_review</code> with <code>postComment: true</code> writes to GitHub. The webhook path always requests that write. Chat instructions alone don&#39;t prevent it.</p></div><h2 id="watch-parallel-work-in-the-playground" tabindex="-1">Watch parallel work in the playground <a class="header-anchor" href="#watch-parallel-work-in-the-playground" aria-label="Permalink to &quot;Watch parallel work in the playground&quot;">​</a></h2><p>Run the dev server:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> dev</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> examples/security-reviewer</span></span></code></pre></div><p>Open the printed playground and start a review. The custom <code>run_reviewers</code> renderer polls the progress channel&#39;s <code>GET /:runId</code> route.</p><p>It refreshes every 500 ms while the stage runs. Each row shows a reviewer module&#39;s state, candidates, reviewed areas, and failure. A second section shows triage jobs and confirmed or rejected counts.</p><p>This is an authored playground extension. Agentkit discovers it by the tool name, so the generic <code>run_reviewers</code> chip becomes a domain-specific view without changing the framework playground.</p><h2 id="fan-out-reviewers-while-triage-starts" tabindex="-1">Fan out reviewers while triage starts <a class="header-anchor" href="#fan-out-reviewers-while-triage-starts" aria-label="Permalink to &quot;Fan out reviewers while triage starts&quot;">​</a></h2><p>Module selection uses repository and path rules. The current module set covers:</p><ul><li>agent tooling trust boundaries,</li><li>privileged service RPCs,</li><li>product-specific security risks,</li><li>dependency and supply-chain changes,</li><li>deployment and infrastructure code,</li><li>filesystem and workspace boundaries,</li><li>privacy, and</li><li>general security review.</li></ul><p>Selected modules may run more than once. Candidates pass through a duplicate gate, then bounded triage. Reviewer or triage failures can produce partial results. A final dedupe failure stops finalization.</p><p>The pipeline writes JSONL journals as work completes. Final artifacts include the review bundle, patch, reviewer outputs, candidates, triage decisions, findings, accounting, and audit events.</p><h2 id="separate-session-storage-from-review-artifacts" tabindex="-1">Separate session storage from review artifacts <a class="header-anchor" href="#separate-session-storage-from-review-artifacts" aria-label="Permalink to &quot;Separate session storage from review artifacts&quot;">​</a></h2><p><code>defineStorage</code> + <code>cursorHostedStorage</code> sends agentkit session and event records to Cursor-hosted Bugbot storage through the control-plane proxy. Security Reviewer sets <code>restore: &quot;off&quot;</code> so startup doesn&#39;t load old review sessions in bulk. A continuation lookup can still fetch a needed session.</p><p>The staged review files are separate from session storage. Session-store durability doesn&#39;t preserve those files. All stages for one <code>runId</code> must see the same filesystem.</p><p>This split is useful when conversation history needs shared durability but large review artifacts belong on attached storage or an object store.</p><h2 id="compare-live-comment-variants" tabindex="-1">Compare live comment variants <a class="header-anchor" href="#compare-live-comment-variants" aria-label="Permalink to &quot;Compare live comment variants&quot;">​</a></h2><p>The comment-severity experiment uses sticky session assignment with a 5% holdout:</p><ul><li><code>control</code> posts every finding.</li><li><code>treatment</code> posts only high and critical findings.</li></ul><p>Finalization enforces the comment filter. The treatment also adds an instruction overlay asking chat and playground summaries to lead with high and critical findings. Full artifacts, <code>finalResponse</code>, finding counts, and status still include every finding. Stage-tool counters appear in the playground A/B view. Local sample and snapshot files persist under <code>.agent-serve/</code>.</p><p>When a treatment session has only low or medium findings, the filtered review body currently says no vulnerabilities were found even though artifacts and status retain findings. Account for that mismatch before using this experiment as a publishing policy.</p><p>Eval sessions skip A/B enrollment.</p><h2 id="test-the-github-channel-carefully" tabindex="-1">Test the GitHub channel carefully <a class="header-anchor" href="#test-the-github-channel-carefully" aria-label="Permalink to &quot;Test the GitHub channel carefully&quot;">​</a></h2><p>The channel currently accepts two configured repositories. It wakes on <code>opened</code> and <code>synchronize</code>, skips drafts, and requests <code>pr-write</code> access.</p><p>Inspect its event surface:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> github</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> events</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \\</span></span>
14
14
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> examples/security-reviewer</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \\</span></span>
15
15
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --json</span></span></code></pre></div><p>Replay reaches the full publishing path:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">TEST_PR_URL</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">https://github.com/your-org/allowlisted-test-repo/pull/123</span></span>
16
16
  <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> github</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> replay</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \\</span></span>
@@ -1 +1 @@
1
- import{_ as s,c as t,o as a,ag as i}from"./chunks/framework.CAZyNGu9.js";const u=JSON.parse('{"title":"Run staged security reviews from GitHub events","description":"Orchestrate parallel reviewer agents, streamed triage, deterministic stages, live playground progress, durable sessions, and severity A/B metrics.","frontmatter":{"title":"Run staged security reviews from GitHub events","description":"Orchestrate parallel reviewer agents, streamed triage, deterministic stages, live playground progress, durable sessions, and severity A/B metrics."},"headers":[],"relativePath":"example-agents/security-reviewer.md","filePath":"example-agents/security-reviewer.md"}'),r={name:"example-agents/security-reviewer.md"};function n(l,e,o,h,d,p){return a(),t("div",null,[...e[0]||(e[0]=[i("",73)])])}const g=s(r,[["render",n]]);export{u as __pageData,g as default};
1
+ import{_ as s,c as t,o as a,ag as i}from"./chunks/framework.CAZyNGu9.js";const u=JSON.parse('{"title":"Run staged security reviews from GitHub events","description":"Orchestrate parallel reviewer agents, streamed triage, deterministic stages, live playground progress, durable sessions, and severity A/B metrics.","frontmatter":{"title":"Run staged security reviews from GitHub events","description":"Orchestrate parallel reviewer agents, streamed triage, deterministic stages, live playground progress, durable sessions, and severity A/B metrics."},"headers":[],"relativePath":"example-agents/security-reviewer.md","filePath":"example-agents/security-reviewer.md"}'),r={name:"example-agents/security-reviewer.md"};function n(l,e,o,d,h,p){return a(),t("div",null,[...e[0]||(e[0]=[i("",73)])])}const g=s(r,[["render",n]]);export{u as __pageData,g as default};
@@ -1,4 +1,4 @@
1
- import{_ as a,c as e,o as i,ag as t}from"./chunks/framework.CAZyNGu9.js";const c=JSON.parse('{"title":"Slack","description":"Put the agent in Slack over Socket Mode: manifests, tokens, doctor, engagement rules, and approval buttons.","frontmatter":{"title":"Slack","description":"Put the agent in Slack over Socket Mode: manifests, tokens, doctor, engagement rules, and approval buttons."},"headers":[],"relativePath":"guides/slack.md","filePath":"guides/slack.md"}'),n={name:"guides/slack.md"};function h(l,s,p,o,r,d){return i(),e("div",null,[...s[0]||(s[0]=[t(`<h1 id="slack-agents" tabindex="-1">Slack agents <a class="header-anchor" href="#slack-agents" aria-label="Permalink to &quot;Slack agents&quot;">​</a></h1><p>The Slack channel puts your agent in Slack over Socket Mode: no public Request URL, streaming replies in threads, tool &quot;thinking&quot; steps, suggested prompts, and opt-in approval buttons. Setup is four short phases. Two are yours, and two need a human with Slack admin access.</p><p>The companion skill is <a href="./../../skills/setup-slack/SKILL.html"><code>skills/setup-slack/SKILL.md</code></a>.</p><h2 id="define-the-channel" tabindex="-1">Define the channel <a class="header-anchor" href="#define-the-channel" aria-label="Permalink to &quot;Define the channel&quot;">​</a></h2><p>Author <code>agent/channels/slack.ts</code> with <code>slackChannel()</code> from <code>@cursor/july/channels/slack</code>:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { slackChannel } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;@cursor/july/channels/slack&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
1
+ import{_ as a,c as e,o as i,ag as t}from"./chunks/framework.CAZyNGu9.js";const c=JSON.parse('{"title":"Slack","description":"Put the agent in Slack over Socket Mode: manifests, tokens, doctor, engagement rules, and approval buttons.","frontmatter":{"title":"Slack","description":"Put the agent in Slack over Socket Mode: manifests, tokens, doctor, engagement rules, and approval buttons."},"headers":[],"relativePath":"guides/slack.md","filePath":"guides/slack.md"}'),n={name:"guides/slack.md"};function h(l,s,o,p,r,d){return i(),e("div",null,[...s[0]||(s[0]=[t(`<h1 id="slack-agents" tabindex="-1">Slack agents <a class="header-anchor" href="#slack-agents" aria-label="Permalink to &quot;Slack agents&quot;">​</a></h1><p>The Slack channel puts your agent in Slack over Socket Mode: no public Request URL, streaming replies in threads, tool &quot;thinking&quot; steps, suggested prompts, and opt-in approval buttons. Setup is four short phases. Two are yours, and two need a human with Slack admin access.</p><p>The companion skill is <a href="./../../skills/setup-slack/SKILL.html"><code>skills/setup-slack/SKILL.md</code></a>.</p><h2 id="define-the-channel" tabindex="-1">Define the channel <a class="header-anchor" href="#define-the-channel" aria-label="Permalink to &quot;Define the channel&quot;">​</a></h2><p>Author <code>agent/channels/slack.ts</code> with <code>slackChannel()</code> from <code>@cursor/july/channels/slack</code>:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { slackChannel } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;@cursor/july/channels/slack&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
2
2
  <span class="line"></span>
3
3
  <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// Single agent: reads SLACK_BOT_TOKEN + SLACK_APP_TOKEN</span></span>
4
4
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> default</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> slackChannel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">();</span></span>
@@ -21,17 +21,18 @@ import{_ as a,c as e,o as i,ag as t}from"./chunks/framework.CAZyNGu9.js";const c
21
21
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> allow: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;#triage-alerts&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">], </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// explicit allowlist; no wildcard exists</span></span>
22
22
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> posts: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;top-level&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// default: thread replies never dispatch</span></span>
23
23
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> debounceMs: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">15_000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// optional: let rapid edits settle</span></span>
24
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> includeBotPosts: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// default: bot-authored posts never dispatch</span></span>
24
25
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
25
26
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
26
27
  <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> onChannelPost</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">async</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">ctx</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">message</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
27
28
  <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // same contract as onAppMention: return null to skip</span></span>
28
29
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> message.markdown.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">length</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &gt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> 20</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> ?</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {} </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> null</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
29
30
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
30
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">});</span></span></code></pre></div><p>Channel watching needs the <code>message.channels</code> / <code>message.groups</code> events on the Slack app (Socket Mode only — not available with <code>cursorAccount: true</code>). Generate manifests with <code>slack init --channel-posts</code>, or add the events and re-import. The bot must also be a member of each watched channel. Posts that mention the bot stay on the <code>app_mention</code> path, bot-authored posts never dispatch, and deleting a post inside the debounce window cancels its dispatch. Watched posts reuse the thread-scoped principal, so a later @mention continues the same session. Set <code>engagement.mentions</code> or <code>engagement.directMessages</code> to <code>false</code> to turn those surfaces off.</p><h2 id="prepare-work-on-the-host" tabindex="-1">Prepare work on the host <a class="header-anchor" href="#prepare-work-on-the-host" aria-label="Permalink to &quot;Prepare work on the host&quot;">​</a></h2><p>Mention and DM handlers may return a prepared <code>message</code>, <code>workspaceFiles</code>, or <code>cloud</code> block. It&#39;s the same host-prep pattern as <a href="./webhooks.html#prepare-on-the-host-then-hand-off">custom channels</a>. PR agents use it: extract a PR URL from the mention text and run the same host path as the HTTP channel.</p><h2 id="add-approval-buttons" tabindex="-1">Add approval buttons <a class="header-anchor" href="#add-approval-buttons" aria-label="Permalink to &quot;Add approval buttons&quot;">​</a></h2><p>Tools with <code>needsApproval</code> park until a person decides. Route that through Slack with one flag:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> default</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> slackChannel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
31
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">});</span></span></code></pre></div><p>Channel watching needs the <code>message.channels</code> / <code>message.groups</code> events on the Slack app (Socket Mode only — not available with <code>cursorAccount: true</code>). Generate manifests with <code>slack init --channel-posts</code>, or add the events and re-import. The bot must also be a member of each watched channel. Posts that mention the bot stay on the <code>app_mention</code> path, bot-authored posts never dispatch by default, and deleting a post inside the debounce window cancels its dispatch. Watched posts reuse the thread-scoped principal, so a later @mention continues the same session. Set <code>engagement.mentions</code> or <code>engagement.directMessages</code> to <code>false</code> to turn those surfaces off.</p><p>Set <code>includeBotPosts: true</code> when the posts worth watching come from bots: alert feeds, webhook integrations, or other agents posting notes. The watching app&#39;s own posts stay dropped either way, matched by the <code>bot_id</code> and bot user id from <code>auth.test</code>, so an agent can never dispatch on its own replies. The <a href="./../example-agents/oncall.html">alert investigator example</a> watches a bot-fed alerts channel this way.</p><h2 id="prepare-work-on-the-host" tabindex="-1">Prepare work on the host <a class="header-anchor" href="#prepare-work-on-the-host" aria-label="Permalink to &quot;Prepare work on the host&quot;">​</a></h2><p>Mention and DM handlers may return a prepared <code>message</code>, <code>workspaceFiles</code>, or <code>cloud</code> block. It&#39;s the same host-prep pattern as <a href="./webhooks.html#prepare-on-the-host-then-hand-off">custom channels</a>. PR agents use it: extract a PR URL from the mention text and run the same host path as the HTTP channel.</p><h2 id="add-approval-buttons" tabindex="-1">Add approval buttons <a class="header-anchor" href="#add-approval-buttons" aria-label="Permalink to &quot;Add approval buttons&quot;">​</a></h2><p>Tools with <code>needsApproval</code> park until a person decides. Route that through Slack with one flag:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> default</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> slackChannel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
31
32
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> toolApprovals: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// posts Block Kit Approve/Deny cards + routes clicks</span></span>
32
33
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">});</span></span></code></pre></div><p>The Slack app manifest must have interactivity enabled. Regenerate with <code>buildSlackManifest({ interactivity: true })</code> (or edit and re-import) if your app predates it. Composing events by hand instead: spread <code>buildToolApprovalEvents({ credentials })</code> into <code>events</code> and set <code>interactivity: true</code> on the channel so Socket Mode routes the clicks.</p><p>Approval buttons need Socket Mode. <code>slackChannel({ cursorAccount: true })</code> rejects <code>toolApprovals</code> and <code>interactivity</code> at construction, since the Cursor Slack connection does not relay Block Kit clicks. Use a dedicated Slack app to run approvals for a cursor-account agent.</p><p>Cards show redacted, truncated arguments (Block Kit size limits); execution still uses the full validated input, so review sensitive tools in the playground when the arguments may exceed the card. Approvals exist for <code>execution: &quot;server&quot;</code> tools on the local runtime only, and parked calls do not survive a host restart. The full lifecycle is in <a href="./human-in-the-loop.html">Human-in-the-loop</a>.</p><h2 id="run-several-agents-on-one-host" tabindex="-1">Run several agents on one host <a class="header-anchor" href="#run-several-agents-on-one-host" aria-label="Permalink to &quot;Run several agents on one host&quot;">​</a></h2><p>One Slack app and token pair per agent. Never share a pair across agents in the same process. <code>envPrefix</code> keeps them apart (<code>WEATHER_AGENT_SLACK_*</code>, <code>TRIAGE_SLACK_*</code>, …), and agents without tokens mount with their Slack channel idle while everything else serves normally.</p><h2 id="keep-the-channel-healthy" tabindex="-1">Keep the channel healthy <a class="header-anchor" href="#keep-the-channel-healthy" aria-label="Permalink to &quot;Keep the channel healthy&quot;">​</a></h2><p>Two habits matter most.</p><ul><li>Don&#39;t <code>await</code> long work inside Slack dispatch handlers. The pack dispatches through <code>waitUntil</code> and streams as the turn progresses.</li><li>In <code>--dev</code> (loopback) or <code>--allow-anonymous</code> (trusted shared host), the playground can list and stream Slack sessions and resolve their parked approvals (the audit trail records the HTTP caller). Bearer-auth hosts stay strict: Slack approvals must come from Slack interactivity or a matching principal.</li></ul><h2 id="cli-reference" tabindex="-1">CLI reference <a class="header-anchor" href="#cli-reference" aria-label="Permalink to &quot;CLI reference&quot;">​</a></h2><p>The <code>slack</code> subcommands cover setup end to end.</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> slack</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> setup</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # guided checklist</span></span>
33
34
  <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> slack</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> init</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> .</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --name</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;My Agent&quot;</span></span>
34
35
  <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> slack</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> init</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> .</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --no-prefix</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # shared SLACK_* vars</span></span>
35
36
  <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> slack</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> init</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --dir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> .</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --channel-posts</span></span>
36
37
  <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> slack</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> manifest</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --env</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> both</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # regenerate manifests</span></span>
37
- <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> slack</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> doctor</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --prefix</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> MY_AGENT</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # token / connectivity checks</span></span></code></pre></div><h2 id="what-s-next" tabindex="-1">What&#39;s next <a class="header-anchor" href="#what-s-next" aria-label="Permalink to &quot;What&#39;s next&quot;">​</a></h2><p>Continue with these pages:</p><ul><li><a href="./human-in-the-loop.html">Human-in-the-loop</a>: the approval lifecycle behind <code>toolApprovals</code></li><li><a href="./webhooks.html">Webhooks and custom channels</a>: the mechanism this pack is built on</li></ul>`,53)])])}const g=a(n,[["render",h]]);export{c as __pageData,g as default};
38
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> slack</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> doctor</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --prefix</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> MY_AGENT</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # token / connectivity checks</span></span></code></pre></div><h2 id="what-s-next" tabindex="-1">What&#39;s next <a class="header-anchor" href="#what-s-next" aria-label="Permalink to &quot;What&#39;s next&quot;">​</a></h2><p>Continue with these pages:</p><ul><li><a href="./human-in-the-loop.html">Human-in-the-loop</a>: the approval lifecycle behind <code>toolApprovals</code></li><li><a href="./webhooks.html">Webhooks and custom channels</a>: the mechanism this pack is built on</li></ul>`,54)])])}const g=a(n,[["render",h]]);export{c as __pageData,g as default};
@@ -1 +1 @@
1
- import{_ as a,c as e,o as i,ag as t}from"./chunks/framework.CAZyNGu9.js";const c=JSON.parse('{"title":"Slack","description":"Put the agent in Slack over Socket Mode: manifests, tokens, doctor, engagement rules, and approval buttons.","frontmatter":{"title":"Slack","description":"Put the agent in Slack over Socket Mode: manifests, tokens, doctor, engagement rules, and approval buttons."},"headers":[],"relativePath":"guides/slack.md","filePath":"guides/slack.md"}'),n={name:"guides/slack.md"};function h(l,s,p,o,r,d){return i(),e("div",null,[...s[0]||(s[0]=[t("",53)])])}const g=a(n,[["render",h]]);export{c as __pageData,g as default};
1
+ import{_ as a,c as e,o as i,ag as t}from"./chunks/framework.CAZyNGu9.js";const c=JSON.parse('{"title":"Slack","description":"Put the agent in Slack over Socket Mode: manifests, tokens, doctor, engagement rules, and approval buttons.","frontmatter":{"title":"Slack","description":"Put the agent in Slack over Socket Mode: manifests, tokens, doctor, engagement rules, and approval buttons."},"headers":[],"relativePath":"guides/slack.md","filePath":"guides/slack.md"}'),n={name:"guides/slack.md"};function h(l,s,o,p,r,d){return i(),e("div",null,[...s[0]||(s[0]=[t("",54)])])}const g=a(n,[["render",h]]);export{c as __pageData,g as default};
@@ -1,8 +1,8 @@
1
- import{_ as s,c as t,o as a,ag as n}from"./chunks/framework.CAZyNGu9.js";const u=JSON.parse('{"title":"Sessions, events, and streaming","description":"Understand how conversations continue, how events stream, and where session data lives.","frontmatter":{"title":"Sessions, events, and streaming","description":"Understand how conversations continue, how events stream, and where session data lives."},"headers":[],"relativePath":"reference/sessions.md","filePath":"reference/sessions.md"}'),o={name:"reference/sessions.md"};function i(d,e,r,l,c,h){return a(),t("div",null,[...e[0]||(e[0]=[n(`<h1 id="sessions-events-and-streaming" tabindex="-1">Sessions, events, and streaming <a class="header-anchor" href="#sessions-events-and-streaming" aria-label="Permalink to &quot;Sessions, events, and streaming&quot;">​</a></h1><p>A session keeps one conversation, its workspace, and an append-only record of every message and tool call.</p><h2 id="what-does-a-session-contain" tabindex="-1">What does a session contain? <a class="header-anchor" href="#what-does-a-session-contain" aria-label="Permalink to &quot;What does a session contain?&quot;">​</a></h2><p>Each session combines:</p><ul><li>A channel and authenticated caller</li><li>A conversation the caller can continue</li><li>A workspace for local turns</li><li>An NDJSON event stream</li><li>Runtime state needed to resume after a server restart</li></ul><p>Sessions belong to the principal that created them. Follow-up, stream, and list routes return <code>403</code> when another caller tries to access one.</p><h2 id="which-session-identifier-should-i-use" tabindex="-1">Which session identifier should I use? <a class="header-anchor" href="#which-session-identifier-should-i-use" aria-label="Permalink to &quot;Which session identifier should I use?&quot;">​</a></h2><p>Sessions have two identifiers because conversation routing and inspection are different jobs.</p><table tabindex="0"><thead><tr><th>Identifier</th><th>Use it for</th></tr></thead><tbody><tr><td><code>continuationToken</code></td><td>Continue a conversation through its channel</td></tr><tr><td><code>sessionId</code></td><td>Stream events, inspect state, resolve approvals, or run a session-bound tool call</td></tr></tbody></table><p>Channels decide what a continuation token looks like. Slack uses its thread identity. A PR channel can use a key such as <code>pr:owner/repo#1</code>. The built-in HTTP API returns an opaque token and rotates it after each accepted follow-up. Reusing a stale HTTP token returns <code>409</code>.</p><p>Use the continuation token to keep talking. Use the session ID to observe or manage the stored session.</p><h2 id="which-session-modes-are-available" tabindex="-1">Which session modes are available? <a class="header-anchor" href="#which-session-modes-are-available" aria-label="Permalink to &quot;Which session modes are available?&quot;">​</a></h2><table tabindex="0"><thead><tr><th>Mode</th><th>Created by</th><th>What happens after a turn</th></tr></thead><tbody><tr><td><code>chat</code></td><td>HTTP sessions, channel <code>send</code>, Slack, or MCP <code>ask</code></td><td>Waits in <code>session.waiting</code> and accepts follow-ups</td></tr><tr><td><code>task</code></td><td>Markdown schedules and fire-and-forget dispatch</td><td>Ends in <code>session.completed</code> or <code>session.failed</code></td></tr></tbody></table><p>Task sessions don&#39;t accept follow-ups. Trying one returns <code>409</code>.</p><h2 id="what-happens-when-i-send-a-follow-up" tabindex="-1">What happens when I send a follow-up? <a class="header-anchor" href="#what-happens-when-i-send-a-follow-up" aria-label="Permalink to &quot;What happens when I send a follow-up?&quot;">​</a></h2><p>A follow-up to an idle chat session starts another turn. A follow-up to a busy session interrupts the current turn, waits for it to settle, and then sends the new message.</p><p><code>POST /v1/session/:id/stop</code> interrupts a turn without sending a new message. Interrupted turns record <code>turn.failed</code> with <code>&quot;turn interrupted&quot;</code>. This means the turn was preempted.</p><p>Session-bound deterministic tool calls share the same execution lock. They return <code>409 session_busy</code> while a model turn is running.</p><h2 id="which-events-can-i-stream" tabindex="-1">Which events can I stream? <a class="header-anchor" href="#which-events-can-i-stream" aria-label="Permalink to &quot;Which events can I stream?&quot;">​</a></h2><p>Each NDJSON line uses this envelope: <code>{ type, index, sessionId, turnId?, at, data }</code>. The <code>index</code> increases within one session. The <code>at</code> field is an ISO-8601 timestamp.</p><table tabindex="0"><thead><tr><th>Phase</th><th>Events</th><th>What they tell you</th></tr></thead><tbody><tr><td>Session</td><td><code>session.started</code>, <a href="./../ab.html#assign-sticky-variants"><code>ab.assigned</code></a>, <code>session.waiting</code>, <code>session.completed</code>, <code>session.failed</code></td><td>Session creation, A/B enrollment, readiness, and task completion</td></tr><tr><td>Agent</td><td><code>agent.bound</code></td><td>Cursor SDK agent ID and cloud conversation URL</td></tr><tr><td>Input</td><td><code>message.received</code></td><td>A user message was accepted</td></tr><tr><td>Turn</td><td><code>turn.started</code>, <code>turn.completed</code>, <code>turn.failed</code></td><td>Turn status, final result, and token usage</td></tr><tr><td>Steps</td><td><code>step.started</code>, <code>step.completed</code></td><td>Model step boundaries and duration</td></tr><tr><td>Reasoning</td><td><code>reasoning.appended</code>, <code>reasoning.completed</code></td><td>Streamed reasoning blocks</td></tr><tr><td>Reply</td><td><code>message.appended</code>, <code>message.completed</code></td><td>Text deltas and finalized assistant messages</td></tr><tr><td>Tools</td><td><code>actions.requested</code>, <code>action.result</code></td><td>Tool names, validated arguments, outputs, and errors</td></tr><tr><td>Approvals</td><td><code>action.approval_requested</code>, <code>action.approval_resolved</code></td><td>A parked tool call and the human decision</td></tr><tr><td>Subagents</td><td><code>subagent.called</code>, <code>subagent.completed</code></td><td>Delegated work</td></tr></tbody></table><p>Pair <code>actions.requested</code> with <code>action.result</code> to reconstruct the tool trajectory. Read <code>turn.completed.data.usage</code> for input, output, and cache token counts.</p><h2 id="how-do-i-stream-or-replay-session-events" tabindex="-1">How do I stream or replay session events? <a class="header-anchor" href="#how-do-i-stream-or-replay-session-events" aria-label="Permalink to &quot;How do I stream or replay session events?&quot;">​</a></h2><p>One endpoint handles both live streaming and replay:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -N</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;http://127.0.0.1:3000/&lt;slug&gt;/v1/session/ses_…/stream?startIndex=0&#39;</span></span></code></pre></div><p>Pass <code>startIndex</code> to continue after the last event you received. Omit it or pass <code>0</code> to replay the full session before following new events. <code>GET /v1/session/:id/events</code> returns a one-time dump without staying connected.</p><p>Event streams replay from disk after a server restart. Conversation state resumes from the Cursor SDK store.</p><h2 id="what-goes-into-a-local-session-workspace" tabindex="-1">What goes into a local session workspace? <a class="header-anchor" href="#what-goes-into-a-local-session-workspace" aria-label="Permalink to &quot;What goes into a local session workspace?&quot;">​</a></h2><p>agentkit creates a workspace before the first local turn:</p><table tabindex="0"><thead><tr><th>Source path</th><th>Lands as</th></tr></thead><tbody><tr><td><code>instructions.*</code></td><td><code>AGENTS.md</code></td></tr><tr><td><code>skills/*</code></td><td><code>.cursor/skills/&lt;name&gt;/SKILL.md</code></td></tr><tr><td>agent tools (<code>execution: &quot;agent&quot;</code>)</td><td>scripts under <code>.agentkit/tools/</code>, with a catalog in <code>AGENTS.md</code></td></tr><tr><td><code>sandbox/workspace/**</code></td><td>copied in as seed files</td></tr><tr><td>per-send <code>workspaceFiles</code></td><td>written before the turn</td></tr></tbody></table><p>The local harness uses this workspace as its working directory. Parent directories can contribute <code>AGENTS.md</code> and <code>.cursor</code> settings. Set <code>local.cwd</code> when you need a clean parent directory. A channel can also provide a different working directory for one session, such as a PR worktree.</p><p>See <a href="./agent-config.html#local-cwd">Agent config: local cwd</a> for the inheritance rules.</p><h2 id="where-does-agentkit-store-session-data" tabindex="-1">Where does agentkit store session data? <a class="header-anchor" href="#where-does-agentkit-store-session-data" aria-label="Permalink to &quot;Where does agentkit store session data?&quot;">​</a></h2><p>Local state uses one directory tree:</p><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>&lt;project&gt;/.agentkit/ # or &lt;stateRoot&gt;/&lt;slug&gt;/ under serve</span></span>
1
+ import{_ as t,c as s,o as a,ag as o}from"./chunks/framework.CAZyNGu9.js";const u=JSON.parse('{"title":"Sessions, events, and streaming","description":"Understand how conversations continue, how events stream, and where session data lives.","frontmatter":{"title":"Sessions, events, and streaming","description":"Understand how conversations continue, how events stream, and where session data lives."},"headers":[],"relativePath":"reference/sessions.md","filePath":"reference/sessions.md"}'),n={name:"reference/sessions.md"};function i(d,e,r,c,l,h){return a(),s("div",null,[...e[0]||(e[0]=[o(`<h1 id="sessions-events-and-streaming" tabindex="-1">Sessions, events, and streaming <a class="header-anchor" href="#sessions-events-and-streaming" aria-label="Permalink to &quot;Sessions, events, and streaming&quot;">​</a></h1><p>A session keeps one conversation, its workspace, and an append-only record of every message and tool call.</p><h2 id="what-does-a-session-contain" tabindex="-1">What does a session contain? <a class="header-anchor" href="#what-does-a-session-contain" aria-label="Permalink to &quot;What does a session contain?&quot;">​</a></h2><p>Each session combines:</p><ul><li>A channel and authenticated caller</li><li>A conversation the caller can continue</li><li>A workspace for local turns</li><li>An NDJSON event stream</li><li>Runtime state needed to resume after a server restart</li></ul><p>Sessions belong to the principal that created them. Follow-up, stream, and list routes return <code>403</code> when another caller tries to access one.</p><h2 id="which-session-identifier-should-i-use" tabindex="-1">Which session identifier should I use? <a class="header-anchor" href="#which-session-identifier-should-i-use" aria-label="Permalink to &quot;Which session identifier should I use?&quot;">​</a></h2><p>Sessions have two identifiers because conversation routing and inspection are different jobs.</p><table tabindex="0"><thead><tr><th>Identifier</th><th>Use it for</th></tr></thead><tbody><tr><td><code>continuationToken</code></td><td>Continue a conversation through its channel</td></tr><tr><td><code>sessionId</code></td><td>Stream events, inspect state, resolve approvals, or run a session-bound tool call</td></tr></tbody></table><p>Channels decide what a continuation token looks like. Slack uses its thread identity. A PR channel can use a key such as <code>pr:owner/repo#1</code>. The built-in HTTP API returns an opaque token and rotates it after each accepted follow-up. Reusing a stale HTTP token returns <code>409</code>.</p><p>Use the continuation token to keep talking. Use the session ID to observe or manage the stored session.</p><h2 id="which-session-modes-are-available" tabindex="-1">Which session modes are available? <a class="header-anchor" href="#which-session-modes-are-available" aria-label="Permalink to &quot;Which session modes are available?&quot;">​</a></h2><table tabindex="0"><thead><tr><th>Mode</th><th>Created by</th><th>What happens after a turn</th></tr></thead><tbody><tr><td><code>chat</code></td><td>HTTP sessions, channel <code>send</code>, Slack, or MCP <code>ask</code></td><td>Waits in <code>session.waiting</code> and accepts follow-ups</td></tr><tr><td><code>task</code></td><td>Markdown schedules and fire-and-forget dispatch</td><td>Ends in <code>session.completed</code> or <code>session.failed</code></td></tr></tbody></table><p>Task sessions don&#39;t accept follow-ups. Trying one returns <code>409</code>.</p><h2 id="what-happens-when-i-send-a-follow-up" tabindex="-1">What happens when I send a follow-up? <a class="header-anchor" href="#what-happens-when-i-send-a-follow-up" aria-label="Permalink to &quot;What happens when I send a follow-up?&quot;">​</a></h2><p>A follow-up to an idle chat session starts another turn. Admission when the session is already busy depends on the channel:</p><table tabindex="0"><thead><tr><th>Path</th><th>Busy-session policy</th></tr></thead><tbody><tr><td>HTTP playground / <code>POST /v1/session/:id</code> / MCP <code>ask</code></td><td><strong>Preempt</strong> (default): interrupt the in-flight turn, wait for it to settle, then run the new message</td></tr><tr><td>Slack mentions / DMs / alert-watch</td><td><strong>Coalesce</strong>: leave the active turn running, enqueue the follow-up, and drain queued asks into one follow-up turn when the active turn finishes (no mid-turn tool/hook inject)</td></tr></tbody></table><p>Pass <code>admission: &quot;coalesce&quot;</code> on <code>send()</code> to opt into the Slack policy from other callers. Omit it (or pass <code>&quot;preempt&quot;</code>) to keep interrupt semantics.</p><p><code>POST /v1/session/:id/stop</code> interrupts a turn without sending a new message. Interrupted turns record <code>turn.failed</code> with <code>&quot;turn interrupted&quot;</code>. This means the turn was preempted. A whole-message Slack <code>stop</code> / <code>@agent stop</code> does the same for that thread and clears pending coalesced nudges.</p><p>Session-bound deterministic tool calls share the same execution lock. They return <code>409 session_busy</code> while a model turn is running.</p><h2 id="which-events-can-i-stream" tabindex="-1">Which events can I stream? <a class="header-anchor" href="#which-events-can-i-stream" aria-label="Permalink to &quot;Which events can I stream?&quot;">​</a></h2><p>Each NDJSON line uses this envelope: <code>{ type, index, sessionId, turnId?, at, data }</code>. The <code>index</code> increases within one session. The <code>at</code> field is an ISO-8601 timestamp.</p><table tabindex="0"><thead><tr><th>Phase</th><th>Events</th><th>What they tell you</th></tr></thead><tbody><tr><td>Session</td><td><code>session.started</code>, <a href="./../ab.html#assign-sticky-variants"><code>ab.assigned</code></a>, <code>session.waiting</code>, <code>session.completed</code>, <code>session.failed</code></td><td>Session creation, A/B enrollment, readiness, and task completion</td></tr><tr><td>Agent</td><td><code>agent.bound</code></td><td>Cursor SDK agent ID and cloud conversation URL</td></tr><tr><td>Input</td><td><code>message.received</code></td><td>A user message was accepted</td></tr><tr><td>Turn</td><td><code>turn.started</code>, <code>turn.completed</code>, <code>turn.failed</code></td><td>Turn status, final result, and token usage</td></tr><tr><td>Steps</td><td><code>step.started</code>, <code>step.completed</code></td><td>Model step boundaries and duration</td></tr><tr><td>Reasoning</td><td><code>reasoning.appended</code>, <code>reasoning.completed</code></td><td>Streamed reasoning blocks</td></tr><tr><td>Reply</td><td><code>message.appended</code>, <code>message.completed</code></td><td>Text deltas and finalized assistant messages</td></tr><tr><td>Tools</td><td><code>actions.requested</code>, <code>action.result</code></td><td>Tool names, validated arguments, outputs, and errors</td></tr><tr><td>Approvals</td><td><code>action.approval_requested</code>, <code>action.approval_resolved</code></td><td>A parked tool call and the human decision</td></tr><tr><td>Subagents</td><td><code>subagent.called</code>, <code>subagent.completed</code></td><td>Delegated work</td></tr></tbody></table><p>Pair <code>actions.requested</code> with <code>action.result</code> to reconstruct the tool trajectory. Read <code>turn.completed.data.usage</code> for input, output, and cache token counts.</p><h2 id="how-do-i-stream-or-replay-session-events" tabindex="-1">How do I stream or replay session events? <a class="header-anchor" href="#how-do-i-stream-or-replay-session-events" aria-label="Permalink to &quot;How do I stream or replay session events?&quot;">​</a></h2><p>One endpoint handles both live streaming and replay:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">curl</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -N</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &#39;http://127.0.0.1:3000/&lt;slug&gt;/v1/session/ses_…/stream?startIndex=0&#39;</span></span></code></pre></div><p>Pass <code>startIndex</code> to continue after the last event you received. Omit it or pass <code>0</code> to replay the full session before following new events. <code>GET /v1/session/:id/events</code> returns a one-time dump without staying connected.</p><p>Event streams replay from disk after a server restart. Conversation state resumes from the Cursor SDK store.</p><h2 id="what-goes-into-a-local-session-workspace" tabindex="-1">What goes into a local session workspace? <a class="header-anchor" href="#what-goes-into-a-local-session-workspace" aria-label="Permalink to &quot;What goes into a local session workspace?&quot;">​</a></h2><p>agentkit creates a workspace before the first local turn:</p><table tabindex="0"><thead><tr><th>Source path</th><th>Lands as</th></tr></thead><tbody><tr><td><code>instructions.*</code></td><td><code>AGENTS.md</code></td></tr><tr><td><code>skills/*</code></td><td><code>.cursor/skills/&lt;name&gt;/SKILL.md</code></td></tr><tr><td>agent tools (<code>execution: &quot;agent&quot;</code>)</td><td>scripts under <code>.agentkit/tools/</code>, with a catalog in <code>AGENTS.md</code></td></tr><tr><td><code>sandbox/workspace/**</code></td><td>copied in as seed files</td></tr><tr><td>per-send <code>workspaceFiles</code></td><td>written before the turn</td></tr></tbody></table><p>The local harness uses this workspace as its working directory. Parent directories can contribute <code>AGENTS.md</code> and <code>.cursor</code> settings. Set <code>local.cwd</code> when you need a clean parent directory. A channel can also provide a different working directory for one session, such as a PR worktree.</p><p>See <a href="./agent-config.html#local-cwd">Agent config: local cwd</a> for the inheritance rules.</p><h2 id="where-does-agentkit-store-session-data" tabindex="-1">Where does agentkit store session data? <a class="header-anchor" href="#where-does-agentkit-store-session-data" aria-label="Permalink to &quot;Where does agentkit store session data?&quot;">​</a></h2><p>Local state uses one directory tree:</p><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>&lt;project&gt;/.agentkit/ # or &lt;stateRoot&gt;/&lt;slug&gt;/ under serve</span></span>
2
2
  <span class="line"><span> sessions/&lt;id&gt;/session.json # metadata: channel, mode, principal, tokens</span></span>
3
3
  <span class="line"><span> sessions/&lt;id&gt;/events.ndjson # the durable stream</span></span>
4
4
  <span class="line"><span> sessions/&lt;id&gt;/workspace/ # the harness cwd</span></span>
5
5
  <span class="line"><span> traces/&lt;sessionId&gt;.ndjson # written by \`run\`</span></span>
6
6
  <span class="line"><span> runner/ # Cursor SDK conversation store</span></span>
7
7
  <span class="line"><span> tool-calls/&lt;callId&gt;/ # ephemeral deterministic-call workspaces</span></span></code></pre></div><p>Deleting a session directory removes the session from the server: it disappears from listings and can no longer be streamed or continued. The <code>runner/</code> store keeps its own conversation copy until you remove it. Cloud conversations remain on the Cursor backend.</p><p>Change the root with <code>--state-root</code> or <code>stateRoot</code>. Keep it outside repositories whose parent rules shouldn&#39;t reach the agent. See <a href="./../concepts.html#what-files-can-a-local-session-access">local session workspaces</a>.</p><h2 id="how-do-i-inspect-a-saved-event-stream" tabindex="-1">How do I inspect a saved event stream? <a class="header-anchor" href="#how-do-i-inspect-a-saved-event-stream" aria-label="Permalink to &quot;How do I inspect a saved event stream?&quot;">​</a></h2><p>Use <code>trajectory</code> with a trace or session event file:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> trajectory</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --events</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> .agentkit/traces/</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&lt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">sessionI</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&gt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">.ndjson</span></span>
8
- <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> trajectory</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --events</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">stateRoo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&gt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">/</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&lt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">slu</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">g</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&gt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">/sessions/</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&lt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">i</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&gt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">/events.ndjson</span></span></code></pre></div><p>The command prints tool calls, the reply, and token usage in the same JSON shape as <code>run</code>. Use <strong>Open trace</strong> in the playground for a visual view.</p><h2 id="related" tabindex="-1">Related <a class="header-anchor" href="#related" aria-label="Permalink to &quot;Related&quot;">​</a></h2><ul><li><a href="./http-api.html">HTTP API</a></li><li><a href="./hooks.html">Hooks</a></li><li><a href="./../ab.html">Live A/B metrics</a></li><li><a href="./../concepts.html">How agentkit works</a></li></ul>`,43)])])}const k=s(o,[["render",i]]);export{u as __pageData,k as default};
8
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">agentkit</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> trajectory</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --events</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">stateRoo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">t</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&gt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">/</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&lt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">slu</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">g</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&gt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">/sessions/</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&lt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">i</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">d</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">&gt;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">/events.ndjson</span></span></code></pre></div><p>The command prints tool calls, the reply, and token usage in the same JSON shape as <code>run</code>. Use <strong>Open trace</strong> in the playground for a visual view.</p><h2 id="related" tabindex="-1">Related <a class="header-anchor" href="#related" aria-label="Permalink to &quot;Related&quot;">​</a></h2><ul><li><a href="./http-api.html">HTTP API</a></li><li><a href="./hooks.html">Hooks</a></li><li><a href="./../ab.html">Live A/B metrics</a></li><li><a href="./../concepts.html">How agentkit works</a></li></ul>`,45)])])}const k=t(n,[["render",i]]);export{u as __pageData,k as default};
@@ -1 +1 @@
1
- import{_ as s,c as t,o as a,ag as n}from"./chunks/framework.CAZyNGu9.js";const u=JSON.parse('{"title":"Sessions, events, and streaming","description":"Understand how conversations continue, how events stream, and where session data lives.","frontmatter":{"title":"Sessions, events, and streaming","description":"Understand how conversations continue, how events stream, and where session data lives."},"headers":[],"relativePath":"reference/sessions.md","filePath":"reference/sessions.md"}'),o={name:"reference/sessions.md"};function i(d,e,r,l,c,h){return a(),t("div",null,[...e[0]||(e[0]=[n("",43)])])}const k=s(o,[["render",i]]);export{u as __pageData,k as default};
1
+ import{_ as t,c as s,o as a,ag as o}from"./chunks/framework.CAZyNGu9.js";const u=JSON.parse('{"title":"Sessions, events, and streaming","description":"Understand how conversations continue, how events stream, and where session data lives.","frontmatter":{"title":"Sessions, events, and streaming","description":"Understand how conversations continue, how events stream, and where session data lives."},"headers":[],"relativePath":"reference/sessions.md","filePath":"reference/sessions.md"}'),n={name:"reference/sessions.md"};function i(d,e,r,c,l,h){return a(),s("div",null,[...e[0]||(e[0]=[o("",45)])])}const k=t(n,[["render",i]]);export{u as __pageData,k as default};
@@ -1,4 +1,4 @@
1
- import{_ as e,c as t,o as i,ag as a}from"./chunks/framework.CAZyNGu9.js";const c=JSON.parse(`{"title":"Storage","description":"Point agentkit's durable storage at a backend you own with defineStorage.","frontmatter":{"title":"Storage","description":"Point agentkit's durable storage at a backend you own with defineStorage."},"headers":[],"relativePath":"storage.md","filePath":"storage.md"}`),n={name:"storage.md"};function o(d,s,r,h,l,p){return i(),t("div",null,[...s[0]||(s[0]=[a(`<h1 id="storage" tabindex="-1">Storage <a class="header-anchor" href="#storage" aria-label="Permalink to &quot;Storage&quot;">​</a></h1><p>agentkit owns durable storage for sessions, continuation tokens, reminders, playground eval history, and live A/B samples. It chooses the keys (under <code>agentkit/v1/</code>), when to read and write, and how to restore after restart.</p><p>Keys have bounded length: caller-controlled segments (channel ids, continuation tokens) are URI-encoded, and any segment past 256 encoded bytes is replaced by its <code>sha256:…</code> digest — deterministically, so writes and lookups always agree. Backends can rely on this instead of imposing their own key-length caps (which would silently drop writes, since a throwing <code>put</code> is at-most-once).</p><p>By default that storage lives under <code>--state-root</code> on local disk. Fine for one machine; it does not survive replacing the host.</p><p>To keep the same framework storage across hosts, plug in a key-value backend with <code>agent/storage.ts</code>. You provide <code>put</code> / <code>get</code> / <code>delete</code> / <code>list</code>. agentkit does the rest.</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// agent/storage.ts</span></span>
1
+ import{_ as t,c as e,o as i,ag as a}from"./chunks/framework.CAZyNGu9.js";const c=JSON.parse(`{"title":"Storage","description":"Point agentkit's durable storage at a backend you own with defineStorage.","frontmatter":{"title":"Storage","description":"Point agentkit's durable storage at a backend you own with defineStorage."},"headers":[],"relativePath":"storage.md","filePath":"storage.md"}`),n={name:"storage.md"};function o(h,s,r,d,l,p){return i(),e("div",null,[...s[0]||(s[0]=[a(`<h1 id="storage" tabindex="-1">Storage <a class="header-anchor" href="#storage" aria-label="Permalink to &quot;Storage&quot;">​</a></h1><p>agentkit owns durable storage for sessions, continuation tokens, reminders, playground eval history, and live A/B samples. It chooses the keys (under <code>agentkit/v1/</code>), when to read and write, and how to restore after restart.</p><p>Keys have bounded length: caller-controlled segments (channel ids, continuation tokens) are URI-encoded, and any segment past 256 encoded bytes is replaced by its <code>sha256:…</code> digest — deterministically, so writes and lookups always agree. Backends can rely on this instead of imposing their own key-length caps (which would silently drop writes, since a throwing <code>put</code> is at-most-once).</p><p>By default that storage lives under <code>--state-root</code> on local disk. Fine for one machine; it does not survive replacing the host.</p><p>To keep the same framework storage across hosts, plug in a key-value backend with <code>agent/storage.ts</code>. You provide <code>put</code> / <code>get</code> / <code>delete</code> / <code>list</code>. agentkit does the rest.</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// agent/storage.ts</span></span>
2
2
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { defineStorage } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;@cursor/july/storage&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
3
3
  <span class="line"></span>
4
4
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> default</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> defineStorage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
@@ -14,4 +14,11 @@ import{_ as e,c as t,o as i,ag as a}from"./chunks/framework.CAZyNGu9.js";const c
14
14
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> restore: { maxSessions: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">500</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
15
15
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
16
16
  <span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // put / get / delete / list …</span></span>
17
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">});</span></span></code></pre></div><table tabindex="0"><thead><tr><th>Knob</th><th>Default</th><th>Meaning</th></tr></thead><tbody><tr><td><code>debounceMs</code></td><td>unset (once per turn)</td><td>Wait this long after activity before writing event batches</td></tr><tr><td><code>restore</code></td><td>caps below</td><td>How much to load at serve start</td></tr><tr><td><code>restore.maxSessions</code></td><td><code>1000</code></td><td>Max sessions loaded at serve start</td></tr><tr><td><code>restore.maxAgeMs</code></td><td>30 days</td><td>Skip older sessions at serve start</td></tr><tr><td><code>restore.maxTotalBytes</code></td><td>1 GiB</td><td>Stop loading once this budget is reached</td></tr></tbody></table><p>Set <code>restore: &quot;off&quot;</code> on high-traffic hosts. Sessions then load when a follow-up arrives instead of at startup.</p><h2 id="restore-after-restart" tabindex="-1">Restore after restart <a class="header-anchor" href="#restore-after-restart" aria-label="Permalink to &quot;Restore after restart&quot;">​</a></h2><p>With <code>get</code> and <code>list</code>, serve can rebuild local state from your store:</p><ul><li>At startup, agentkit loads recent sessions up to the restore caps. Local disk wins when both sides have the same session. Reminders hydrate the same way into <code>--state-root/reminders</code>.</li><li>On demand, a missing continuation token resolves through the store and resumes that session.</li><li>Playground eval history and A/B aggregates can load from the same sink.</li></ul><p>A turn in flight at crash time is not replayed. The next follow-up resumes from the last flushed state.</p>`,19)])])}const g=e(n,[["render",o]]);export{c as __pageData,g as default};
17
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">});</span></span></code></pre></div><table tabindex="0"><thead><tr><th>Knob</th><th>Default</th><th>Meaning</th></tr></thead><tbody><tr><td><code>debounceMs</code></td><td>unset (once per turn)</td><td>Wait this long after activity before writing event batches</td></tr><tr><td><code>restore</code></td><td>caps below</td><td>How much to load at serve start</td></tr><tr><td><code>restore.maxSessions</code></td><td><code>1000</code></td><td>Max sessions loaded at serve start</td></tr><tr><td><code>restore.maxAgeMs</code></td><td>30 days</td><td>Skip older sessions at serve start</td></tr><tr><td><code>restore.maxTotalBytes</code></td><td>1 GiB</td><td>Stop loading once this budget is reached</td></tr></tbody></table><p>Set <code>restore: &quot;off&quot;</code> on high-traffic hosts. Sessions then load when a follow-up arrives instead of at startup.</p><h2 id="restore-after-restart" tabindex="-1">Restore after restart <a class="header-anchor" href="#restore-after-restart" aria-label="Permalink to &quot;Restore after restart&quot;">​</a></h2><p>With <code>get</code> and <code>list</code>, serve can rebuild local state from your store:</p><ul><li>At startup, agentkit loads recent sessions up to the restore caps. Local disk wins when both sides have the same session. Reminders hydrate the same way into <code>--state-root/reminders</code>.</li><li>On demand, a missing continuation token resolves through the store and resumes that session.</li><li>Playground eval history and A/B aggregates can load from the same sink.</li></ul><p>A turn in flight at crash time is not replayed. The next follow-up resumes from the last flushed state.</p><h2 id="author-kv-ctx-host-kv" tabindex="-1">Author KV (<code>ctx.host.kv</code>) <a class="header-anchor" href="#author-kv-ctx-host-kv" aria-label="Permalink to &quot;Author KV (\`ctx.host.kv\`)&quot;">​</a></h2><p>Handlers can store their own JSON under the same sink without minting framework keys:</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">await</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ctx.host.kv.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">put</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;alert-memory/abc&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, { updated: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;…&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> });</span></span>
18
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> prior</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> await</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ctx.host.kv.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">get</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;alert-memory/abc&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">);</span></span></code></pre></div><p>agentkit prefixes author keys as <code>agentkit/v1/{agent}/kv/{key}</code> (same bounded encoding as continuation tokens). Writes <strong>await</strong> the sink and propagate errors — unlike session mirrors, which are at-most-once.</p><p>Without <code>agent/storage.ts</code>, <code>host.kv</code> falls back to files under <code>--state-root/kv</code>. That is fine for local dogfood; it does <strong>not</strong> survive replacing the host. For Cursor-managed hosting, prefer <code>@cursor/july/storage/cursor-hosted</code> so sessions and author KV share the platform Bugbot tables through a control-plane HTTP proxy (authenticated as the deployment pod credential — engines never receive a database URL). For self-hosted Postgres you own, use <code>@cursor/july/storage/postgres-kv</code> and supply <code>BUGBOTDB_URL</code> / <code>AGENT_SERVE_DEPLOYMENT_ID</code> yourself.</p><div class="language-ts vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// agent/storage.ts — Cursor-managed hosting</span></span>
19
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { defineStorage } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;@cursor/july/storage&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
20
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { cursorHostedStorage } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;@cursor/july/storage/cursor-hosted&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
21
+ <span class="line"></span>
22
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">export</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> default</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> defineStorage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">({</span></span>
23
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> ...</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">cursorHostedStorage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(),</span></span>
24
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">});</span></span></code></pre></div><p>Built-in helpers:</p><table tabindex="0"><thead><tr><th>Import</th><th>Backend</th></tr></thead><tbody><tr><td><code>@cursor/july/storage/file-kv</code></td><td>File-per-key under <code>.agent-serve/kv</code></td></tr><tr><td><code>@cursor/july/storage/cursor-hosted</code></td><td>Platform Bugbot <code>agent_serve_*</code> via control-plane proxy</td></tr><tr><td><code>@cursor/july/storage/postgres-kv</code></td><td>Same tables; you supply <code>BUGBOTDB_URL</code> / <code>AGENT_SERVE_DEPLOYMENT_ID</code></td></tr></tbody></table>`,27)])])}const g=t(n,[["render",o]]);export{c as __pageData,g as default};
@@ -1 +1 @@
1
- import{_ as e,c as t,o as i,ag as a}from"./chunks/framework.CAZyNGu9.js";const c=JSON.parse(`{"title":"Storage","description":"Point agentkit's durable storage at a backend you own with defineStorage.","frontmatter":{"title":"Storage","description":"Point agentkit's durable storage at a backend you own with defineStorage."},"headers":[],"relativePath":"storage.md","filePath":"storage.md"}`),n={name:"storage.md"};function o(d,s,r,h,l,p){return i(),t("div",null,[...s[0]||(s[0]=[a("",19)])])}const g=e(n,[["render",o]]);export{c as __pageData,g as default};
1
+ import{_ as t,c as e,o as i,ag as a}from"./chunks/framework.CAZyNGu9.js";const c=JSON.parse(`{"title":"Storage","description":"Point agentkit's durable storage at a backend you own with defineStorage.","frontmatter":{"title":"Storage","description":"Point agentkit's durable storage at a backend you own with defineStorage."},"headers":[],"relativePath":"storage.md","filePath":"storage.md"}`),n={name:"storage.md"};function o(h,s,r,d,l,p){return i(),e("div",null,[...s[0]||(s[0]=[a("",27)])])}const g=t(n,[["render",o]]);export{c as __pageData,g as default};