@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,216 @@
1
+ ---
2
+ title: "Investigate every alert in its own Slack thread"
3
+ 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."
4
+ ---
5
+
6
+ # Investigate every alert in its own Slack thread
7
+
8
+ This agent is an on-call teammate. Alert feeds post into an alerts channel
9
+ as bots. Each new alert dispatches an investigation session pinned to that
10
+ post's thread: the agent reacts 👀 the moment it locks in, investigates
11
+ immediately, and posts brief findings backed by evidence it observed.
12
+ Replies in the thread reach it only after the thread has been quiet for
13
+ about a minute, and reminder tools let it wake itself later to re-check a
14
+ baseline or confirm an alert cleared.
15
+
16
+ Use this example when alerts land in Slack and you want one thread-scoped
17
+ investigation per alert, with an agent that paces its own engagement
18
+ instead of answering every message.
19
+
20
+ [Browse the current alert-investigator source.](../../examples/oncall/)
21
+
22
+ ## Follow an alert
23
+
24
+ 1. An alert feed (Alertmanager, PagerDuty, Datadog) posts a new top-level
25
+ message in the watched alerts channel.
26
+ 2. The channel watch accepts it. `includeBotPosts` lets bot authors
27
+ through; the agent's own posts always stay dropped.
28
+ 3. The handler reacts 👀 on the alert post and sets "Investigating…"
29
+ typing. The reaction is the lock-in signal: this alert has an owner.
30
+ 4. Agentkit creates a session keyed to the alert's thread and dispatches
31
+ immediately. New alerts get no debounce.
32
+ 5. The agent reads the alert, gathers evidence, and posts findings to the
33
+ thread once it has a hypothesis.
34
+ 6. People discuss in the thread. Replies buffer per thread and dispatch as
35
+ one coalesced follow-up after roughly a minute of quiet.
36
+ 7. The agent arms reminders for anything that needs time and posts interim
37
+ updates when new evidence changes the picture.
38
+
39
+ Mentions and DMs skip the watch entirely and behave like ordinary chat.
40
+
41
+ ## Map the files
42
+
43
+ | File | Purpose |
44
+ | --- | --- |
45
+ | [`agent/agent.ts`](../../examples/oncall/agent/agent.ts) | Names the agent and keeps harness workspaces outside any monorepo checkout. |
46
+ | [`agent/instructions.md`](../../examples/oncall/agent/instructions.md) | Engagement rules, the investigation loop, and the message discipline. |
47
+ | [`agent/channels/slack-app.ts`](../../examples/oncall/agent/channels/slack-app.ts) | Dedicated Socket Mode app: watch configuration and handler wiring. |
48
+ | [`agent/lib/alert-watch.ts`](../../examples/oncall/agent/lib/alert-watch.ts) | The engagement policy: lock in on new alerts, coalesce replies. |
49
+ | [`agent/lib/thread-debounce.ts`](../../examples/oncall/agent/lib/thread-debounce.ts) | Per-thread quiet window. |
50
+ | [`agent/lib/alerts.ts`](../../examples/oncall/agent/lib/alerts.ts) | Dispatch classification, prompt building, and thread addressing. |
51
+ | [`agent/lib/slack-api.ts`](../../examples/oncall/agent/lib/slack-api.ts) | Reactions and thread posts on this agent's own token pair. |
52
+ | [`agent/tools/reminders_create.ts`](../../examples/oncall/agent/tools/reminders_create.ts) | Self-scheduled wakes bound to the thread (plus `reminders_list` and `reminders_cancel`). |
53
+ | [`agent/tools/post_thread_update.ts`](../../examples/oncall/agent/tools/post_thread_update.ts) | Interim updates to the thread mid-turn. |
54
+ | [`evals/smoke.eval.ts`](../../examples/oncall/evals/smoke.eval.ts) | Checks identity and the reminder-tool route. |
55
+
56
+ ## Let bot posts through the watch
57
+
58
+ Channel watching drops bot-authored posts by default so two agents can
59
+ never feed each other. Alert channels invert the assumption: the posts
60
+ worth watching come from bots. `channelPosts.includeBotPosts` opts in per
61
+ channel:
62
+
63
+ ```ts
64
+ engagement: {
65
+ channelPosts: {
66
+ allow: ["#alerts"],
67
+ posts: "all",
68
+ includeBotPosts: true,
69
+ },
70
+ },
71
+ ```
72
+
73
+ Loop safety survives the opt-in. The pack matches the watching app's own
74
+ posts by the `bot_id` and bot user id from `auth.test` and drops them, so
75
+ the agent's findings never re-dispatch it. Posts that mention the bot stay
76
+ on the mention path.
77
+
78
+ `posts: "all"` also delivers thread replies. The handler, not the pack,
79
+ decides their pace.
80
+
81
+ ## Pace the engagement
82
+
83
+ The example runs two rhythms:
84
+
85
+ - A new alert dispatches immediately.
86
+ - Thread replies produce one engagement per lull.
87
+
88
+ The pack's `debounceMs` is per message; it exists to let edits settle. This
89
+ agent needs a per-thread window instead, so the handler owns it
90
+ ([`lib/thread-debounce.ts`](../../examples/oncall/agent/lib/thread-debounce.ts)).
91
+ Every reply restarts a 60-second timer keyed by thread. Superseded waiters
92
+ resolve `null` and the handler returns `null` for them. When the thread
93
+ goes quiet, the newest waiter receives the whole batch and dispatches one
94
+ follow-up that lists every message with mentionable attribution.
95
+
96
+ Two details make the window matter. A follow-up that arrives while a turn
97
+ runs preempts that turn (latest message wins), so engaging per message
98
+ would keep cancelling the investigation. And @mentions bypass the window
99
+ through Slack's mention path, so a person who needs the agent now still
100
+ gets it now.
101
+
102
+ ## Schedule your own re-checks
103
+
104
+ Investigations rarely finish in one pass. A baseline comparison needs 20
105
+ minutes of data. An alert that cleared may re-fire. The example hands the
106
+ model three tools over `host.reminders`:
107
+
108
+ - `reminders_create` arms a one-shot (`delay: "20m"`) or recurring
109
+ (`every: "30m"` with a plain-language stop condition) wake bound to the
110
+ thread's conversation.
111
+ - `reminders_list` shows the thread's standing watches.
112
+ - `reminders_cancel` disarms one, and refuses ids that belong to another
113
+ thread's conversation.
114
+
115
+ When a reminder fires, its prompt returns to the same session as a
116
+ follow-up turn, and the reply lands in the alert thread. The instructions
117
+ keep wake prompts generic (re-read live state instead of replaying stale
118
+ numbers) and wake replies to one line, for example "re-checked p99 on
119
+ api-gateway: 120ms, back at baseline, cancelling the watch."
120
+
121
+ Keep these tool filenames if you copy the design: the framework's reminder
122
+ fire prompt tells the model to call `reminders_cancel` by name when a stop
123
+ condition is set.
124
+
125
+ ## Alert people mid-investigation
126
+
127
+ The final reply of each turn posts to the thread on its own.
128
+ `post_thread_update` covers evidence that shouldn't wait for the turn to
129
+ finish: it posts a one-or-two-sentence update through the agent's token,
130
+ with `<@USERID>` mentions for the people who need to act. The instructions
131
+ restrict it to changes in hypothesis, severity, or blast radius. Progress
132
+ narration doesn't qualify.
133
+
134
+ ## Connect the Slack app
135
+
136
+ Channel watching is Socket Mode only, so this example uses a dedicated
137
+ app:
138
+
139
+ ```bash
140
+ agentkit slack init --dir examples/oncall --name "Oncall" --channel-posts
141
+ agentkit slack doctor --prefix ONCALL
142
+ ```
143
+
144
+ The generated manifest subscribes to `message.channels` and
145
+ `message.groups` and includes `reactions:write` for the lock-in reaction.
146
+ Install the app, export `ONCALL_SLACK_BOT_TOKEN` and
147
+ `ONCALL_SLACK_APP_TOKEN`, and invite the bot to each watched channel.
148
+
149
+ `ONCALL_ALERTS_CHANNELS` sets the watch list as comma-separated ids or
150
+ `#names`. It defaults to `#alerts`.
151
+
152
+ Wire observability MCP servers under `agent/mcp-connections/` so evidence
153
+ gathering reaches your logs, metrics, and dashboards. The example ships
154
+ none; without them the agent works from the alert text, its links, and the
155
+ thread.
156
+
157
+ ## Validate and start the server
158
+
159
+ ```bash
160
+ agentkit validate --dir examples/oncall
161
+ agentkit info --dir examples/oncall --json
162
+ agentkit dev examples/oncall
163
+ ```
164
+
165
+ The info output lists four server tools and the watched channel on the
166
+ `slack-app` channel. Missing tokens leave that channel idle without
167
+ stopping the server.
168
+
169
+ In dev mode, reminder timers don't auto-fire. List and fire them by hand
170
+ through the dev routes described in
171
+ [Schedules and reminders](../reference/schedules.md#dispatch-and-dev-mode).
172
+
173
+ ## Test the policy without Slack
174
+
175
+ The engagement policy is plain code with unit tests:
176
+
177
+ ```bash
178
+ pnpm exec vitest run examples/oncall
179
+ ```
180
+
181
+ The integration test drives a synthetic Events API delivery through the
182
+ real parse, watch, and dispatch plumbing. It asserts a bot alert
183
+ dispatches pinned to its thread after the lock-in reaction, the agent's
184
+ own posts never loop, and replies coalesce behind the quiet window.
185
+
186
+ The smoke eval spends a model turn:
187
+
188
+ ```bash
189
+ agentkit eval --dir examples/oncall smoke --json
190
+ ```
191
+
192
+ It checks identity and the reminder-tool route lexically. It doesn't prove
193
+ Slack delivery or reaction behavior; the unit tests cover the dispatch
194
+ side, and a live check needs the dedicated app connected.
195
+
196
+ ## Build an alert investigator
197
+
198
+ Use this structure when a bot feed should drive thread-scoped work:
199
+
200
+ 1. Watch the feed channel with `includeBotPosts: true` and a narrow
201
+ allowlist.
202
+ 2. Acknowledge on the triggering post before dispatching, so people see
203
+ ownership without opening the thread.
204
+ 3. Dispatch new items immediately; coalesce thread chatter behind a
205
+ per-thread quiet window.
206
+ 4. Give the agent reminder tools for anything that needs time, and make
207
+ cancel discipline part of the instructions.
208
+ 5. Keep every posted message brief and tied to evidence the agent saw.
209
+
210
+ ## Where to go next
211
+
212
+ - [Slack](../guides/slack.md)
213
+ - [Schedules and reminders](../reference/schedules.md)
214
+ - [Tools](../reference/tools.md)
215
+ - [Playbook router](./benny.md) for the human-post variant of channel
216
+ watching
@@ -71,7 +71,7 @@ status from the finding count even when the review posting result says
71
71
  | Progress channel | [`agent/channels/asr-progress.ts`](../../examples/security-reviewer/agent/channels/asr-progress.ts) | Serve live reviewer and triage state by `runId`. |
72
72
  | Playground renderer | [`agent/playground/tools/run_reviewers.tsx`](../../examples/security-reviewer/agent/playground/tools/run_reviewers.tsx) | Replace the generic tool chip with live module rows. |
73
73
  | SDK review pipeline | [`agent/lib/asr/`](../../examples/security-reviewer/agent/lib/asr/), [`review-stages.ts`](../../examples/security-reviewer/agent/lib/review-stages.ts) | Select modules, call model workers, triage, deduplicate, and write artifacts. |
74
- | Storage | [`agent/storage.ts`](../../examples/security-reviewer/agent/storage.ts) | Persist framework sessions through a custom adapter with lazy restore. |
74
+ | Storage | [`agent/storage.ts`](../../examples/security-reviewer/agent/storage.ts) | Persist framework sessions with `cursorHostedStorage` (lazy restore). |
75
75
  | A/B | [`agent/ab.ts`](../../examples/security-reviewer/agent/ab.ts), [`agent/ab.config.ts`](../../examples/security-reviewer/agent/ab.config.ts) | Compare all-severity versus high-only GitHub comments. |
76
76
  | Eval | [`evals/`](../../examples/security-reviewer/evals/) | Check stage-tool presence against a pinned sample. |
77
77
 
@@ -190,10 +190,10 @@ findings, accounting, and audit events.
190
190
 
191
191
  ## Separate session storage from review artifacts
192
192
 
193
- `defineStorage` sends agentkit session and event records to a custom durable
194
- store. Security Reviewer sets `restore: "off"` so startup doesn't load old
195
- review sessions in bulk. A continuation lookup can still fetch a needed
196
- session.
193
+ `defineStorage` + `cursorHostedStorage` sends agentkit session and event records
194
+ to Cursor-hosted Bugbot storage through the control-plane proxy. Security
195
+ Reviewer sets `restore: "off"` so startup doesn't load old review sessions in
196
+ bulk. A continuation lookup can still fetch a needed session.
197
197
 
198
198
  The staged review files are separate from session storage. Session-store
199
199
  durability doesn't preserve those files. All stages for one `runId` must see
@@ -146,6 +146,7 @@ export default slackChannel({
146
146
  allow: ["#triage-alerts"], // explicit allowlist; no wildcard exists
147
147
  posts: "top-level", // default: thread replies never dispatch
148
148
  debounceMs: 15_000, // optional: let rapid edits settle
149
+ includeBotPosts: false, // default: bot-authored posts never dispatch
149
150
  },
150
151
  },
151
152
  onChannelPost: async (ctx, message) => {
@@ -160,12 +161,20 @@ on the Slack app (Socket Mode only — not available with
160
161
  `cursorAccount: true`). Generate manifests with `slack init --channel-posts`,
161
162
  or add the events and re-import. The bot must also be a member of each
162
163
  watched channel. Posts that mention the bot stay on the `app_mention`
163
- path, bot-authored posts never dispatch, and deleting a post inside the
164
- debounce window cancels its dispatch. Watched posts reuse the
164
+ path, bot-authored posts never dispatch by default, and deleting a post
165
+ inside the debounce window cancels its dispatch. Watched posts reuse the
165
166
  thread-scoped principal, so a later @mention continues the same session.
166
167
  Set `engagement.mentions` or `engagement.directMessages` to `false` to
167
168
  turn those surfaces off.
168
169
 
170
+ Set `includeBotPosts: true` when the posts worth watching come from bots:
171
+ alert feeds, webhook integrations, or other agents posting notes. The
172
+ watching app's own posts stay dropped either way, matched by the `bot_id`
173
+ and bot user id from `auth.test`, so an agent can never dispatch on its
174
+ own replies. The
175
+ [alert investigator example](../example-agents/oncall.md) watches a
176
+ bot-fed alerts channel this way.
177
+
169
178
  ## Prepare work on the host
170
179
 
171
180
  Mention and DM handlers may return a prepared `message`,
@@ -50,13 +50,22 @@ Task sessions don't accept follow-ups. Trying one returns `409`.
50
50
 
51
51
  ## What happens when I send a follow-up?
52
52
 
53
- A follow-up to an idle chat session starts another turn. A follow-up to
54
- a busy session interrupts the current turn, waits for it to settle, and
55
- then sends the new message.
53
+ A follow-up to an idle chat session starts another turn. Admission when
54
+ the session is already busy depends on the channel:
55
+
56
+ | Path | Busy-session policy |
57
+ | --- | --- |
58
+ | HTTP playground / `POST /v1/session/:id` / MCP `ask` | **Preempt** (default): interrupt the in-flight turn, wait for it to settle, then run the new message |
59
+ | Slack mentions / DMs / alert-watch | **Coalesce**: 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) |
60
+
61
+ Pass `admission: "coalesce"` on `send()` to opt into the Slack policy from
62
+ other callers. Omit it (or pass `"preempt"`) to keep interrupt semantics.
56
63
 
57
64
  `POST /v1/session/:id/stop` interrupts a turn without sending a new
58
65
  message. Interrupted turns record `turn.failed` with
59
- `"turn interrupted"`. This means the turn was preempted.
66
+ `"turn interrupted"`. This means the turn was preempted. A whole-message
67
+ Slack `stop` / `@agent stop` does the same for that thread and clears
68
+ pending coalesced nudges.
60
69
 
61
70
  Session-bound deterministic tool calls share the same execution lock.
62
71
  They return `409 session_busy` while a model turn is running.
package/docs/storage.md CHANGED
@@ -96,3 +96,44 @@ With `get` and `list`, serve can rebuild local state from your store:
96
96
 
97
97
  A turn in flight at crash time is not replayed. The next follow-up
98
98
  resumes from the last flushed state.
99
+
100
+ ## Author KV (`ctx.host.kv`)
101
+
102
+ Handlers can store their own JSON under the same sink without minting
103
+ framework keys:
104
+
105
+ ```ts
106
+ await ctx.host.kv.put("alert-memory/abc", { updated: "…" });
107
+ const prior = await ctx.host.kv.get("alert-memory/abc");
108
+ ```
109
+
110
+ agentkit prefixes author keys as `agentkit/v1/{agent}/kv/{key}` (same
111
+ bounded encoding as continuation tokens). Writes **await** the sink and
112
+ propagate errors — unlike session mirrors, which are at-most-once.
113
+
114
+ Without `agent/storage.ts`, `host.kv` falls back to files under
115
+ `--state-root/kv`. That is fine for local dogfood; it does **not**
116
+ survive replacing the host. For Cursor-managed hosting, prefer
117
+ `@cursor/july/storage/cursor-hosted` so sessions and author KV share the
118
+ platform Bugbot tables through a control-plane HTTP proxy (authenticated
119
+ as the deployment pod credential — engines never receive a database URL).
120
+ For self-hosted Postgres you own, use `@cursor/july/storage/postgres-kv`
121
+ and supply `BUGBOTDB_URL` / `AGENT_SERVE_DEPLOYMENT_ID` yourself.
122
+
123
+ ```ts
124
+ // agent/storage.ts — Cursor-managed hosting
125
+ import { defineStorage } from "@cursor/july/storage";
126
+ import { cursorHostedStorage } from "@cursor/july/storage/cursor-hosted";
127
+
128
+ export default defineStorage({
129
+ ...cursorHostedStorage(),
130
+ });
131
+ ```
132
+
133
+ Built-in helpers:
134
+
135
+ | Import | Backend |
136
+ | --- | --- |
137
+ | `@cursor/july/storage/file-kv` | File-per-key under `.agent-serve/kv` |
138
+ | `@cursor/july/storage/cursor-hosted` | Platform Bugbot `agent_serve_*` via control-plane proxy |
139
+ | `@cursor/july/storage/postgres-kv` | Same tables; you supply `BUGBOTDB_URL` / `AGENT_SERVE_DEPLOYMENT_ID` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cursor/july",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "(early alpha) Filesystem-first framework for defining Cursor agents as markdown and TypeScript and serving them over channels with the Cursor SDK.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "repository": {
@@ -124,6 +124,27 @@
124
124
  "import": "./dist/storage.js",
125
125
  "default": "./dist/storage.js"
126
126
  },
127
+ "./storage/file-kv": {
128
+ "anysphere-source": "./src/storage-backends/file-kv.ts",
129
+ "bun": "./src/storage-backends/file-kv.ts",
130
+ "types": "./dist/storage-backends/file-kv.d.ts",
131
+ "import": "./dist/storage-backends/file-kv.js",
132
+ "default": "./dist/storage-backends/file-kv.js"
133
+ },
134
+ "./storage/postgres-kv": {
135
+ "anysphere-source": "./src/storage-backends/postgres-kv.ts",
136
+ "bun": "./src/storage-backends/postgres-kv.ts",
137
+ "types": "./dist/storage-backends/postgres-kv.d.ts",
138
+ "import": "./dist/storage-backends/postgres-kv.js",
139
+ "default": "./dist/storage-backends/postgres-kv.js"
140
+ },
141
+ "./storage/cursor-hosted": {
142
+ "anysphere-source": "./src/storage-backends/cursor-hosted.ts",
143
+ "bun": "./src/storage-backends/cursor-hosted.ts",
144
+ "types": "./dist/storage-backends/cursor-hosted.d.ts",
145
+ "import": "./dist/storage-backends/cursor-hosted.js",
146
+ "default": "./dist/storage-backends/cursor-hosted.js"
147
+ },
127
148
  "./package.json": {
128
149
  "anysphere-source": "./package.json",
129
150
  "bun": "./package.json",
@@ -97,10 +97,12 @@ Two handles, two owners:
97
97
  - **sessionId** — runtime-owned handle for streaming/inspection
98
98
  (`GET /v1/session/:id/stream?startIndex=N`, durable NDJSON replay).
99
99
 
100
- A follow-up to a busy session **interrupts the in-flight turn** and waits
101
- for it to settle before the new turn sends. `POST /v1/session/:id/stop`
102
- interrupts without sending. Session-bound deterministic tool calls are
103
- rejected with `409 session_busy` while a turn runs.
100
+ A follow-up to a busy HTTP/MCP session **interrupts the in-flight turn**
101
+ and waits for it to settle before the new turn sends. Slack uses
102
+ **coalesce** instead (enqueue + boundary drain; see
103
+ `docs/reference/sessions.md`). `POST /v1/session/:id/stop` interrupts
104
+ without sending. Session-bound deterministic tool calls are rejected with
105
+ `409 session_busy` while a turn runs.
104
106
 
105
107
  Key built-in routes (each agent, under `/<slug>` in multi-agent mode):
106
108
  `POST /v1/session`, `POST /v1/session/:id`, `GET /v1/session/:id/stream`,
@@ -89,6 +89,10 @@ Usage:
89
89
  ${CLI} call <tool> [--input <json>] [--dir <path> | --url <baseUrl> | --prod]
90
90
  [--session <id>] [--slug <slug>] [--team <id>]
91
91
  ${CLI} init [dir] [--json]
92
+ ${CLI} mcp --prod [--slug <slug>] [--team <id>] [--dir <path>]
93
+ | --url <baseUrl> [--bearer-token <t>]
94
+ ${CLI} mcp install [--name <serverName>] [--print] [--json]
95
+ (same target flags as mcp)
92
96
  ${CLI} eval [--dir <path>] [evalId...] [--list] [--json]
93
97
  [--tag <tag>]... [--verbose] [--timeout-ms <n>]
94
98
  [--no-stream] [--no-wait]
@@ -158,6 +162,15 @@ Commands:
158
162
  run login when unsigned, then print next steps. --json is
159
163
  machine-readable and never blocks on login. No directory:
160
164
  print the setup guide.
165
+ mcp Serve this agent's MCP endpoint over stdio for MCP clients
166
+ that spawn local servers (e.g. Cursor). Proxies
167
+ newline-delimited JSON-RPC to <target>/v1/mcp. --prod resolves
168
+ the hosted deployment with the signed-in Cursor account and
169
+ re-mints short-lived engine credentials as they expire;
170
+ --url targets a running server. stdout is reserved for the
171
+ MCP wire. \`mcp install\` writes the matching entry into
172
+ ~/.cursor/mcp.json (--print to preview; --name to override
173
+ the server name).
161
174
  eval Run filesystem evals under evals/**/*.eval.ts (streams on a TTY).
162
175
  Default: ephemeral local harness against evals on disk. Pass
163
176
  --prod to kick off the batch on the team's hosted deployment
@@ -418,8 +431,9 @@ async function main(argv: string[]): Promise<number> {
418
431
  const dir = values.dir ?? ".";
419
432
 
420
433
  // Best-effort, cached, and silent on failure. `update` does its own fresh
421
- // lookup, and --json runs stay quiet for machine consumers.
422
- if (command !== "update" && values.json !== true) {
434
+ // lookup, --json runs stay quiet for machine consumers, and `mcp` skips it
435
+ // so MCP client spawns stay fast (stdout there is the protocol wire).
436
+ if (command !== "update" && command !== "mcp" && values.json !== true) {
423
437
  await warnIfOutdated();
424
438
  }
425
439
 
@@ -486,6 +500,22 @@ async function main(argv: string[]): Promise<number> {
486
500
  });
487
501
  }
488
502
 
503
+ case "mcp": {
504
+ const { cmdMcp } = await import("../internal/cli-mcp.js");
505
+ return cmdMcp(positionals[0], {
506
+ dir,
507
+ slug: values.slug,
508
+ team: values.team,
509
+ apiKey: values["api-key"],
510
+ url: values.url,
511
+ prod: values.prod,
512
+ bearerToken: values["bearer-token"],
513
+ name: values.name,
514
+ print: values.print,
515
+ json: values.json,
516
+ });
517
+ }
518
+
489
519
  case "login": {
490
520
  const { cmdLogin } = await import("../internal/cli-cursor.js");
491
521
  return cmdLogin({
@@ -6,7 +6,10 @@
6
6
  * - Top-level posts only by default; thread replies never dispatch unless
7
7
  * `posts: "all"` is configured.
8
8
  * - Posts that @mention the bot are skipped here (the `app_mention` path
9
- * owns them), and bot-authored posts are dropped upstream in parsing.
9
+ * owns them).
10
+ * - Bot-authored posts are dropped unless `includeBotPosts` is configured
11
+ * (alert feeds / webhook integrations); the watching bot's own posts are
12
+ * always dropped either way (loop safety).
10
13
  * - An optional debounce window lets rapid edits settle: edits update the
11
14
  * pending text, deleting the post cancels the dispatch.
12
15
  */
@@ -114,11 +117,14 @@ export function createChannelPostWatcher(args: {
114
117
  allowlist: SlackChannelAllowlist;
115
118
  /** Bot user id (from `auth.test`) so bot mentions route via app_mention. */
116
119
  getBotUserId: () => string | undefined;
120
+ /** This app's own `bot_id` (from `auth.test`) for self-post loop safety. */
121
+ getBotId?: () => string | undefined;
117
122
  dispatch: (message: SlackMessage, eventId?: string) => void;
118
123
  log?: SlackLog;
119
124
  }): ChannelPostWatcher {
120
125
  const log = args.log ?? defaultSlackLog;
121
126
  const includeReplies = args.options.posts === "all";
127
+ const includeBotPosts = args.options.includeBotPosts === true;
122
128
  const debounceMs = args.options.debounceMs ?? 0;
123
129
  const pending = new Map<
124
130
  string,
@@ -185,6 +191,27 @@ export function createChannelPostWatcher(args: {
185
191
  return skip("thread reply (posts: top-level)", message);
186
192
  }
187
193
  const botUserId = args.getBotUserId();
194
+ const author = message.author;
195
+ if (author?.isBot === true) {
196
+ if (!includeBotPosts) {
197
+ return skip("bot-authored post (includeBotPosts off)", message);
198
+ }
199
+ const selfBotId = args.getBotId?.();
200
+ if (selfBotId === undefined && botUserId === undefined) {
201
+ return skip(
202
+ "bot-authored post (bot identity unknown; loop safety)",
203
+ message
204
+ );
205
+ }
206
+ if (
207
+ (selfBotId !== undefined && author.botId === selfBotId) ||
208
+ (botUserId !== undefined &&
209
+ author.userId !== "" &&
210
+ author.userId === botUserId)
211
+ ) {
212
+ return skip("own post (loop safety)", message);
213
+ }
214
+ }
188
215
  if (botUserId !== undefined && message.text.includes(`<@${botUserId}>`)) {
189
216
  return skip("bot mention (app_mention path owns it)", message);
190
217
  }
@@ -114,6 +114,12 @@ export async function dispatchCursorSlackEnvelope(args: {
114
114
  envelope: CursorSlackEnvelope;
115
115
  send: SendMessageFn;
116
116
  host: HostContext;
117
+ hasContinuationSession?: (continuationToken: string) => Promise<boolean>;
118
+ isContinuationBusy?: (continuationToken: string) => Promise<boolean>;
119
+ getContinuationLastBotMessageTs?: (
120
+ continuationToken: string
121
+ ) => Promise<string | undefined>;
122
+ interruptContinuation?: (continuationToken: string) => Promise<boolean>;
117
123
  }): Promise<void> {
118
124
  const { runtime, envelope } = args;
119
125
  const log = runtime.getLog();
@@ -161,6 +167,20 @@ export async function dispatchCursorSlackEnvelope(args: {
161
167
  credentials: runtime.credentials,
162
168
  host: args.host,
163
169
  log,
170
+ ...(args.hasContinuationSession === undefined
171
+ ? {}
172
+ : { hasContinuationSession: args.hasContinuationSession }),
173
+ ...(args.isContinuationBusy === undefined
174
+ ? {}
175
+ : { isContinuationBusy: args.isContinuationBusy }),
176
+ ...(args.getContinuationLastBotMessageTs === undefined
177
+ ? {}
178
+ : {
179
+ getContinuationLastBotMessageTs: args.getContinuationLastBotMessageTs,
180
+ }),
181
+ ...(args.interruptContinuation === undefined
182
+ ? {}
183
+ : { interruptContinuation: args.interruptContinuation }),
164
184
  ...(envelope.eventId === undefined ? {} : { eventId: envelope.eventId }),
165
185
  });
166
186
  }