@cursor/july 0.1.96 → 0.1.99

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 (381) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +3 -2
  3. package/dist/bin/agent-serve.js +13 -3
  4. package/dist/channels/github/github-channel.d.ts.map +1 -1
  5. package/dist/channels/github/github-channel.js +16 -0
  6. package/dist/channels/origin/api.d.ts +15 -4
  7. package/dist/channels/origin/api.d.ts.map +1 -1
  8. package/dist/channels/origin/api.js +19 -3
  9. package/dist/channels/origin/binding.d.ts +11 -0
  10. package/dist/channels/origin/binding.d.ts.map +1 -1
  11. package/dist/channels/origin/binding.js +41 -25
  12. package/dist/channels/origin/index.d.ts +1 -0
  13. package/dist/channels/origin/index.d.ts.map +1 -1
  14. package/dist/channels/origin/index.js +1 -0
  15. package/dist/channels/origin/origin-channel.d.ts +2 -0
  16. package/dist/channels/origin/origin-channel.d.ts.map +1 -1
  17. package/dist/channels/origin/origin-channel.js +40 -4
  18. package/dist/channels/origin/reactions.d.ts +56 -0
  19. package/dist/channels/origin/reactions.d.ts.map +1 -0
  20. package/dist/channels/origin/reactions.js +67 -0
  21. package/dist/channels/origin/types.d.ts +11 -0
  22. package/dist/channels/origin/types.d.ts.map +1 -1
  23. package/dist/channels/slack/approvals.d.ts +16 -0
  24. package/dist/channels/slack/approvals.d.ts.map +1 -1
  25. package/dist/channels/slack/approvals.js +86 -0
  26. package/dist/channels/slack/cli-project.d.ts +19 -0
  27. package/dist/channels/slack/cli-project.d.ts.map +1 -0
  28. package/dist/channels/slack/cli-project.js +234 -0
  29. package/dist/channels/slack/defaults.d.ts.map +1 -1
  30. package/dist/channels/slack/defaults.js +142 -63
  31. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  32. package/dist/channels/slack/dispatch.js +1 -1
  33. package/dist/channels/slack/init.d.ts +3 -2
  34. package/dist/channels/slack/init.d.ts.map +1 -1
  35. package/dist/channels/slack/init.js +5 -5
  36. package/dist/channels/slack/interactive.d.ts +2 -1
  37. package/dist/channels/slack/interactive.d.ts.map +1 -1
  38. package/dist/channels/slack/interactive.js +15 -4
  39. package/dist/channels/slack/live-delivery.d.ts +1 -1
  40. package/dist/channels/slack/live-delivery.d.ts.map +1 -1
  41. package/dist/channels/slack/live-delivery.js +4 -2
  42. package/dist/channels/slack/manifest.d.ts +1 -1
  43. package/dist/channels/slack/manifest.js +1 -1
  44. package/dist/channels/slack/progress-delivery.d.ts +28 -0
  45. package/dist/channels/slack/progress-delivery.d.ts.map +1 -0
  46. package/dist/channels/slack/progress-delivery.js +115 -0
  47. package/dist/channels/slack/setup.d.ts +1 -1
  48. package/dist/channels/slack/setup.d.ts.map +1 -1
  49. package/dist/channels/slack/setup.js +10 -6
  50. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  51. package/dist/channels/slack/slack-channel.js +1 -0
  52. package/dist/channels/slack/tool-approval-events.d.ts.map +1 -1
  53. package/dist/channels/slack/tool-approval-events.js +91 -1
  54. package/dist/channels/slack/types.d.ts +9 -1
  55. package/dist/channels/slack/types.d.ts.map +1 -1
  56. package/dist/docs/404.html +2 -2
  57. package/dist/docs/ab.html +4 -4
  58. package/dist/docs/assets/{app.jXleCmOc.js → app.BplEAZ3S.js} +1 -1
  59. package/dist/docs/assets/chunks/@localSearchIndexroot.DpuI4i7Z.js +1 -0
  60. package/dist/docs/assets/chunks/{VPLocalSearchBox.8LhyglIT.js → VPLocalSearchBox.CHXyv4wP.js} +1 -1
  61. package/dist/docs/assets/chunks/{theme.9ged-pr2.js → theme.BTcsZmjQ.js} +2 -2
  62. package/dist/docs/assets/{deployment.md.D9msOFOW.js → deployment.md.MFPKqsqI.js} +2 -2
  63. package/dist/docs/assets/{guides_mcp-oauth.md.DPYmBCbV.js → guides_mcp-oauth.md.CN-6YmTJ.js} +4 -4
  64. package/dist/docs/assets/{guides_mcp-oauth.md.DPYmBCbV.lean.js → guides_mcp-oauth.md.CN-6YmTJ.lean.js} +1 -1
  65. package/dist/docs/assets/guides_slack.md.VDXQV3ja.js +22 -0
  66. package/dist/docs/assets/guides_slack.md.VDXQV3ja.lean.js +1 -0
  67. package/dist/docs/assets/index.md.CVeRUOeZ.js +5 -0
  68. package/dist/docs/assets/{reference_agent-config.md.Cp_x38Nl.js → reference_agent-config.md.BHeYMgme.js} +1 -1
  69. package/dist/docs/assets/{reference_cli.md.CCnG-nJx.js → reference_cli.md.DfoeyvL0.js} +9 -6
  70. package/dist/docs/assets/{reference_cli.md.CCnG-nJx.lean.js → reference_cli.md.DfoeyvL0.lean.js} +1 -1
  71. package/dist/docs/assets/{reference_connections.md.BH8Oc0D0.js → reference_connections.md.DxldvyIB.js} +4 -3
  72. package/dist/docs/assets/{reference_http-api.md.D89k1mdm.js → reference_http-api.md.BEJx9XVj.js} +2 -2
  73. package/dist/docs/assets/reference_sessions.md.NxFP_CAC.js +1 -0
  74. package/dist/docs/assets/{reference_tools.md.BHeXn2id.js → reference_tools.md.CvAHsdSp.js} +10 -2
  75. package/dist/docs/assets/{reference_tools.md.BHeXn2id.lean.js → reference_tools.md.CvAHsdSp.lean.js} +1 -1
  76. package/dist/docs/assets/templates_agents-md.md.CXSMZljE.js +2 -0
  77. package/dist/docs/assets/templates_agents-md.md.CXSMZljE.lean.js +1 -0
  78. package/dist/docs/assets/troubleshooting.md.DCiPBhYs.js +1 -0
  79. package/dist/docs/building-with-agents.html +4 -4
  80. package/dist/docs/concepts.html +4 -4
  81. package/dist/docs/deployment.html +7 -7
  82. package/dist/docs/deployment.md +5 -4
  83. package/dist/docs/design/runtime-abstraction.md +1757 -0
  84. package/dist/docs/evals.html +4 -4
  85. package/dist/docs/guides/agent-to-agent.html +4 -4
  86. package/dist/docs/guides/cloud-runtime.html +4 -4
  87. package/dist/docs/guides/convert-automation.html +4 -4
  88. package/dist/docs/guides/github.html +4 -4
  89. package/dist/docs/guides/human-in-the-loop.html +4 -4
  90. package/dist/docs/guides/mcp-oauth.html +8 -8
  91. package/dist/docs/guides/mcp-oauth.md +26 -8
  92. package/dist/docs/guides/opentelemetry.html +4 -4
  93. package/dist/docs/guides/slack.html +17 -33
  94. package/dist/docs/guides/slack.md +92 -201
  95. package/dist/docs/guides/webhooks.html +4 -4
  96. package/dist/docs/hashmap.json +1 -1
  97. package/dist/docs/hillclimbing.html +4 -4
  98. package/dist/docs/index.html +6 -6
  99. package/dist/docs/index.md +5 -3
  100. package/dist/docs/llms-full.txt +2065 -637
  101. package/dist/docs/llms.txt +3 -2
  102. package/dist/docs/quickstart.html +4 -4
  103. package/dist/docs/reference/agent-config.html +6 -6
  104. package/dist/docs/reference/agent-config.md +2 -2
  105. package/dist/docs/reference/artifacts.html +4 -4
  106. package/dist/docs/reference/channels.html +4 -4
  107. package/dist/docs/reference/cli.html +13 -10
  108. package/dist/docs/reference/cli.md +32 -16
  109. package/dist/docs/reference/connections.html +9 -8
  110. package/dist/docs/reference/connections.md +6 -3
  111. package/dist/docs/reference/hooks.html +4 -4
  112. package/dist/docs/reference/http-api.html +7 -7
  113. package/dist/docs/reference/http-api.md +3 -1
  114. package/dist/docs/reference/instructions.html +4 -4
  115. package/dist/docs/reference/playground.html +4 -4
  116. package/dist/docs/reference/project-layout.html +4 -4
  117. package/dist/docs/reference/prompt.html +4 -4
  118. package/dist/docs/reference/schedules.html +4 -4
  119. package/dist/docs/reference/sessions.html +5 -5
  120. package/dist/docs/reference/sessions.md +4 -2
  121. package/dist/docs/reference/skills.html +4 -4
  122. package/dist/docs/reference/subagents.html +4 -4
  123. package/dist/docs/reference/tools.html +15 -7
  124. package/dist/docs/reference/tools.md +34 -2
  125. package/dist/docs/scaffolding-agents.html +4 -4
  126. package/dist/docs/storage.html +4 -4
  127. package/dist/docs/templates/agentic-owners.html +4 -4
  128. package/dist/docs/templates/agents-md.html +28 -0
  129. package/dist/docs/templates/agents-md.md +105 -0
  130. package/dist/docs/templates/code-wiki.html +5 -5
  131. package/dist/docs/templates/demo.html +4 -4
  132. package/dist/docs/templates/pr-autofixer.html +4 -4
  133. package/dist/docs/templates/security-help.html +4 -4
  134. package/dist/docs/templates/security-reviewer.html +5 -5
  135. package/dist/docs/templates/triage.html +4 -4
  136. package/dist/docs/troubleshooting.html +5 -5
  137. package/dist/docs/troubleshooting.md +2 -2
  138. package/dist/evals/run-facts.d.ts.map +1 -1
  139. package/dist/evals/run-facts.js +27 -0
  140. package/dist/files-backends/cursor-hosted.d.ts +3 -2
  141. package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
  142. package/dist/files-backends/cursor-hosted.js +3 -2
  143. package/dist/index.d.ts +1 -0
  144. package/dist/index.d.ts.map +1 -1
  145. package/dist/index.js +1 -0
  146. package/dist/internal/advertise-tools.d.ts.map +1 -1
  147. package/dist/internal/advertise-tools.js +15 -9
  148. package/dist/internal/approval-gate.d.ts +12 -13
  149. package/dist/internal/approval-gate.d.ts.map +1 -1
  150. package/dist/internal/approval-gate.js +16 -21
  151. package/dist/internal/as-of.d.ts +19 -0
  152. package/dist/internal/as-of.d.ts.map +1 -0
  153. package/dist/internal/as-of.js +82 -0
  154. package/dist/internal/chat-client.js +10 -0
  155. package/dist/internal/cli-deploy.d.ts +6 -0
  156. package/dist/internal/cli-deploy.d.ts.map +1 -1
  157. package/dist/internal/cli-deploy.js +149 -21
  158. package/dist/internal/cli-mcp-oauth.js +8 -18
  159. package/dist/internal/cli-slack-manual.d.ts +24 -2
  160. package/dist/internal/cli-slack-manual.d.ts.map +1 -1
  161. package/dist/internal/cli-slack-manual.js +251 -20
  162. package/dist/internal/cli-slack.d.ts +9 -2
  163. package/dist/internal/cli-slack.d.ts.map +1 -1
  164. package/dist/internal/cli-slack.js +29 -82
  165. package/dist/internal/cursor/account-mcp.d.ts +1 -0
  166. package/dist/internal/cursor/account-mcp.d.ts.map +1 -1
  167. package/dist/internal/cursor/account-mcp.js +17 -0
  168. package/dist/internal/cursor-agent-template.d.ts +1 -1
  169. package/dist/internal/cursor-agent-template.d.ts.map +1 -1
  170. package/dist/internal/cursor-agent-template.js +1 -0
  171. package/dist/internal/cursor-event-relay.d.ts.map +1 -1
  172. package/dist/internal/cursor-event-relay.js +5 -6
  173. package/dist/internal/deploy-client.d.ts +17 -0
  174. package/dist/internal/deploy-client.d.ts.map +1 -1
  175. package/dist/internal/deploy-client.js +31 -10
  176. package/dist/internal/env-file.d.ts +19 -0
  177. package/dist/internal/env-file.d.ts.map +1 -0
  178. package/dist/internal/env-file.js +90 -0
  179. package/dist/internal/framework-file-storage.d.ts +11 -17
  180. package/dist/internal/framework-file-storage.d.ts.map +1 -1
  181. package/dist/internal/framework-file-storage.js +17 -40
  182. package/dist/internal/framework-storage-selection.d.ts +13 -11
  183. package/dist/internal/framework-storage-selection.d.ts.map +1 -1
  184. package/dist/internal/framework-storage-selection.js +37 -26
  185. package/dist/internal/hosted-admission-adapter.d.ts +28 -0
  186. package/dist/internal/hosted-admission-adapter.d.ts.map +1 -0
  187. package/dist/internal/hosted-admission-adapter.js +7 -0
  188. package/dist/internal/hosted-delivery-protocol.d.ts +13 -1
  189. package/dist/internal/hosted-delivery-protocol.d.ts.map +1 -1
  190. package/dist/internal/hosted-delivery-protocol.js +8 -1
  191. package/dist/internal/hosted-delivery.d.ts +21 -1
  192. package/dist/internal/hosted-delivery.d.ts.map +1 -1
  193. package/dist/internal/hosted-delivery.js +101 -34
  194. package/dist/internal/hosted-execution-diag.d.ts +59 -0
  195. package/dist/internal/hosted-execution-diag.d.ts.map +1 -0
  196. package/dist/internal/hosted-execution-diag.js +187 -0
  197. package/dist/internal/http-channel.d.ts.map +1 -1
  198. package/dist/internal/http-channel.js +85 -2
  199. package/dist/internal/init-project.d.ts.map +1 -1
  200. package/dist/internal/init-project.js +2 -0
  201. package/dist/internal/logs-client.d.ts +5 -0
  202. package/dist/internal/logs-client.d.ts.map +1 -1
  203. package/dist/internal/logs-client.js +5 -0
  204. package/dist/internal/mcp-endpoint.js +2 -3
  205. package/dist/internal/mcp-host.d.ts +10 -0
  206. package/dist/internal/mcp-host.d.ts.map +1 -1
  207. package/dist/internal/mcp-host.js +57 -8
  208. package/dist/internal/mcp-oauth-on-demand.d.ts +68 -0
  209. package/dist/internal/mcp-oauth-on-demand.d.ts.map +1 -0
  210. package/dist/internal/mcp-oauth-on-demand.js +287 -0
  211. package/dist/internal/mcp-oauth-persist.d.ts +42 -0
  212. package/dist/internal/mcp-oauth-persist.d.ts.map +1 -0
  213. package/dist/internal/mcp-oauth-persist.js +92 -0
  214. package/dist/internal/mcp-oauth.d.ts +1 -0
  215. package/dist/internal/mcp-oauth.d.ts.map +1 -1
  216. package/dist/internal/mcp-oauth.js +10 -2
  217. package/dist/internal/null-kv-storage.d.ts +48 -0
  218. package/dist/internal/null-kv-storage.d.ts.map +1 -0
  219. package/dist/internal/null-kv-storage.js +58 -0
  220. package/dist/internal/otel/collector.d.ts.map +1 -1
  221. package/dist/internal/otel/collector.js +22 -9
  222. package/dist/internal/server.d.ts +6 -0
  223. package/dist/internal/server.d.ts.map +1 -1
  224. package/dist/internal/server.js +123 -33
  225. package/dist/internal/session-engine.d.ts +88 -3
  226. package/dist/internal/session-engine.d.ts.map +1 -1
  227. package/dist/internal/session-engine.js +538 -46
  228. package/dist/internal/session-run-log.d.ts +120 -0
  229. package/dist/internal/session-run-log.d.ts.map +1 -0
  230. package/dist/internal/session-run-log.js +359 -0
  231. package/dist/internal/sessions-client.d.ts +9 -7
  232. package/dist/internal/sessions-client.d.ts.map +1 -1
  233. package/dist/internal/sessions-client.js +3 -1
  234. package/dist/internal/storage-coordinator.d.ts +13 -0
  235. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  236. package/dist/internal/storage-coordinator.js +37 -6
  237. package/dist/internal/storage-roles.d.ts +5 -0
  238. package/dist/internal/storage-roles.d.ts.map +1 -1
  239. package/dist/internal/stream-progress.d.ts.map +1 -1
  240. package/dist/internal/stream-progress.js +10 -0
  241. package/dist/internal/tool-policy.d.ts +29 -9
  242. package/dist/internal/tool-policy.d.ts.map +1 -1
  243. package/dist/internal/tool-policy.js +24 -0
  244. package/dist/internal/trajectory.d.ts.map +1 -1
  245. package/dist/internal/trajectory.js +10 -0
  246. package/dist/internal/turn-outbox.d.ts +24 -0
  247. package/dist/internal/turn-outbox.d.ts.map +1 -0
  248. package/dist/internal/turn-outbox.js +80 -0
  249. package/dist/playground/assets/index-D7rGZNjy.css +1 -0
  250. package/dist/playground/assets/index-jzNbJENa.js +69 -0
  251. package/dist/playground/index.html +2 -2
  252. package/dist/storage-backends/cursor-hosted.d.ts +1 -1
  253. package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
  254. package/dist/storage-backends/cursor-hosted.js +12 -0
  255. package/dist/storage-protocol.d.ts +8 -0
  256. package/dist/storage-protocol.d.ts.map +1 -1
  257. package/dist/storage-protocol.js +8 -0
  258. package/dist/storage.d.ts +8 -0
  259. package/dist/storage.d.ts.map +1 -1
  260. package/dist/tools.d.ts +12 -2
  261. package/dist/tools.d.ts.map +1 -1
  262. package/dist/types.d.ts +88 -7
  263. package/dist/types.d.ts.map +1 -1
  264. package/docs/README.md +5 -3
  265. package/docs/deployment.md +5 -4
  266. package/docs/design/runtime-abstraction.md +1757 -0
  267. package/docs/guides/mcp-oauth.md +26 -8
  268. package/docs/guides/slack.md +93 -202
  269. package/docs/reference/agent-config.md +2 -2
  270. package/docs/reference/cli.md +32 -16
  271. package/docs/reference/connections.md +6 -3
  272. package/docs/reference/http-api.md +3 -1
  273. package/docs/reference/sessions.md +4 -2
  274. package/docs/reference/tools.md +34 -2
  275. package/docs/templates/agents-md.md +110 -0
  276. package/docs/troubleshooting.md +2 -2
  277. package/package.json +1 -1
  278. package/skills/create-agent/SKILL.md +2 -1
  279. package/skills/mcp-auth/SKILL.md +6 -3
  280. package/skills/setup-slack/SKILL.md +23 -13
  281. package/src/bin/agent-serve.ts +12 -1
  282. package/src/channels/github/github-channel.ts +17 -0
  283. package/src/channels/origin/api.ts +24 -5
  284. package/src/channels/origin/binding.ts +55 -26
  285. package/src/channels/origin/index.ts +1 -0
  286. package/src/channels/origin/origin-channel.ts +78 -10
  287. package/src/channels/origin/reactions.ts +82 -0
  288. package/src/channels/origin/types.ts +11 -0
  289. package/src/channels/slack/approvals.ts +105 -0
  290. package/src/channels/slack/cli-project.ts +267 -0
  291. package/src/channels/slack/defaults.ts +165 -74
  292. package/src/channels/slack/dispatch.ts +2 -0
  293. package/src/channels/slack/init.ts +10 -5
  294. package/src/channels/slack/interactive.ts +35 -9
  295. package/src/channels/slack/live-delivery.ts +5 -3
  296. package/src/channels/slack/manifest.ts +1 -1
  297. package/src/channels/slack/progress-delivery.ts +133 -0
  298. package/src/channels/slack/setup.ts +10 -6
  299. package/src/channels/slack/slack-channel.ts +1 -0
  300. package/src/channels/slack/tool-approval-events.ts +93 -0
  301. package/src/channels/slack/types.ts +9 -1
  302. package/src/evals/run-facts.ts +32 -0
  303. package/src/files-backends/cursor-hosted.ts +3 -2
  304. package/src/index.ts +4 -0
  305. package/src/internal/advertise-tools.ts +31 -10
  306. package/src/internal/approval-gate.ts +20 -29
  307. package/src/internal/as-of.ts +107 -0
  308. package/src/internal/chat-client.ts +19 -0
  309. package/src/internal/cli-deploy.ts +203 -13
  310. package/src/internal/cli-mcp-oauth.ts +7 -21
  311. package/src/internal/cli-slack-manual.ts +350 -18
  312. package/src/internal/cli-slack.ts +40 -84
  313. package/src/internal/cursor/account-mcp.ts +21 -0
  314. package/src/internal/cursor-agent-template.ts +1 -0
  315. package/src/internal/cursor-event-relay.ts +5 -6
  316. package/src/internal/deploy-client.ts +43 -0
  317. package/src/internal/env-file.ts +84 -0
  318. package/src/internal/framework-file-storage.ts +18 -46
  319. package/src/internal/framework-storage-selection.ts +41 -25
  320. package/src/internal/hosted-admission-adapter.ts +42 -0
  321. package/src/internal/hosted-delivery-protocol.ts +14 -2
  322. package/src/internal/hosted-delivery.ts +169 -44
  323. package/src/internal/hosted-execution-diag.ts +252 -0
  324. package/src/internal/http-channel.ts +107 -0
  325. package/src/internal/init-project.ts +2 -0
  326. package/src/internal/logs-client.ts +5 -0
  327. package/src/internal/mcp-endpoint.ts +13 -4
  328. package/src/internal/mcp-host.ts +77 -11
  329. package/src/internal/mcp-oauth-on-demand.ts +352 -0
  330. package/src/internal/mcp-oauth-persist.ts +127 -0
  331. package/src/internal/mcp-oauth.ts +17 -1
  332. package/src/internal/null-kv-storage.ts +74 -0
  333. package/src/internal/otel/collector.ts +30 -0
  334. package/src/internal/server.ts +159 -11
  335. package/src/internal/session-engine.ts +691 -57
  336. package/src/internal/session-run-log.ts +584 -0
  337. package/src/internal/sessions-client.ts +17 -8
  338. package/src/internal/storage-coordinator.ts +49 -9
  339. package/src/internal/storage-roles.ts +7 -0
  340. package/src/internal/stream-progress.ts +19 -0
  341. package/src/internal/tool-policy.ts +49 -9
  342. package/src/internal/trajectory.ts +10 -0
  343. package/src/internal/turn-outbox.ts +85 -0
  344. package/src/storage-backends/cursor-hosted.ts +13 -1
  345. package/src/storage-protocol.ts +10 -0
  346. package/src/storage.ts +12 -0
  347. package/src/tools.ts +12 -2
  348. package/src/types.ts +93 -8
  349. package/templates/agents-md/README.md +39 -0
  350. package/templates/agents-md/agent/agent.ts +8 -0
  351. package/templates/agents-md/agent/channels/github.ts +10 -0
  352. package/templates/agents-md/agent/channels/queue.ts +28 -0
  353. package/templates/agents-md/agent/channels/slack.ts +9 -0
  354. package/templates/agents-md/agent/instructions.md +39 -0
  355. package/templates/agents-md/agent/lib/collect.ts +439 -0
  356. package/templates/agents-md/agent/lib/config.ts +8 -0
  357. package/templates/agents-md/agent/lib/paths.ts +51 -0
  358. package/templates/agents-md/agent/schedules/weekly.ts +17 -0
  359. package/templates/agents-md/agent/tools/apply_agents.ts +226 -0
  360. package/templates/agents-md/evals/agents.eval.ts +135 -0
  361. package/templates/agents-md/evals/evals.config.ts +5 -0
  362. package/templates/agents-md/init.json +38 -0
  363. package/templates/agents-md/package.json +18 -0
  364. package/templates/agents-md/tsconfig.json +12 -0
  365. package/dist/docs/assets/chunks/@localSearchIndexroot.DFoGuETX.js +0 -1
  366. package/dist/docs/assets/guides_slack.md.C32HsdKk.js +0 -38
  367. package/dist/docs/assets/guides_slack.md.C32HsdKk.lean.js +0 -1
  368. package/dist/docs/assets/index.md.CI93Ltlb.js +0 -5
  369. package/dist/docs/assets/reference_sessions.md.D0mIh4KK.js +0 -1
  370. package/dist/docs/assets/troubleshooting.md.Ctv3T8C2.js +0 -1
  371. package/dist/docs/design/agsh.md +0 -406
  372. package/dist/playground/assets/index-B1DDvBeC.css +0 -1
  373. package/dist/playground/assets/index-Cx9CclK_.js +0 -88
  374. package/docs/design/agsh.md +0 -406
  375. /package/dist/docs/assets/{deployment.md.D9msOFOW.lean.js → deployment.md.MFPKqsqI.lean.js} +0 -0
  376. /package/dist/docs/assets/{index.md.CI93Ltlb.lean.js → index.md.CVeRUOeZ.lean.js} +0 -0
  377. /package/dist/docs/assets/{reference_agent-config.md.Cp_x38Nl.lean.js → reference_agent-config.md.BHeYMgme.lean.js} +0 -0
  378. /package/dist/docs/assets/{reference_connections.md.BH8Oc0D0.lean.js → reference_connections.md.DxldvyIB.lean.js} +0 -0
  379. /package/dist/docs/assets/{reference_http-api.md.D89k1mdm.lean.js → reference_http-api.md.BEJx9XVj.lean.js} +0 -0
  380. /package/dist/docs/assets/{reference_sessions.md.D0mIh4KK.lean.js → reference_sessions.md.NxFP_CAC.lean.js} +0 -0
  381. /package/dist/docs/assets/{troubleshooting.md.Ctv3T8C2.lean.js → troubleshooting.md.DCiPBhYs.lean.js} +0 -0
@@ -693,9 +693,10 @@ use `UPPER_SNAKE_CASE`. Commit names only; set their values after
693
693
  creating the deployment. Names beginning with `CURSOR_` are reserved.
694
694
 
695
695
  For `defineConnection({ url, oauth: true })`, declare
696
- `MCP_OAUTH_<CONNECTION>_*` in `secretNames`, authorize with
697
- `agent-sdk mcp oauth <connection> --store`, then redeploy. See
698
- [Host MCP OAuth](/docs/guides/mcp-oauth.md).
696
+ `MCP_OAUTH_<CONNECTION>_*` in `secretNames`. Finish Connect for the
697
+ current process. Run `agent-sdk mcp oauth <connection> --store` and
698
+ redeploy so the next pod sees the secrets.
699
+ See [Host MCP OAuth](/docs/guides/mcp-oauth.md).
699
700
 
700
701
  Run `agent-sdk validate` before deploying. It reports invalid domains and
701
702
  secret names as warnings, so fix them even when validation exits zero.
@@ -943,7 +944,7 @@ A self-hosted server can read these credentials.
943
944
  | Slack tokens | Slack channels | `<PREFIX>_SLACK_BOT_TOKEN` + `<PREFIX>_SLACK_APP_TOKEN` per agent |
944
945
  | GitHub webhook secret | delivery signature verification | `GITHUB_WEBHOOK_SECRET`, same value on server and signer |
945
946
  | GitHub API | outbound API calls | a GitHub App (`GITHUB_APP_ID` + `GITHUB_APP_PRIVATE_KEY` + installation id) or `GITHUB_TOKEN` / `gh auth login` |
946
- | MCP connection tokens | authored MCP connections | env vars your `mcp-connections/*.ts` read, or host OAuth secrets from `agent-sdk mcp oauth <name> --store` (`MCP_OAUTH_*`; see [Host MCP OAuth](/docs/guides/mcp-oauth.md)) |
947
+ | MCP connection tokens | authored MCP connections | env vars your `mcp-connections/*.ts` read, or host OAuth secrets from hosted Connect / `agent-sdk mcp oauth <name> --store` (`MCP_OAUTH_*`; see [Host MCP OAuth](/docs/guides/mcp-oauth.md)) |
947
948
 
948
949
  Use a dedicated Cursor key per host. `agent-sdk whoami` shows the active
949
950
  credential. `logout` removes the stored key from the host; revoke the key
@@ -1087,414 +1088,1765 @@ Continue with these pages:
1087
1088
 
1088
1089
  ---
1089
1090
 
1090
- Source: /docs/design/agsh.md
1091
+ Source: /docs/design/runtime-abstraction.md
1092
+
1093
+ # Replacing `runtime: "local" | "cloud"` with a code interface
1094
+
1095
+ > **Status:** design proposal for review. No product code in this PR.
1096
+ > Implementation is split across later PRs (see §8). Do not start those
1097
+ > until this document has been reviewed.
1098
+ > **Audience:** Agent SDK and cursor-sdk maintainers; change-monitor as the
1099
+ > first consumer that is already faking a third runtime.
1100
+ > **Companions:** [cloud-runtime.md](/docs/guides/cloud-runtime.md) (today's
1101
+ > user-facing contract),
1102
+ > [factory/change-monitor/docs/tools.md](https://github.com/cursor/cursor/blob/main/factory/change-monitor/docs/tools.md)
1103
+ > (why change-monitor is pinned to local),
1104
+ > [factory/change-monitor/docs/computer-use.md](https://github.com/cursor/cursor/blob/main/factory/change-monitor/docs/computer-use.md)
1105
+ > (why a cloud VM as the *workspace* is not `runtime: "cloud"`).
1106
+ > **Checked against:** `@cursor/july` (`packages/agent-serve`) and
1107
+ > `@cursor/sdk` (`packages/cursor-sdk`) on `origin/main` as of 2026-08-31.
1108
+
1109
+ ## Goals
1110
+
1111
+ 1. **Stay compatible with agents that already ship.**
1112
+ The current `defineAgent` / `AgentOptions` shape keeps
1113
+ working: string `runtime`, sibling `local`, sibling
1114
+ `cloud`. Existing files do not need a rewrite.
1115
+ Constructors are additive (`localRuntime(opts)` takes
1116
+ today's `AgentLocalOptions`). The old fields are
1117
+ **deprecated and still supported** — JSDoc `@deprecated`
1118
+ plus published-docs notes — not removed in Phases 1–4.
1119
+
1120
+ 2. **Default to `localRuntime()`.**
1121
+ That is today's default and the one developers mean when
1122
+ they omit `runtime`: local loop, host workspace, full
1123
+ harness toolset. `defineAgent({})` and
1124
+ `defineAgent({ runtime: "local" })` both become
1125
+ `localRuntime()`.
1126
+
1127
+ 3. **Refuse incoherent pairs at discovery.**
1128
+ The string let authors write `runtime: "cloud"` next to
1129
+ `local: { sandbox, cwd, … }`, or `tools` on a cloud
1130
+ agent, and only some of those failed. `Runtime` is a
1131
+ closed union. `workspace` exists only on the local arm,
1132
+ so `cloudRuntime` cannot take one. `virtualRuntime`
1133
+ refuses `send({ runtime: cloudRuntime(...) })`.
1134
+ Combinations an arm cannot honor are errors, not
1135
+ ignored fields.
1136
+
1137
+ 4. **Replace the enum with an extensible interface.**
1138
+ External users of `@cursor/july` / `@cursor/sdk` should
1139
+ be able to bring their own workspace (a `FileSystem`, a test
1140
+ map, a remote machine mount) without waiting for a new
1141
+ string value. `Workspace` is that interface.
1142
+ Do not grow `"local" | "cloud" | "virtual" | …`.
1143
+
1144
+ The Agent SDK exposes execution as a string on `defineAgent`:
1091
1145
 
1092
- # agsh: a shell for deployed agents
1146
+ ```ts
1147
+ runtime?: "local" | "cloud" | "grokbot";
1148
+ ```
1149
+
1150
+ `"local"` and `"cloud"` look like two places the same agent can run.
1151
+ They are not. The string selects a **loop backend**, a **workspace**, and
1152
+ an **artifact-delivery path** at once, and it already has a third value
1153
+ (`"grokbot"`) that fits neither name. Change-monitor then takes
1154
+ `"local"` and hollows it out: native shell off, native `read`/`grep`/
1155
+ `glob`/`ls` replaced by server tools over a virtual filesystem. That is
1156
+ a virtual workspace on the local loop, forced to claim it is local
1157
+ because there is no other legal value.
1158
+
1159
+ This document replaces the string with a closed `Runtime` union.
1160
+ The local arm carries a **workspace**. Cloud and grokbot do not.
1161
+ The workspace is one object whose optional methods (`read`,
1162
+ `ls`, `shell`, …) are the capabilities. Presence enables the
1163
+ matching tool. All methods share one path namespace.
1164
+ `workspace` exists only on the local arm, so `cloudRuntime`
1165
+ cannot take one.
1166
+
1167
+ The first implementation cut is native redirection in Cursor SDK
1168
+ (`AgentOptions.local.workspace` rebound through local-exec). Agent
1169
+ SDK then authors that workspace as `virtualRuntime(fs)`.
1170
+
1171
+ Lookalike server tools are not good enough. Change-monitor's
1172
+ `read` is an Agent SDK server tool, which the local harness
1173
+ exposes as MCP (`custom-user-tools`). The model gets
1174
+ `GetMcpTools` / `CallMcpTool`, not a builtin `read`. That is
1175
+ the bug. Copying native schemas does not fix the tool the
1176
+ model is offered. The SDK change exists so the model calls
1177
+ harness `read` against the injected workspace.
1178
+
1179
+ ---
1180
+
1181
+ ## 1. Chosen abstraction
1182
+
1183
+ ```ts
1184
+ /**
1185
+ * Native tool args, open for fields the harness adds later.
1186
+ * Known keys match the model-facing input schema. Extra keys
1187
+ * are forwarded, not stripped.
1188
+ */
1189
+ type NativeArgs<T> = T & { [key: string]: unknown };
1190
+
1191
+ /**
1192
+ * Path-namespace verbs. Method names match `@cursor/sdk`
1193
+ * `ToolName`s (`read`, `semSearch`, `readLints`). The model
1194
+ * sees the harness names (`Read`, `SemanticSearch`,
1195
+ * `ReadLints`). `write` is the exception: the model tool is
1196
+ * `Write`, but proto folded it into `edit_tool_call` — there
1197
+ * is no public `tools: ["write"]`. Args and results are those
1198
+ * tools' input/output schemas. `@cursor/sdk` owns this type.
1199
+ */
1200
+ interface FileSystem {
1201
+ ls?(args: NativeArgs<LsArgs>): Promise<LsResult>;
1202
+ glob?(args: NativeArgs<GlobArgs>): Promise<GlobResult>;
1203
+ read?(args: NativeArgs<ReadArgs>): Promise<ReadResult>;
1204
+ grep?(args: NativeArgs<GrepArgs>): Promise<GrepResult>;
1205
+ /** Native Write tool: create or overwrite a whole file. */
1206
+ write?(args: NativeArgs<WriteArgs>): Promise<WriteResult>;
1207
+ /** Native Edit tool: str-replace / multi-replace / apply-patch. */
1208
+ edit?(args: NativeArgs<EditArgs>): Promise<EditResult>;
1209
+ delete?(args: NativeArgs<DeleteArgs>): Promise<DeleteResult>;
1210
+ semSearch?(args: NativeArgs<SemSearchArgs>): Promise<SemSearchResult>;
1211
+ readLints?(args: NativeArgs<ReadLintsArgs>): Promise<ReadLintsResult>;
1212
+ }
1213
+
1214
+ /**
1215
+ * One namespace. File verbs and shell are optional methods —
1216
+ * presence is the capability. They must share `root()`.
1217
+ * Do not implement `read` against Origin and `shell` against
1218
+ * host bash.
1219
+ */
1220
+ interface Workspace extends FileSystem {
1221
+ shell?(args: NativeArgs<ShellArgs>): Promise<ShellResult>;
1222
+ root(): string;
1223
+ brief?(ctx: WorkspaceBriefContext): string;
1224
+ }
1093
1225
 
1094
- ## What this is
1226
+ /**
1227
+ * Closed union. `kind` is internal dispatch. Authors call
1228
+ * constructors. Each constructor returns its arm, not
1229
+ * `Runtime`, so `send({ runtime })` can require
1230
+ * `CloudRuntime | LocalRuntime` and exclude grokbot.
1231
+ * `workspace` exists only on the local arm.
1232
+ */
1233
+ type LocalRuntime = { kind: "local"; workspace: Workspace };
1234
+ type CloudRuntime = { kind: "cloud"; cloud: AgentCloudOptions };
1235
+ type GrokbotRuntime = { kind: "grokbot" };
1236
+ type Runtime = LocalRuntime | CloudRuntime | GrokbotRuntime;
1237
+
1238
+ function localRuntime(opts?: AgentLocalOptions): LocalRuntime {
1239
+ return { kind: "local", workspace: localWorkspace(opts) };
1240
+ }
1095
1241
 
1096
- `agsh` (agent shell) is a standalone CLI that connects to one agent-sdk
1097
- deployment and turns the agent's live tool surface into commands. Every tool
1098
- the deployment can execute (authored server tools and tools provided by the
1099
- agent's MCP connections) becomes a subcommand with a synopsis derived from its
1100
- input schema, a man-page style `--help`, and a place in an interactive shell.
1242
+ /**
1243
+ * Host-local with Cursor's local sandbox on. Same
1244
+ * `kind: "local"` family as `localRuntime` / `virtualRuntime`.
1245
+ * `cwd` / `workspaceDir` still configure the host tree.
1246
+ * The workspace omits `shell`: this object cannot honor
1247
+ * `sandboxOptions`. Host+sandbox turns keep the SDK's
1248
+ * `LocalShellExecutor` and do not inject the workspace.
1249
+ */
1250
+ function sandboxRuntime(
1251
+ opts?: Omit<AgentLocalOptions, "sandbox">
1252
+ ): LocalRuntime {
1253
+ return localRuntime({ ...opts, sandbox: true });
1254
+ }
1101
1255
 
1102
- It is a separate binary and a separate package from `agent-sdk`. The
1103
- `agent-sdk` CLI stays what it is today: the developer workflow tool for
1104
- authoring, validating, deploying, and debugging agent projects. `agsh` is the
1105
- operator's tool for working *inside* one deployed agent. The split also keeps
1106
- heavy presentation dependencies (markdown rendering, syntax highlighting, the
1107
- shell interpreter) out of `@cursor/july`, which ships to every agent project.
1256
+ /**
1257
+ * Local arm + a virtual workspace. Default is empty: `root()`
1258
+ * is `/`, no file/shell methods, no materialization, no
1259
+ * skills mount. Pass a `FileSystem` (or an async per-session
1260
+ * factory) to compose mounts. Authors pass a `FileSystem`,
1261
+ * not a `Workspace` the lift is not a knob.
1262
+ */
1263
+ function virtualRuntime(
1264
+ fs?: FileSystem | ((ctx: SessionWorkspaceContext) => Promise<FileSystem>)
1265
+ ): LocalRuntime {
1266
+ return { kind: "local", workspace: virtualWorkspace(fs) };
1267
+ }
1108
1268
 
1109
- ## The experience
1269
+ function cloudRuntime(cloud: AgentCloudOptions): CloudRuntime {
1270
+ return { kind: "cloud", cloud };
1271
+ }
1110
1272
 
1273
+ function grokbotRuntime(): GrokbotRuntime {
1274
+ return { kind: "grokbot" };
1275
+ }
1111
1276
  ```
1112
- $ agsh help # list of commands, man-page style
1113
- $ agsh read --help # man-page style: NAME, SYNOPSIS, DESCRIPTION, OPTIONS
1114
- $ agsh read /repo/README.md
1115
- $ agsh datadog_list_monitors --query "service:api"
1116
- $ agsh # bare: interactive shell on a TTY, script from stdin otherwise
1117
- ls /repo | grep -i readme
1118
- read /repo/config.json | jq .version
1277
+
1278
+ `cwd` / `workspaceDir` are arguments to `localRuntime` and
1279
+ `sandboxRuntime`, same as `repos` on `cloudRuntime`. They
1280
+ configure the host tree `localWorkspace(opts)` wraps. They
1281
+ are not a `Workspace` and they are not on `virtualRuntime`.
1282
+ Prefer `sandboxRuntime()` when the harness should run
1283
+ sandboxed. `localRuntime({ sandbox: true })` and sibling
1284
+ `local.sandbox` stay as equivalent deprecated sugar.
1285
+ The injected object on the Cursor SDK is
1286
+ `AgentOptions.local.workspace`. Cursor SDK's existing
1287
+ `WorkspaceRuntime` is the session factory, not `Workspace`.
1288
+ Host+sandbox turns do not inject: a Workspace that claimed
1289
+ `shell` would replace `LocalShellExecutor`.
1290
+
1291
+ Authoring:
1292
+
1293
+ ```ts
1294
+ export default defineAgent({
1295
+ runtime: localRuntime(),
1296
+ });
1297
+
1298
+ export default defineAgent({
1299
+ runtime: sandboxRuntime({ workspaceDir: "/repo" }),
1300
+ });
1301
+
1302
+ export default defineAgent({
1303
+ runtime: virtualRuntime(async (ctx) =>
1304
+ unionFs({
1305
+ "/repo": originRepoFs(bindingFor(ctx)),
1306
+ "/host": hostFilesFs(),
1307
+ "/agent/skills": skillsFs(),
1308
+ })
1309
+ ),
1310
+ });
1311
+
1312
+ export default defineAgent({
1313
+ runtime: cloudRuntime({
1314
+ repos: [{ url: "https://github.com/org/repo", startingRef: "main" }],
1315
+ }),
1316
+ });
1119
1317
  ```
1120
1318
 
1121
- Every invocation binds to the deployment's latest session by default, with
1122
- `--session` and `--continuation-token` overrides, and prints the session
1123
- identifier as a final stderr line.
1319
+ `localRuntime` / `sandboxRuntime` / `virtualRuntime` /
1320
+ `cloudRuntime` / `grokbotRuntime` are constructors over that
1321
+ union. They are not five implementations of one `fs()` +
1322
+ `shell()` pair. `sandboxRuntime` stays on the local arm.
1124
1323
 
1125
- ## Configuration
1324
+ ### Backward compatibility
1126
1325
 
1127
- `agsh` is a client only; it never boots an agent. Every invocation needs a
1128
- target deployment, given by flags or by environment variables. Flags always
1129
- win over the environment.
1326
+ Today's fields stay on `defineAgent` and on Cursor
1327
+ `AgentOptions`. Constructors sit beside them. Nothing that
1328
+ compiles today is broken by Phase 2.
1130
1329
 
1131
- Global command line options, accepted on every command and on the bare shell
1132
- launch:
1330
+ ```ts
1331
+ // Still valid. Deprecated. Same agent as localRuntime(opts).
1332
+ defineAgent({
1333
+ runtime: "local",
1334
+ local: { workspaceDir: "/repo", sandbox: true },
1335
+ });
1133
1336
 
1134
- | Option | Environment default | Meaning |
1135
- | --- | --- | --- |
1136
- | `--target <url \| name>` | `AGENT_SHELL_TARGET` | The deployment to talk to: a URL is a local deployment (`http://127.0.0.1:39400/executor`), a name a production one (`change-monitor-executor`). |
1137
- | `--team <team>` | `AGENT_SHELL_TEAM` | Team override for production resolution, when the login spans several. |
1138
- | `--bearer-token <token>` | `AGENT_SHELL_BEARER_TOKEN` | Explicit bearer auth for a deployment that is not behind the Cursor login. |
1139
- | `--session <id>` | | Bind to a specific session instead of the latest. |
1140
- | `--continuation-token <token>` | | Bind by continuation token instead of session id. |
1141
- | `--output <text\|json>` | | Result rendering: human-friendly views (default) or raw JSON. |
1142
- | `-h`, `--help` | | Per-command help. |
1143
-
1144
- One parameter carries the whole target selection, and the value's shape
1145
- encodes the mode: a URL (`http://` or `https://`) targets a local
1146
- deployment, anything else names a production one. Two options with a
1147
- precedence rule would invite exactly the confusion a target selector must
1148
- not have; with one parameter the only rule is that the flag beats the
1149
- environment. A URL is self-contained down to the agent because one local
1150
- agent-sdk serve process hosts every agent of the project (change-monitor's
1151
- dev stack mounts `/executor` and `/planner` from a single port); a
1152
- production deployment is a single agent, so its name is the complete
1153
- address (`--team` narrows resolution when the login spans several).
1154
- Authentication defaults to the stored Cursor login (the same engine-access
1155
- credential agent-sdk uses); `--bearer-token` is the escape hatch for direct
1156
- deployments. Session flags are per invocation and have no environment
1157
- default: a session is state, not configuration. Color output follows the
1158
- `NO_COLOR` convention and TTY detection; there is no agsh-specific color
1159
- setting. No configuration file: one environment variable pins a working
1160
- target for a terminal session
1161
- (`AGENT_SHELL_TARGET=change-monitor-executor`, or a URL for a local stack),
1162
- which is the whole persistent-configuration need.
1163
-
1164
- With no target from flags or environment, every command fails with a message
1165
- naming both ways to provide one.
1166
-
1167
- ## Architecture
1168
-
1169
- ### A new package
1170
-
1171
- A new workspace package (working name `packages/agsh`, bin `agsh`) that
1172
- depends on `@cursor/july` for target resolution, stored Cursor login, and the
1173
- HTTP client plumbing. It owns the presentation stack: `marked` for terminal
1174
- markdown (moved out of `@cursor/july`), with syntax highlighting (`shiki`)
1175
- arriving in the phase that renders code; the shell interpreter is
1176
- purpose-built (see Rationale).
1177
- No new abstraction seam between the two packages; `agsh` imports what it
1178
- needs until a second consumer justifies extracting a thin client.
1179
-
1180
- ### The tool catalog
1181
-
1182
- At startup `agsh` fetches one live catalog of everything invocable on the
1183
- deployment. This is the piece the current `/v1/info` cannot provide: `/v1/info`
1184
- projects the authored manifest, and connection tools only exist at runtime,
1185
- resolved per session under the connection's auth. A new endpoint provides the
1186
- live view (see Backend changes).
1187
-
1188
- Catalog entries carry exactly one identifier each: the tool name exactly as
1189
- the agent sees it. Authored server tools keep their authored name (`read`).
1190
- Connection tools appear under their model-facing advertised name (the
1191
- sanitized passthrough name from `advertise-tools.ts`, e.g.
1192
- `datadog_list_monitors`). The CLI never invents a different naming format:
1193
- a tool name copied from a session transcript is a valid `agsh` command, and
1194
- vice versa. Where a tool came from the upstream connector name when the
1195
- tool declares one, the connection name otherwise — is a field on the
1196
- catalog entry, not part of the identifier.
1197
-
1198
- ### Two command tiers
1199
-
1200
- Each catalog entry becomes a command, through one of two shapes:
1201
-
1202
- **Curated commands for builtin tools.** The well-known tool names (`ls`,
1203
- `read`, `grep`, `glob`, `diff`, ...) get hand-designed, POSIX-flavored
1204
- command shapes, hardcoded in `agsh` next to their titles. These tools are
1205
- what an operator types all day; their shapes should feel like the unix
1206
- commands they mirror, not like generated bindings. A curated shape decides
1207
- which schema fields are positional operands and which are flags, and every
1208
- input has exactly one spelling: an operand is only an operand, never also a
1209
- flag.
1210
-
1211
- ```
1212
- $ agsh read /repo/package.json --limit 2
1213
- {
1214
- "name": "change-monitor",
1215
- ses_a99d1b69c329eb75a2ec8603
1216
-
1217
- $ agsh grep -i -A 2 toolEffect /repo/src
1218
- src/tool-policy.ts:12:export type ToolEffect = "read" | "write";
1219
- ...
1220
- ses_a99d1b69c329eb75a2ec8603
1221
-
1222
- $ agsh ls /repo --ignore-globs '*.test.ts' --ignore-globs 'node_modules/**'
1223
- ```
1224
-
1225
- `read` takes its path as an operand mapped to the schema's `path` field, with
1226
- `--offset` and `--limit` as integer flags. `grep` follows POSIX grep:
1227
- `grep [options] <pattern> [path]`, with the rg-style options (`-i`, `-A`,
1228
- `-B`, `-C`, `--output-mode`, `--head-limit`) mapping onto the schema fields
1229
- of the same names (kebab-cased). `ls` shows array input: an array field's flag repeats once
1230
- per element. A curated shape binds to the deployment's live schema at
1231
- startup; when a deployment's tool lacks the expected field, the command
1232
- degrades to the generic shape below rather than guessing.
1233
-
1234
- A curated shape may also reformat the tool's text result toward the unix
1235
- command's own output conventions: the VFS ls tool returns the model-facing
1236
- tree (` - name/` rows under a header), and `agsh ls` prints it as standard
1237
- ls does, one name per line with the trailing slash kept on directories. The
1238
- tool's result string itself stays what the model sees; when a result does
1239
- not match the expected shape it prints verbatim.
1240
-
1241
- **Generated commands for MCP tools.** Connection tools are dynamically
1242
- discovered, so no special treatment is possible; they get a uniform
1243
- schema-derived mapping:
1244
-
1245
- - Every schema property is accepted as one flag, spelled as the
1246
- kebab-cased property name (`org_slug` `--org-slug`) the unix
1247
- convention; kebab collisions gain a numeric suffix. Properties already
1248
- shaped like flags (grep's `-i`) stay literal. No positionals, no other
1249
- aliases.
1250
- - Object-typed properties flatten recursively into one flag per leaf,
1251
- dash-joined (`--telemetry-context` for `telemetry.context`), so every
1252
- option reads as a plain value; a free-form object with no declared
1253
- properties stays one JSON-valued flag. A leaf is required only when its
1254
- whole ancestor chain is.
1255
- - Values are coerced by schema type: booleans are valueless flags, numbers
1256
- and integers are parsed, arrays accept the flag repeated once per element,
1257
- enums are validated before the call.
1258
-
1259
- ```
1260
- $ agsh datadog_list_monitors --query "service:api" --limit 10
1261
- ```
1262
-
1263
- In both tiers `-h`/`--help` and the global target and session flags are
1264
- reserved and injected, a flag that names no schema property fails before any
1265
- request (listing the tool's actual properties), and the bound session prints
1266
- as a final stderr line.
1267
-
1268
- ### Result rendering
1269
-
1270
- Raw JSON on a terminal is not an experience for people, so `--output=text`
1271
- (the default) renders structured results through a small set of views,
1272
- selected automatically by the shape of the value each call actually returned;
1273
- tool metadata plays no part, since most tools advertise no output schema, and
1274
- many return structured data as JSON text. A string result that parses as a
1275
- JSON object or array counts as structured. An array of objects renders as a
1276
- table (columns are the union of keys, missing cells stay blank, the table
1277
- clamps to the terminal width); a single object renders as a property view
1278
- (aligned keys, scalar lists as bullets, nested structures indented); an
1279
- object that is nothing but an error wrapper renders as an `Error:` line;
1280
- plain text prints verbatim. `--output=json` renders the structured value as
1281
- raw JSON. The rendering never depends on the TTY: piped and interactive
1282
- output carry the same content, only color follows TTY detection.
1283
-
1284
- ### Help rendering
1285
-
1286
- `--help` on a tool renders a man-page layout: NAME (the tool name, with the
1287
- tool's `title` beside it when the catalog carries one; titles are curated
1288
- data, never derived from the description), SYNOPSIS (operands from the
1289
- curated shape; options never enumerate they summarize as `[options...]`,
1290
- man-page style, so the line stays bounded), DESCRIPTION (the tool
1291
- description rendered as terminal markdown), OPERANDS (positional arguments,
1292
- curated commands only), and OPTIONS. Descriptions of operands and options
1293
- come from the schema's property descriptions. Effect and approval metadata
1294
- render as notes when declared. Everything except the curated shape derives
1295
- from `GET /v1/tools/:name`; nothing else is hand-written per tool.
1296
-
1297
- ```
1298
- $ agsh read --help
1299
- NAME
1300
- read - Read a file
1301
-
1302
- SYNOPSIS
1303
- read [options...] <path>
1304
-
1305
- DESCRIPTION
1306
- Reads a file from the local filesystem. This tool can also read image
1307
- files when called with the appropriate path. Formats supported:
1308
- jpeg/jpg, png, gif, webp.
1309
-
1310
- OPERANDS
1311
- <path>
1312
- The absolute path of the file to read.
1313
-
1314
- OPTIONS
1315
- --offset <integer>
1316
- The line number to start reading from. Positive values are 1-indexed
1317
- from the start of the file. Negative values count backwards from the
1318
- end. Only provide if the file is too large to read at once.
1319
-
1320
- --limit <integer>
1321
- The number of lines to read. Only provide if the file is too large
1322
- to read at once.
1323
-
1324
- NOTES
1325
- Effect: read (performs no writes).
1326
- ```
1327
-
1328
- `agsh help` lists the available command names grouped by source, authored
1329
- tools first, then one group per upstream connector (its name is the group
1330
- header one aggregating connection can host tools from several connectors,
1331
- and the connector name is what an operator recognizes). Each row is the
1332
- name, with the title beside it when the tool declares one; everything else
1333
- lives behind the command's `--help`:
1334
-
1335
- The agent's description renders as a DESCRIPTION section when the deployment
1336
- declares one (`/v1/info` carries both name and description).
1337
-
1338
- ```
1339
- $ agsh help
1340
- NAME
1341
- change-monitor-executor
1342
-
1343
- DESCRIPTION
1344
- Executes monitoring plans against changed code.
1345
-
1346
- COMMANDS
1347
- diff Show workspace changes
1348
- glob Find files by pattern
1349
- grep Search file contents
1350
- ls List a directory
1351
- read Read a file
1352
- report_change_issue
1353
- report_change_succeeded
1354
-
1355
- DATADOG
1356
- datadog_list_monitors List monitors
1357
- ...
1358
-
1359
- Run any command with --help for its synopsis and options.
1360
- ```
1361
-
1362
- ### Shell mode
1363
-
1364
- Invoked bare, `agsh` starts a shell. On a TTY this is a REPL; on a pipe it
1365
- reads a script from stdin, so `echo 'ls /' | agsh` and here-docs work.
1366
-
1367
- The interpreter is purpose-built and minimal: tokenizing (quotes, escapes),
1368
- pipelines, and `;` / `&&` / `||`. The command namespace is exactly the
1369
- deployment's tool catalog plus a small curated set of local pipe filters
1370
- (`head`, `tail`, `wc`, stdin-filtering `grep`), so a tool name can never be
1371
- shadowed. There is no local filesystem, no variables, no control flow: agsh
1372
- has nothing local to operate on, and every command is a single traced
1373
- `POST /v1/tools/:name` call.
1374
-
1375
- The shell binds one session identity at launch (latest by default) and keeps
1376
- it for the whole run, so a sequence of tool calls observes one consistent
1377
- session context.
1378
-
1379
- ## Backend changes on the agent-sdk runtime
1380
-
1381
- Two read endpoints, mirroring the invocation path:
1382
-
1383
- **`GET /v1/tools`: the live tool listing.** Returns the session's tool
1384
- namespace exactly as a turn would assemble it: authored server tools plus the
1385
- advertised passthrough tools synthesized from connections, under their
1386
- model-facing names. Entries are light (name, source, and `title` when one is
1387
- known); everything else lives behind the detail endpoint. Titles have two
1388
- sources and no new authoring surface: connection tools inherit the upstream
1389
- server's MCP title, which the host already propagates length-capped off
1390
- listings; tools that do not come from MCP get theirs from a hardcoded
1391
- name-to-title table in the runtime's endpoint implementation, covering the
1392
- well-known tool names. A tool in neither place has no title. Accepts the same
1393
- optional session binding as invocation (`session` or `continuationToken`)
1394
- because advertised inventories can be tenant-scoped and resolved per session.
1395
- Implementation reuses the existing plumbing: the discovered manifest for
1396
- authored tools and the advertise-tools synthesis (`McpHost.listTools`, or the
1397
- `oneOff` path when per-session auth substitution applies) for connection
1398
- tools. This is not a duplicate of `/v1/info`: the info document stays the
1399
- static authored manifest; the listing is the runtime view that only the
1400
- running deployment can answer.
1401
-
1402
- **`GET /v1/tools/:name`: one tool's full description.** Description, input
1403
- schema, output schema when declared, effect when declared, approval
1404
- requirement, and source connection. Same path as invocation
1405
- (`POST /v1/tools/:name`), different method: GET describes what POST executes,
1406
- for the same identifier.
1407
-
1408
- Invocation needs no new naming scheme. Advertised connection tools are
1409
- synthesized as ordinary server tools in the session's namespace, so
1410
- `POST /v1/tools/:name` addresses them by their model-facing name like any
1411
- authored tool, with the same session binding, policy checks, and per-call
1412
- tracing. (The direct-call path did need the synthesis step added: it now
1413
- resolves the advertised listing for the call's session identity when the
1414
- authored lookup misses.)
1415
-
1416
- Phase 1 ships the minimal runtime surface agsh calls: the `effect`
1417
- projection in `/v1/info` (rendered in per-tool help), the scratch-workspace
1418
- fallback on direct calls, and `continuationToken` binding on
1419
- `POST /v1/tools/:toolName`. The detail endpoint in phase 2 also closes the
1420
- output-schema gap; `/v1/info` stays as it is.
1421
-
1422
- ## Local development loop
1423
-
1424
- `factory/change-monitor` is the test bed. Its `pnpm start` already serves the
1425
- planner and executor locally through the agent-sdk dev runtime
1426
- (`agent-sdk serve --dir . --dev`). The loop:
1427
-
1428
- 1. `cd factory/change-monitor && pnpm start` (local stack, both agents).
1429
- 2. `agsh --target http://127.0.0.1:<port>/<agent>` against it, via a dev shim
1430
- analogous to `agent-sdk-dev` so the CLI runs from the worktree.
1431
- 3. Iterate end to end: VFS verbs (`ls`, `read`, `grep`, `glob`, `diff`) for the
1432
- authored-tool path, and the planner's tenant connectors for the
1433
- connection-tool path once `GET /v1/tools` exists.
1434
-
1435
- ## Removing the inspector surface from agent-sdk
1436
-
1437
- The inspector CLI is still on development branches, so nothing migrates: the
1438
- CLI-side code is removed from `agent-sdk` and `agsh` is built in its place.
1439
-
1440
- - The verb commands (`ls`, `read`, `grep`, `glob`, `diff`) become the
1441
- curated tier: their hand-designed shapes, schema-binding logic (including
1442
- the candidate-field fallback), and session binding carry over. The
1443
- schema-to-argv flag mapping seeds the generated tier for MCP tools.
1444
- - The `tools` and `skills` commands disappear entirely. `agsh help` and
1445
- per-tool `--help` are the discovery surface.
1446
- - `marked` and `shiki` leave `@cursor/july`; agsh's help rendering takes
1447
- `marked`, and `shiki` returns when agsh ships syntax highlighting.
1448
- `agent-sdk` keeps its developer workflow commands unchanged.
1449
-
1450
- ## Plan
1451
-
1452
- 1. **Package and core invocation.** Create the package, port target
1453
- resolution, the schema-to-argv mapping, and help rendering from the
1454
- inspector code. Authored tools only, against the existing endpoints.
1455
- Verified end to end on the local change-monitor stack.
1456
- 2. **Live catalog.** Add `GET /v1/tools` and `GET /v1/tools/:name` to
1457
- the agent-sdk runtime, with the hardcoded title table for non-MCP tools, and verify
1458
- direct invocation resolves advertised connection tools by their
1459
- model-facing names. Connection tools appear as commands. Verified against
1460
- the planner's connectors.
1461
- 3. **Shell mode.** The purpose-built mini-shell: REPL on TTY, script on
1462
- stdin, tools as the command namespace, one session per shell run.
1463
- 4. **Cleanup.** Remove the inspector CLI surface and presentation
1464
- dependencies from `@cursor/july`.
1465
-
1466
- ## Rationale and rejected alternatives
1467
-
1468
- **Why not extend `agent-sdk`.** The audiences differ: `agent-sdk` is for the
1469
- person building and deploying an agent; this tool is for the person operating
1470
- inside one. Bundling also forces every agent project to carry markdown
1471
- rendering, syntax highlighting, and a bash interpreter it never uses.
1472
-
1473
- **Name.** `agsh` reads as "agent shell", is four characters, collides with
1474
- nothing common, and works as a shell prompt name. Considered: `august`
1475
- (pairs with `july` but says nothing about purpose), `toolsh` (awkward to
1476
- pronounce), `cursor-shell` (too broad; this is scoped to one agent).
1477
-
1478
- **Why a REST catalog instead of the MCP endpoint.** The deployment already
1479
- speaks MCP at `/v1/mcp/tools`, including a per-connection bridge, but the
1480
- bridge is bound to an active turn and speaks JSON-RPC. The CLI wants a plain
1481
- authenticated GET with session binding that returns the assembled tool
1482
- namespace under the names the model sees. Wrapping that in MCP framing buys
1483
- nothing for a first-party client.
1484
-
1485
- **Why a purpose-built interpreter instead of just-bash.** just-bash was the
1486
- original plan (a full bash emulation with a custom-command extension point),
1487
- and a prototype disproved it: custom commands replace its coreutils but can
1488
- never shadow its shell builtins, and `read`, `test`, `type`, and `help` are
1489
- builtins so the flagship `read` tool is unreachable, and the precedence is
1490
- not ours to control (vercel-labs owns the package). No other embeddable JS
1491
- shell interpreter has a workable custom-command story (mvdan-sh's JS build
1492
- does not expose one; bash-parser is a parser only). agsh also needs almost
1493
- none of bash: no local filesystem, no variables, no control flow — just
1494
- tokenizing, pipelines, and a command namespace it fully owns. A TypeScript
1495
- REPL with tools as async functions (the shape of change-monitor's `script`
1496
- tool) was considered and kept as a possible later addition; it trades away
1497
- the unix muscle memory the curated commands exist for.
1337
+ // Preferred.
1338
+ defineAgent({
1339
+ runtime: sandboxRuntime({ workspaceDir: "/repo" }),
1340
+ });
1341
+
1342
+ // Still valid. Deprecated. Same agent as cloudRuntime(opts).
1343
+ defineAgent({
1344
+ runtime: "cloud",
1345
+ cloud: { repos: [{ url: "https://github.com/org/repo", startingRef: "main" }] },
1346
+ });
1347
+
1348
+ // Preferred.
1349
+ defineAgent({
1350
+ runtime: cloudRuntime({
1351
+ repos: [{ url: "https://github.com/org/repo", startingRef: "main" }],
1352
+ }),
1353
+ });
1354
+ ```
1355
+
1356
+ | Today's field | Still does | Preferred |
1357
+ |---|---|---|
1358
+ | omit `runtime` / `runtime: "local"` | `localRuntime(agent.local)` | `runtime: localRuntime(opts)` |
1359
+ | `runtime: "cloud"` | `cloudRuntime(agent.cloud)` | `runtime: cloudRuntime(opts)` |
1360
+ | `runtime: "grokbot"` | `grokbotRuntime()` | `runtime: grokbotRuntime()` |
1361
+ | `local: { cwd, workspaceDir, sandbox }` | Host options when the string / omitted runtime is local | `localRuntime(opts)` or `sandboxRuntime(opts)`. Same `AgentLocalOptions`. |
1362
+ | `cloud: { repos, }` | Payload for `runtime: "cloud"` | `cloudRuntime(opts)` |
1363
+ | `send({ cloud })` | This session becomes cloud; options merge over agent `cloud` | `send({ runtime: cloudRuntime(opts) })`. No merge. |
1364
+ | `send({ workspaceDir })` | Harness cwd for this local session | `send({ runtime: localRuntime({ workspaceDir }) })` |
1365
+
1366
+ Runtime payloads live on constructors: `AgentLocalOptions`
1367
+ on `localRuntime`, `AgentCloudOptions` on `cloudRuntime`.
1368
+ These stay on `defineAgent` / `send` / `Agent.create`, not
1369
+ on `Runtime`: `model`, `tools`, `builtinTools`, `hosting`,
1370
+ `architecture`, `workspaceFiles`.
1371
+
1372
+ `virtualRuntime` does not take `AgentLocalOptions`. There is
1373
+ no host cwd to point. `local.workspaceDir` / `sandbox` +
1374
+ `virtualRuntime` is still a discovery error.
1375
+
1376
+ Cursor SDK stays on `AgentOptions.local` / `.cloud` for
1377
+ Phases 1–4. Presence of `cloud` still dispatches. Phase 1
1378
+ **adds** `local.workspace`. july `localRuntime(opts)` writes
1379
+ cwd / sandbox / workspaceDir into that bag. Constructors on
1380
+ `Agent.create` are a later SDK deprecation, after july has
1381
+ shipped the same types. Do not leave “`cloud` means cloud”
1382
+ as the forever SDK contract — just do not do that cut in
1383
+ Phase 1.
1384
+
1385
+ Deprecation is documentation and JSDoc (`@deprecated` with
1386
+ the constructor equivalent). Do not delete the string or
1387
+ the sibling fields in Phases 1–4. Removal is a later
1388
+ window after Phase 4 has taught constructors. Subagents
1389
+ keep today's behavior, stored internally as `Runtime`
1390
+ (`G12`).
1391
+
1392
+ If a constructor and a sibling disagree (`localRuntime(a)`
1393
+ plus `local: b`; `cloudRuntime(a)` plus `cloud: b`;
1394
+ `virtualRuntime` plus `local.workspaceDir`), fail closed.
1395
+ If only the deprecated form is present, accept it.
1396
+
1397
+ `kind` is internal dispatch. Authors never write it; they
1398
+ call a constructor. `AgentRunner` stays on
1399
+ `StartServerInternalOptions` for replay and eval fakes. It
1400
+ is not a `Runtime` arm and not a `defineAgent` field.
1401
+
1402
+ `virtualRuntime` is an empty local+virtual workspace until the
1403
+ author unions mounts in. `root()` is always `/`. Nothing is
1404
+ materialized onto a host scratch dir; every path is served
1405
+ lazily from the `FileSystem`. The framework does not inject
1406
+ skills, `/repo`, or a fake cwd. `skillsFs()` is a july
1407
+ helper that returns a `FileSystem` the application mounts if
1408
+ it wants skills on the workspace.
1409
+
1410
+ Change-monitor's `search` (regex = `grep`, tags on
1411
+ `/issues`, reserved keyword / semantic) stays a consumer
1412
+ authored tool if they still want it. The framework does not
1413
+ grow a `search` method. `grep` and optional `semSearch`
1414
+ cover retrieval.
1415
+
1416
+ `virtualWorkspace` is an internal lift: take the `FileSystem`,
1417
+ set `root()` to `/`, derive `brief()` from the mount table
1418
+ when there is one, omit `shell` unless a mount is
1419
+ shell-backed. Authors do not compose that lift, and they
1420
+ cannot pass `localWorkspace()` in.
1421
+
1422
+ **Session factory.** `defineAgent` has no PR/SHA. A function
1423
+ argument runs **once per session**, not at discovery and not
1424
+ per turn. It is always async (`Promise<FileSystem>`). The
1425
+ returned `FileSystem` is reused for every turn and disposed
1426
+ with the session. Per-wake data (tenant plugin skills) is a
1427
+ lazy layer *inside* that filesystem, not a new Workspace. A
1428
+ stable tree (`memoryFs({ "/a.txt": "hi" })`) is passed
1429
+ directly and needs no factory.
1430
+
1431
+ ```ts
1432
+ interface SessionWorkspaceContext {
1433
+ sessionId: string;
1434
+ agentName?: string;
1435
+ /** Channel continuation key when the session is addressable. */
1436
+ continuationKey?: string;
1437
+ /** Host services already on tool context. */
1438
+ host?: { files: HostFilesApi; reminders?: ReminderHostApi };
1439
+ }
1440
+ ```
1441
+
1442
+ `sessionId` is the session when one exists. Unscoped
1443
+ `callTool` / `agent-sdk call` has no session row; the
1444
+ framework must not invent a shared default id that would
1445
+ collapse distinct callers onto one factory-backed store.
1446
+
1447
+ ### One workspace, one namespace
1448
+
1449
+ File verbs and `shell` live on **`Workspace`**, not on `Runtime`.
1450
+ The local arm holds that object. Cloud and grokbot have no
1451
+ `workspace` field. Putting `read()` on `Runtime` re-bundles
1452
+ the string this document takes apart.
1453
+
1454
+ `FileSystem` is the shared file-verb type (`@cursor/sdk`).
1455
+ `Workspace` adds `shell` / `root` / `brief`. July authors pass
1456
+ a `FileSystem`; helpers (`memoryFs`, `unionFs`, `skillsFs`)
1457
+ return one. That is not an attachable `Shell`: `shell` stays
1458
+ on `Workspace` so it cannot be pointed at a different tree
1459
+ than `read`. Change-monitor avoids the split today by having
1460
+ no shell. computer-use.md avoids it by deriving every
1461
+ `/workspace` verb from one `RemoteMachine.exec`.
1462
+
1463
+ `unionFs(mounts)` composes FileSystems under absolute
1464
+ prefixes. Paths resolve by **longest prefix**. Method
1465
+ presence is the **union**: `write` is offered if any mount
1466
+ implements it. `shell` is not a FileSystem method and is
1467
+ not synthesized.
1468
+
1469
+ Unifying the type does **not** enforce the coupling. Two methods
1470
+ on one object can still have two backends. The rule is semantic:
1471
+
1472
+ **All Workspace methods share one path namespace, rooted at
1473
+ `root()`.** For `virtualRuntime`, that root is `/`.
1474
+ `ls({ path: "/repo" })` and `shell({ command: "ls /repo" })`
1475
+ see the same entries. Same tree, not the same formatter: the
1476
+ `ls` tool's ignore/dotfile rules and `shell({ command: "ls -la" })`
1477
+ may still render differently, as they do on a real machine.
1478
+
1479
+ The framework cannot typecheck that. The public constructors keep
1480
+ the invariant by not taking a raw `Workspace`:
1481
+
1482
+ ```ts
1483
+ // Host. File verbs and shell wrap the same cwd.
1484
+ localRuntime(opts) localWorkspace(opts)
1485
+
1486
+ // Host + Cursor sandbox. Workspace omits shell.
1487
+ sandboxRuntime(opts) localWorkspace({ ...opts, sandbox: true })
1488
+
1489
+ // Empty virtual. `/`, no methods, no mounts, no skills.
1490
+ virtualRuntime()
1491
+
1492
+ // Composed virtual. Application unions what it needs.
1493
+ virtualRuntime(unionFs({
1494
+ "/repo": originRepoFs(binding),
1495
+ "/host": hostFilesFs(),
1496
+ "/agent/skills": skillsFs(),
1497
+ "/workspace": cloudFs(remoteMachine),
1498
+ }))
1499
+
1500
+ // Tests.
1501
+ virtualRuntime(memoryFs({ "/a.txt": "hi" }))
1502
+ ```
1503
+
1504
+ A shell-primary mount (`cloudFs`) is a `FileSystem`
1505
+ implementation, not a second runtime argument. You union it
1506
+ in. `shell` appears on the lifted Workspace only when that
1507
+ mount (or the union) is shell-backed — same tree as `ls` /
1508
+ `read` under `/workspace`. A TypeScript isolate (tools.md §8)
1509
+ is an authored `script` tool, not a `{ script: true }` flag
1510
+ on `virtualRuntime`.
1511
+
1512
+ Do not put `shell` on a separate type the caller attaches later.
1513
+ Do not take `Workspace` as a public argument to `virtualRuntime`:
1514
+ that is how `virtualRuntime(localWorkspace())` happens.
1515
+
1516
+ ### Capability is method presence
1517
+
1518
+ Every Workspace method above except `root` is optional.
1519
+ The framework exposes the matching tool iff the method is
1520
+ implemented. A missing method is a missing tool. This is
1521
+ change-monitor's optional-write rule ("optionality is earned")
1522
+ applied to the whole workspace.
1523
+
1524
+ | Method present | Model-facing tool | Method absent |
1525
+ |---|---|---|
1526
+ | `read` | `Read` | not offered |
1527
+ | `ls` | `LS` | not offered |
1528
+ | `glob` | `Glob` | not offered |
1529
+ | `grep` | `Grep` | not offered |
1530
+ | `write` | `Write` | not offered |
1531
+ | `edit` | `StrReplace` | not offered |
1532
+ | `delete` | `Delete` | not offered |
1533
+ | `semSearch` | `SemanticSearch` | not offered |
1534
+ | `readLints` | `ReadLints` | not offered |
1535
+ | `shell` | `Shell` | not offered |
1536
+
1537
+ Those are the latest/default harness names. Some prompt
1538
+ versions rename them (`read_file`, `search_replace`,
1539
+ `list_dir`, `codebase_search`, `run_terminal_cmd`). The
1540
+ binding does not invent a fourth name.
1541
+
1542
+ `@cursor/sdk` `AgentOptions.tools` and stream `tool_call`
1543
+ events stay camelCase (`read`, `edit`, `semSearch`,
1544
+ `readLints`, `shell`, …). There is no public
1545
+ `tools: ["write"]`. `Write` and `StrReplace` both travel as
1546
+ proto `edit_tool_call`.
1547
+
1548
+ `tools: ["edit"]` means the **envelope**, not
1549
+ `workspace.edit`. It is satisfied if `write` **or** `edit`
1550
+ is present. A write-only workspace with `tools: ["edit"]`
1551
+ is valid and offers `Write` only. `tools: ["edit"]` on a
1552
+ workspace with neither method is a discovery error. Method
1553
+ presence still decides which of `Write` / `StrReplace` are
1554
+ registered. A harness that already offers `MultiStrReplace`
1555
+ or `ApplyPatch` for that model keeps doing so when `edit`
1556
+ is present, and omits them when it is not.
1557
+
1558
+ Cursor SDK binding: derive `AgentOptions.tools` from the
1559
+ presence set (`write` or `edit` contributes `edit` on the
1560
+ allowlist), then re-point those natives at the workspace.
1561
+ The model calls harness `Read`, not MCP `read` on
1562
+ `custom-user-tools`.
1563
+
1564
+ **Presence means a function, not a stub.**
1565
+ `read() { throw new Error("unsupported") }` still enables `read`.
1566
+ Omit the method.
1567
+
1568
+ **This gates the file/shell family only.**
1569
+ Do not grow Workspace to enumerate every harness builtin.
1570
+ `updateTodos` and `webSearch` do not share `root()`. Putting
1571
+ them on Workspace breaks the namespace rule and still does not
1572
+ make them work on `cloudRuntime` (that arm has no workspace
1573
+ field). Availability is not injectability.
1574
+
1575
+ **`tools` may only narrow, fail-closed.**
1576
+
1577
+ ```
1578
+ workspaceCaps = methodsPresent(workspace) // { read, ls, grep, }
1579
+ fileRequested = agent.tools?.filter(isFileOrShell)
1580
+ harnessRequested = agent.tools?.filter(isHarnessBuiltin)
1581
+
1582
+ fileExposed =
1583
+ fileRequested === undefined ? workspaceCaps : fileRequested
1584
+
1585
+ harnessExposed =
1586
+ agent.tools === undefined
1587
+ ? (virtualRuntime ? : runtime.defaultHarnessTools)
1588
+ : harnessRequested runtime.harnessTools
1589
+ ```
1590
+
1591
+ If `agent.tools` names `grep` and `workspace.grep` is missing,
1592
+ refuse at discovery same posture as today's "restriction that
1593
+ silently does not apply." `tools: ["edit"]` is the exception:
1594
+ it names the envelope, so refuse only when both `write` and
1595
+ `edit` are missing. If it names `webSearch` and the loop
1596
+ does not have that builtin (grokbot), refuse the same way.
1597
+ If `agent.tools` is unset, the workspace *is* the file/shell
1598
+ allowlist. `"mcp"` is still granted when server tools or
1599
+ connections exist; it is transport, not a file verb and not a
1600
+ harness builtin.
1601
+
1602
+ Host `localRuntime()` implements every file/shell method and
1603
+ keeps today's default harness set, so the toolset does not
1604
+ shrink. `virtualRuntime` with `read` + `ls` + `grep` + `glob`
1605
+ and no `shell` is change-monitor without a redundant
1606
+ `tools: ["read", …]` line. Harness builtins stay off that
1607
+ default; opt in:
1608
+
1609
+ ```ts
1610
+ defineAgent({
1611
+ runtime: virtualRuntime(fs),
1612
+ tools: ["read", "ls", "grep", "glob", "webSearch", "updateTodos"],
1613
+ });
1614
+ ```
1615
+
1616
+ `tools` is one allowlist. Listing only `["webSearch"]` turns
1617
+ file/shell natives off, even if the workspace implements them.
1618
+
1619
+ `tools: []` is an empty allowlist, not "legacy suppress
1620
+ natives." `[] workspaceCaps = ∅`. Phase 2 must omit
1621
+ `tools` on `virtualRuntime` agents so workspace methods
1622
+ become the file/shell allowlist. List harness builtins
1623
+ only to opt those in. Change-monitor drops today's
1624
+ `tools: []` when it cuts over (Phase 3).
1625
+
1626
+ ### Three tool families
1627
+
1628
+ | Family | Examples | Owner | How the model gets the native tool |
1629
+ |---|---|---|---|
1630
+ | **Workspace** | `read`, `ls`, `grep`, `glob`, `write`, `edit`, `delete`, `semSearch`, `readLints`, `shell` | Injected `Workspace` (`FileSystem` + `shell`) | Method presence → native tool. Args are that tool's schema. Omit the method if this workspace cannot honor it. |
1631
+ | **Harness** | `webSearch`, `webFetch`, `updateTodos`, `readTodos`, `askQuestion`, `await`, `generateImage` | The **Runtime arm's** Cursor harness | `defineAgent({ tools })`. Same builtin name. Do not put these on Workspace. |
1632
+ | **Transport / spawn** | `mcp`, `task` | Runtime arm | `mcp` when server tools or connections exist. `task` is subagents (`G12`). |
1633
+
1634
+ **Framework server tools.** Agent SDK `builtinTools`
1635
+ (`reminders`, …) are `execution: "server"`. Local
1636
+ in-process; cloud via HTTP MCP; grokbot blocked. They are
1637
+ not Cursor `ToolName`s and not Workspace methods.
1638
+
1639
+ No `Harness` interface on `Runtime`. `webSearch` is the
1640
+ backend; there is nothing to inject. `updateTodos` is
1641
+ conversation state the local harness already keeps. A third
1642
+ object would re-bundle the string (Runtime arm + workspace +
1643
+ "the rest") and still could not be honored on cloud.
1644
+
1645
+ If a later consumer needs a custom todo store or a stub
1646
+ `webSearch` in evals, add executor overrides on
1647
+ `AgentOptions.local` the same way Workspace landed a rebind
1648
+ seam, not a constructor argument.
1649
+
1650
+ ### What each constructor actually offers
1651
+
1652
+ `virtual` and `local` share the **local loop**. Harness
1653
+ builtins are the same implementations: backend `webSearch`,
1654
+ in-process todos. The workspace is what changes. Cloud is a
1655
+ different harness that already includes those builtins and
1656
+ cannot select them.
1657
+
1658
+ | | `localRuntime()` | `virtualRuntime(fs)` | `cloudRuntime(opts)` | `grokbotRuntime()` |
1659
+ |---|---|---|---|---|
1660
+ | File/shell natives | Host workspace; `tools` narrows | Workspace methods only | VM workspace; cannot restrict | Box workspace; cannot restrict |
1661
+ | `webSearch` / `webFetch` | Default on; `tools` narrows | Off until listed in `tools` | On (VM default); `tools` refused | Fail closed if listed |
1662
+ | `updateTodos` / `readTodos` | Same | Off until listed | On (VM default); `tools` refused | Fail closed if listed |
1663
+ | `task` | Default on | Off until listed; children inherit parent workspace (`G12`) | On; cannot restrict | Blocked |
1664
+ | `mcp` | Granted for server tools / connections | Same (local loop) | Bridged HTTP MCP when configured | Blocked |
1665
+ | `edit` / `delete` / `semSearch` / `readLints` | Default on (`localWorkspace`) | Off unless the `FileSystem` implements the method | On (VM tree) | Box default |
1666
+
1667
+ `sandboxRuntime()` is the same local loop as `localRuntime()`,
1668
+ with Cursor sandbox on. The workspace omits `shell`. Host+sandbox
1669
+ turns keep `LocalShellExecutor` and do not inject the workspace.
1670
+
1671
+ One agent file cannot express the same *selected* toolset on
1672
+ all three constructors until the cloud API honors `tools`
1673
+ (non-goal, §10). `tools: ["webSearch", "updateTodos"]` on
1674
+ `cloudRuntime` is still a discovery error: we cannot prove the
1675
+ VM will offer only those, and we must not run unrestricted
1676
+ and pretend we selected.
1677
+
1678
+ What *is* true without that API:
1679
+
1680
+ - Local (including today's `tools: []` agents): list the
1681
+ harness names. That already works. `virtualRuntime` does
1682
+ not unlock `updateTodos` / `webSearch`; the allowlist does.
1683
+ - Cloud: those names are already on the VM. Authors who need
1684
+ them on a cloud agent omit `tools` and accept the rest of
1685
+ the VM workspace.
1686
+ - Do not special-case "harness-only `tools` + cloud" as
1687
+ silently honored. That is the hybrid footgun with extra
1688
+ steps.
1689
+
1690
+ **Do not synthesize omitted verbs.**
1691
+ Change-monitor's current mounts require `grep` on every *mount* because
1692
+ it is derivable from `glob` + `read`. That is a mount-quality
1693
+ rule, not a Workspace-to-tool rule. If the workspace omits `grep`,
1694
+ the model does not get a generated grep that secretly scans via
1695
+ `read`. An implementation that has `glob` and `read` should
1696
+ usually implement `grep` (delegate to `scanGrep`); the framework
1697
+ does not invent it.
1698
+
1699
+ **`write` and `edit` are two native tools, not one.**
1700
+
1701
+ Str-replace `edit` cannot create a file (`oldText: ""`
1702
+ errors). The harness still offers a **Write** tool
1703
+ (`{ path, fileText }`) for create-or-overwrite. Apply-patch
1704
+ `edit` can create via `*** Add File:`. Proto `WriteToolCall`
1705
+ was folded into the `EditToolCall` envelope; the model-facing
1706
+ Write tool remains.
1707
+
1708
+ Workspace keeps both methods, matching those two tools:
1709
+
1710
+ - `write` — native Write schema (`path`, `fileText`, open for
1711
+ later fields). Create or replace a whole file.
1712
+ - `edit` — native Edit schema (`path`, plus `oldText` /
1713
+ `newText`, `edits[]`, or `patchContent`). Change an
1714
+ existing file; apply-patch may add.
1715
+ - `grep` — native result kinds are `"content"` | `"files"` |
1716
+ `"count"` (not `"files_with_matches"`).
1717
+
1718
+ Presence of one does not synthesize the other. A
1719
+ `FileSystem` that implements `write` and omits `edit` offers
1720
+ Write only. That workspace may still list `tools: ["edit"]`
1721
+ (envelope). It must not register `StrReplace`.
1722
+ Change-monitor's CAS `write` / authored str-replace stay
1723
+ authored tools until the mount implements these native
1724
+ schemas. Do not keep a third write contract.
1725
+
1726
+ **`edit`, `delete`, `semSearch`, `readLints` are Workspace
1727
+ methods.** They see `root()`. Optional: `localWorkspace()`
1728
+ implements what the host harness already has;
1729
+ `virtualWorkspace(fs)` omits a method until that
1730
+ `FileSystem` can honor it. The type includes them now. A
1731
+ tree that cannot index or lint simply leaves the method off.
1732
+
1733
+ - `delete` — path verb. `DeleteExecutor` exists; Phase 1 adds
1734
+ the override and fail-closed omission.
1735
+ - `edit` — same tree as `write`. Native schema stays native.
1736
+ Phase 1 fail-closed-omits host `edit` when the method is
1737
+ missing so a virtual tree cannot be patched on disk.
1738
+ Implementing the virtual body can wait for a writable
1739
+ mount (change-monitor Phase 3).
1740
+ - `semSearch` — native schema. Host walks a **codebase index**,
1741
+ then `read`s snippets. A virtual `FileSystem` omits it
1742
+ until that tree has an index. Do not leave host
1743
+ `semSearch` registered against a virtual workspace. Do not
1744
+ alias change-monitor's authored `search` (tags / regex)
1745
+ onto this method.
1746
+ - `readLints` — `diagnosticsExecutor` (LSP against the tree).
1747
+ Same presence rule. A virtual tree has no language service
1748
+ unless a mount provides one.
1749
+
1750
+ ### Effect and approvals stay outside Workspace
1751
+
1752
+ Defaults do not change. `localRuntime()` / no `workspace` is
1753
+ today's `LocalWriteExecutor` + `permissionsService` +
1754
+ pending-decision cards.
1755
+
1756
+ Two planes already exist. Workspace implements neither.
1757
+
1758
+ | Plane | What it is | Who configures it | Virtual |
1759
+ |---|---|---|---|
1760
+ | **Effect** (`read` / `write`) | Dry-run and trace: reads run, writes stub. Native file/shell verbs keep today's effects (`read`/`ls`/`grep`/`glob`/`semSearch`/`readLints` = read; `write`/`edit`/`delete`/`shell` = write). | Existing tool `effect` for authored tools. Natives keep harness defaults. | Same defaults. A Workspace method cannot relabel `edit` as read. |
1761
+ | **Approval policy** | Human-in-the-loop before an effectful call runs. | Today's Agent SDK / harness approval config. | Same policy. The adapter runs **after** the approval gate, not instead of `LocalWriteExecutor`. |
1762
+
1763
+ ```
1764
+ tool call → effect / dry-run → approval policy → Workspace method
1765
+ ```
1766
+
1767
+ Host path allowlists, `.cursorignore`, and worktree guards
1768
+ live on **`localWorkspace` only**. They key on host paths.
1769
+ Virtual paths (`/repo/…`) are a different namespace; wrapping
1770
+ them in `permissionsService.shouldBlockWrite` would block or
1771
+ prompt every virtual call. Mount policy (CAS, publish ladder)
1772
+ stays inside the `FileSystem`.
1773
+
1774
+ Eval `memoryFs` uses the same gates. If the agent's approval
1775
+ policy is off (today's default for many local agents), no new
1776
+ cards appear.
1777
+
1778
+ `@cursor/sdk` exports `FileSystem` and `Workspace`. July
1779
+ authors pass a `FileSystem` into `virtualRuntime`. Helpers
1780
+ return `FileSystem`. Do not add a `search` method: the regex
1781
+ arm is `grep`, `semSearch` is already on `FileSystem`,
1782
+ keyword has no implementation, and tags is an unproven
1783
+ change-monitor issues-mount feature. If they still want tag
1784
+ lookup, it stays an authored tool on their mounts.
1785
+
1786
+ Phase 1 does not need virtual implementations of these four.
1787
+ It does need the omit path: when a workspace is set and the
1788
+ method is absent, the host executor is not registered.
1789
+
1790
+ ### Environment brief is not `Runtime.instructions()`
1791
+
1792
+ The workspace owns facts the model must know — `root()`,
1793
+ mount table, "there is no checkout," grep dialect. Change-monitor
1794
+ currently writes those into `agent/instructions.md` because there
1795
+ is nowhere else to put them. Cloud already injects loop-owned
1796
+ prose the same way (`buildAgentsMdContent` prepends a server-tool
1797
+ MCP catalog and a memory-mount path).
1798
+
1799
+ That is a real injection point. It is not
1800
+ `Runtime.instructions() -> string`.
1801
+
1802
+ `defineAgent({ instructions })` / `instructions.md` is the
1803
+ agent's job: role, steering, what to publish. A method with the
1804
+ same name implies it replaces that file. Today's prompt is
1805
+ already layered, and the layers have different owners:
1806
+
1807
+ | Layer | Owner | When / where |
1808
+ |---|---|---|
1809
+ | `instructions.md` | Agent author | Always; identity of the agent |
1810
+ | `## About you` | Framework | Folded into AGENTS.md / preamble |
1811
+ | Server-tool MCP catalog, "write the script if missing" | **Runtime arm / delivery** | Cloud first prompt; grokbot `createSession({ instructions })` |
1812
+ | `<agentkit_context>` (session, channel, time) | Framework | First user prompt only |
1813
+ | Harness `<user_info>` `Workspace Path` | **Workspace** (today: cwd) | Local harness, every turn |
1814
+ | Mount table, dialects, "no shell" | **Workspace** | Missing as an API; stuffed into instructions.md |
1815
+
1816
+ A string on `Runtime` re-bundles those layers and cannot say
1817
+ which channel to use. Returning prose does not update harness
1818
+ `Workspace Path`. `virtualRuntime` sets `root()` to `/` and
1819
+ does not materialize a scratch cwd. `local.workspaceDir` +
1820
+ virtual is a discovery error. `root()` is the contract; if it
1821
+ disagrees with `brief()`, the model gets two maps.
1822
+
1823
+ Ownership rules:
1824
+
1825
+ 1. **Agent instructions stay authored.** The framework never asks
1826
+ a runtime to supply the agent's job description.
1827
+ 2. **Workspace brief is an addendum.** Mount-table paragraphs move
1828
+ here; "You write a monitoring plan for one PR" does not.
1829
+ 3. **Arm-owned catalogs stay on the arm.** Cloud's
1830
+ `agentsdk-tools` preamble and grokbot's composed blob are
1831
+ delivery, not `Workspace.brief()`.
1832
+ 4. **Brief is session-scoped.** Planner mounts depend on the
1833
+ dispatcher's binding (repo, SHA, which mounts exist).
1834
+ `brief(ctx)` runs when the session workspace is built.
1835
+ 5. **Prefer structure over a blob.** `root()` is what
1836
+ the harness honors. Do not grow `brief()` into a second
1837
+ instructions.md.
1838
+
1839
+ ### What `shell` is allowed to be
1840
+
1841
+ `shell` is "run a program, return stdout/stderr/exit." Language
1842
+ and isolation are implementation details. The namespace is not:
1843
+ `shell` must see the same tree as `ls` / `read`.
1844
+
1845
+ | Implementation | Language | Isolation | How the tree stays one |
1846
+ |---|---|---|---|
1847
+ | Host | bash / zsh / cmd | Serve-host process, optional sandbox | Same cwd as file verbs |
1848
+ | Script isolate (tools.md §8) | TypeScript | Host-side isolate; only bound Workspace methods | `shell` *is* those methods |
1849
+ | Remote machine (computer-use.md) | bash on the pod | anyrun microVM, no host secrets | File verbs are commands over the same `shell` |
1850
+
1851
+ A virtual workspace may omit `shell` (change-monitor today). The
1852
+ loop does not change.
1853
+
1854
+ A TypeScript isolate and host bash are different trust classes.
1855
+ Do not overload one `shell` method for both. Omit `shell` and
1856
+ keep `script` as an authored tool (tools.md §8). A shell-backed
1857
+ mount (`cloudFs`) is the other `shell`. If those two ever
1858
+ need one method, add a `kind` then — not as a staged "v2."
1859
+
1860
+ ### What stays off `Runtime`
1861
+
1862
+ - **Inference.** Local Connect client stays framework-owned.
1863
+ - **`AgentRunner`.** Internal `startServer` test/host seam.
1864
+ Not a `Runtime` arm.
1865
+ - **MCP / OAuth / peer URLs.** Host capabilities, already on
1866
+ `ctx.host` and connection files.
1867
+ - **Server tools / `builtinTools`.** Host-side,
1868
+ `execution: "server"`. They are not workspace verbs.
1869
+ - **`ctx.host.files`.** Durable store. A workspace *may* mount it
1870
+ (change-monitor `/host`); the store API itself is not FS.
1871
+
1872
+ ### Composition rules
1873
+
1874
+ 1. **`localRuntime(opts?)`** → `{ kind: "local", workspace: localWorkspace(opts) }`
1875
+ (today). Sibling `local` is deprecated sugar for `opts`.
1876
+ **`sandboxRuntime(opts?)`** is the named constructor for
1877
+ host-local + Cursor sandbox: `localRuntime({ ...opts, sandbox: true })`.
1878
+ The workspace omits `shell`. Host+sandbox turns do not
1879
+ inject that workspace into the SDK.
1880
+ 2. **`virtualRuntime(fs?)`** → local arm + that workspace.
1881
+ Each implemented method exposes that tool. Authored
1882
+ `execution: "server"` tools stay on the host. Effect and
1883
+ approval policy wrap the tool call, then the Workspace
1884
+ runs. `tools` may only narrow, fail-closed.
1885
+ `local.workspaceDir` / `sandbox` + virtual is a discovery
1886
+ error.
1887
+ 3. **`cloudRuntime` / `grokbotRuntime` have no `workspace`
1888
+ field.** Those arms own their tree. A machine API would
1889
+ add a field on that arm later, not a sibling we then
1890
+ refuse.
1891
+ 4. **`tools` allowlist + cloud/grokbot** → same fail-closed
1892
+ errors as today.
1893
+ 5. **Per-send `runtime?: CloudRuntime | LocalRuntime`.**
1894
+ Host-local + `cloudRuntime` → attach. Host-local +
1895
+ `localRuntime(opts)` / `sandboxRuntime(opts)` → overlay
1896
+ cwd / sandbox / workspaceDir. Cloud + `cloudRuntime` → replace the
1897
+ session cloud payload (no merge). Virtual + either →
1898
+ refuse. Cloud / grokbot + `localRuntime` → refuse.
1899
+ Grokbot + `cloudRuntime` → refuse (today's throw).
1900
+ `grokbotRuntime` is not in the field type. Deprecated
1901
+ `send({ cloud })` / `send({ workspaceDir })` stay as
1902
+ shims. `send({ runtime, cloud })` and
1903
+ `send({ runtime, workspaceDir })` fail closed.
1904
+ 6. **Delivery** is derived from the arm and, on local, which
1905
+ workspace: host → materialize; virtual → no host
1906
+ workspace, `Workspace Path` is `/`, skills only if the
1907
+ author mounted `skillsFs()`; cloud → preamble + store;
1908
+ grokbot → session instructions.
1909
+
1910
+ ---
1911
+
1912
+ ## 2. What the string actually selects
1913
+
1914
+ `AgentRuntime` lives on the Agent SDK config
1915
+ (`packages/agent-serve/src/types.ts`). The Cursor SDK has **no**
1916
+ `runtime` field on `AgentOptions`. It routes on `options.cloud`
1917
+ (or a `bc-` agent id) in `createDefaultAgent` /
1918
+ `resumeDefaultAgent` (`packages/cursor-sdk/src/agent/platform.ts`).
1919
+ The Agent SDK string is a product-level selector that then builds
1920
+ different SDK option bags.
1921
+
1922
+ There are already three values, plus a hybrid, plus a consumer
1923
+ that forges a fourth:
1924
+
1925
+ | | Local loop | Cloud loop | Grok Bot loop | Change-monitor today |
1926
+ |---|---|---|---|---|
1927
+ | **Turn runner** | Cursor SDK local harness on the serve host (`sdk-runner.ts`) | Cursor cloud API (`POST /v1/agents`, SSE) | Hosted `/v0/grokbot` client (`grokbot/runner.ts`) | Local harness |
1928
+ | **Inference** | Backend `AgentService` via Connect; model from `defineAgent` | Coupled inside the cloud agent service; model optional | Hosted harness picks the model; `defineAgent.model` ignored | Same as local |
1929
+ | **Workspace (builtins)** | Host OS via `LocalResourceProvider` | Cloud VM; SDK is an event client only | The account's Sand box | Authored server tools over a consumer filesystem |
1930
+ | **`tools` allowlist** | Enforced (`x-cursor-agent-allowed-tools`) | `ConfigurationError` if set; discovery / hybrid turn fail closed | Fail closed at discovery | `tools: []` so natives never fire |
1931
+ | **Server tools** | SDK `customTools` in-process | HTTP MCP `agentsdk-tools` back to the host | Blocked at discovery | In-process (this is why they stay local) |
1932
+ | **Approvals** | Supported | Not supported | N/A | Supported |
1933
+ | **Instructions** | Host `AGENTS.md` (or inline if cwd is borrowed) | First-prompt preamble | `createSession({ instructions })` | Instructions + skills index; cwd is a fake `/repo` |
1934
+ | **Skills** | Materialized into `.cursor/skills/` | Synced to Agent Store | Warned: not on the box | Advertised in instructions, not harness files |
1935
+ | **Sandbox seeds** | Written into the session workspace | Ignored | Ignored | Unused (`tools: []`) |
1936
+ | **MCP** | Loopback / per-send auth | Bridged or forwarded; needs `--public-url` | Blocked | Used (sandbox stays off so MCP does not fail closed) |
1937
+ | **Subagents** | SDK `agents` | SDK `agents` | Blocked | Off (`tools: []` strands `task`) |
1938
+ | **`send({ runtime })`** | Host-local → attach or overlay; cloud → replace payload | Replace payload | Refused | Not used |
1939
+
1940
+ The string is checked in roughly fifteen Agent SDK sites. The
1941
+ concentrated ones:
1942
+
1943
+ - `discovery.ts` — compile-time capability errors/warnings per value.
1944
+ - `cloud-merge.ts` `resolveSessionRuntime` — per-session cloud attach
1945
+ forces `"cloud"`; grokbot throws.
1946
+ - `session-engine.ts` `buildTurnRequest` — one function assembles
1947
+ tools, customTools, MCP, instructions, skills sync, and the
1948
+ fail-closed hybrid guards.
1949
+ - `sdk-runner.ts` `openAgent` — `cloud:` block vs local `cwd` /
1950
+ `sandbox` / `tools`.
1951
+ - `runtime-dispatch-runner.ts` — grokbot vs everything else.
1952
+
1953
+ There is no plugin registry. `StartServerInternalOptions.runner`
1954
+ can replace the whole `AgentRunner`, which is too coarse to inject
1955
+ a filesystem.
1956
+
1957
+ ---
1958
+
1959
+ ## 3. Change-monitor is already a virtual workspace
1960
+
1961
+ Planner, executor, and curator all do the same thing
1962
+ (`factory/change-monitor/{planner,executor,curator}/agent/agent.ts`):
1963
+
1964
+ ```ts
1965
+ export default defineAgent({
1966
+ runtime: "local",
1967
+ tools: [],
1968
+ local: {
1969
+ workspaceDir: "/repo", // does not need to exist
1970
+ sandbox: false, // sandbox makes MCP fail closed
1971
+ },
1972
+ });
1973
+ ```
1974
+
1975
+ Then `src/vfs/tools.ts` reimplements `ls` / `glob` / `read` /
1976
+ `grep` as server tools whose descriptions and parameter schemas
1977
+ are copied **verbatim** from the native harness
1978
+ (`tools.contract.test.ts` pins the equality). Bodies call
1979
+ `mountVfsForSession` and today's consumer filesystem type
1980
+ (they named it `VFS`; Phase 3 replaces it with `FileSystem`):
1981
+
1982
+ ```ts
1983
+ interface VFS {
1984
+ list(dir: string): Promise<VFSEntry[]>;
1985
+ glob(pattern: string, dir?: string): Promise<string[]>;
1986
+ read(path: string): Promise<string>;
1987
+ grep(query: GrepQuery): Promise<GrepResult>;
1988
+ search(query: SearchQuery): Promise<GrepResult>;
1989
+ write?(path: string, contents: string): Promise<void>;
1990
+ }
1991
+ ```
1992
+
1993
+ That type is **not** a july type. `search` (regex = `grep`,
1994
+ tags on issues, reserved keyword / semantic) stays theirs if
1995
+ they still want the authored tool. The framework cutover is
1996
+ `FileSystem` + native tools.
1997
+
1998
+ Mounts (`src/vfs/session.ts`) compose into one namespace: `/repo`
1999
+ (Origin at a pinned SHA), `/host` (`ctx.host.files`), plus plans /
2000
+ issues / checkpoints / skills. The model thinks it has a
2001
+ filesystem. The serve host has no checkout and no shell.
2002
+
2003
+ Two follow-on designs stay on this local-loop + virtual-workspace
2004
+ posture rather than flipping the string:
2005
+
2006
+ - **Tool scripting** (tools.md §8, not implemented): a `script`
2007
+ server tool that runs TypeScript against a capability-bounded
2008
+ tool bridge. Authored tool, still under
2009
+ `virtualRuntime(fs)`. Not a new loop.
2010
+ - **Computer use** (computer-use.md, not implemented): a
2011
+ writable `/workspace` mount (`cloudFs`) whose file verbs are
2012
+ commands on a lazily-provisioned anyrun microVM. Union it into
2013
+ the same `FileSystem`. Explicitly **not** `runtime: "cloud"`: cloud
2014
+ turns cannot restrict tools, cannot run server tools
2015
+ in-process, and cannot park approvals.
2016
+
2017
+ So "virtual" is one constructor with many filesystems. A third
2018
+ string value would repeat the original mistake.
2019
+
2020
+ ---
2021
+
2022
+ ## 4. Constraints the string hid
2023
+
2024
+ ### 4.1 Cloud is not a different workspace on the same loop
2025
+
2026
+ A local turn: the serve host runs the agent loop, the backend
2027
+ runs inference, tools run against a workspace (host or injected).
2028
+
2029
+ A cloud turn: the SDK is a REST + SSE client (`CloudApiClient`).
2030
+ `createAgent` / `createRun` take a prompt. Tool execution happens
2031
+ on the VM; the SDK surfaces `tool_call` events. There is no
2032
+ in-process `read` or `shell` to swap. The public cloud surface has
2033
+ no `Exec` RPC (computer-use.md Gap 1). Driving the VM by
2034
+ prompting it is "execution by persuasion" — rejected as an exec
2035
+ backend.
2036
+
2037
+ `cloudRuntime` growing a `workspace` field would be either:
2038
+
2039
+ 1. **Lies** — methods that do not run, because that arm owns
2040
+ the tree and the SDK cannot reach it, or
2041
+ 2. **A new product** — a machine primitive (create / exec / read /
2042
+ hibernate, no model attached) that does not exist on the
2043
+ public API.
2044
+
2045
+ Until (2) exists, `cloudRuntime` is a **Runtime arm that
2046
+ owns its tree**. It has no `workspace` field.
2047
+
2048
+ ### 4.2 Inference does not belong on `Runtime`
2049
+
2050
+ Local already separates inference from tools:
2051
+ `AgentConnectClient` streams the model, `resources`
2052
+ (`ResourceAccessor`) execute side effects
2053
+ (`packages/cursor-sdk/src/agent/local-executor.ts`). Cloud and
2054
+ Grok Bot couple them inside a remote service.
2055
+
2056
+ Putting `infer()` on `Runtime` would be true for local, false
2057
+ for cloud/grokbot, and unused by change-monitor. Keep inference
2058
+ off the workspace. Replay / eval fakes keep using
2059
+ `StartServerInternalOptions.runner`.
2060
+
2061
+ ### 4.3 Delivery is a third axis
2062
+
2063
+ | Artifact | Local | Cloud | Grok Bot | Virtual |
2064
+ |---|---|---|---|---|
2065
+ | Instructions | `AGENTS.md` in cwd | First-prompt preamble | Session `instructions` | Authored instructions + optional `brief()` |
2066
+ | Skills | `.cursor/skills/` | Agent Store sync | Not on the box | Nothing unless the author mounts `skillsFs()` |
2067
+ | `execution: "agent"` scripts | Files in workspace | Prompt bodies | Prompt bodies | Banned without `shell` |
2068
+ | Sandbox seeds | Copied into cwd | Dropped | Dropped | Skip; put data in the `FileSystem` if the model should see it |
2069
+ | Workspace Path | Host cwd | VM | Box | `/` |
2070
+
2071
+ `virtualRuntime` does not materialize a scratch dir and does
2072
+ not fake `/repo`. Everything is lazy from the `FileSystem`.
2073
+ Empty `virtualRuntime()` is a workspace at `/` with no methods.
2074
+
2075
+ Skills are the sharp case. Authoring is one tree
2076
+ (`agent/skills/`). Discovery loads it the same way on every
2077
+ constructor. After that the pipes diverge. The model does
2078
+ not get the same catalog, the same load path, or the same
2079
+ ambient extras.
2080
+
2081
+ | | How the catalog is advertised | Where `SKILL.md` bytes live | How the model loads the body | Ambient extras the author did not write |
2082
+ |---|---|---|---|---|
2083
+ | **`localRuntime()`** | Harness native catalog from `.cursor/skills/<name>/SKILL.md` in the session cwd (`materializeWorkspace`, rewritten every turn) | Those files on the host workspace | Harness skill loader; "Using {name}" | Project / user (`~/.cursor/skills`) / team / plugin / Cursor-managed layers if the cwd is a real checkout |
2084
+ | **`cloudRuntime()`** | Native store discovery (`--agent-store-skills-dir`) after `syncSkillsToStore` | Hosted: deployment store `skills/<name>/`. Local serve + personal key: USER store `agent-serve/<agent>/skills/`. Re-copied on every first cloud turn (the mount is writable; a once-per-process copy would let one session edit the definition) | Harness skill loader on the VM | Skills already in the cloud repo. If the store is unreachable or sync fails, **only** those — the turn still runs |
2085
+ | **`grokbotRuntime()`** | Discovery **warning** only: the box does not see authored skills | Nowhere on the box | It cannot | Whatever the hosted Grok Bot box already has. `GET /v1/info` still lists the agent's skills |
2086
+ | **`virtualRuntime`** | None by default. If the author mounts `skillsFs()`, they advertise (instructions index, `brief`, or a later catalog-path override) | Only on that mount, e.g. `/agent/skills/…` | Workspace `read` of the mount path | None. No host `.cursor/skills/` materialization |
2087
+
2088
+ This is why delivery is derived from the **Runtime arm** and,
2089
+ on local, which workspace — not from `Workspace` alone:
2090
+
2091
+ - local + host workspace → materialize `.cursor/skills/` (today).
2092
+ - local + virtual workspace → no host workspace. Skills exist
2093
+ only if the author unions `skillsFs()` (or their own
2094
+ mount) and advertises it. `read` hits `/`.
2095
+ - cloud → store sync. No cwd to write.
2096
+ - grokbot → warn. Do not pretend the box has them.
2097
+
2098
+ Do not put `skills()` on `Workspace`. `skillsFs()` is a
2099
+ `FileSystem` the application may mount. The harness catalog
2100
+ is not pointed at a fake `/repo`.
2101
+
2102
+ ### 4.4 Capability gates stay fail-closed
2103
+
2104
+ `tools` + cloud throws in the Cursor SDK
2105
+ (`assertNoCloudToolsRestriction`). The Agent SDK repeats the
2106
+ guard at discovery and again on hybrid turns
2107
+ (`session-engine.ts` ~2020). `advertiseTools` / per-session MCP
2108
+ auth are local-only for the same reason.
2109
+
2110
+ If a workspace cannot honor an allowlist, the turn is refused.
2111
+ Do not invent a "virtual cloud" that claims `tools: ["read"]`
2112
+ and then runs a VM with a full shell.
2113
+
2114
+ ### 4.5 `send({ runtime })` overlays or attaches
2115
+
2116
+ Two jobs, one field:
2117
+
2118
+ ```ts
2119
+ SendMessageOptions.runtime?: CloudRuntime | LocalRuntime;
2120
+ ```
2121
+
2122
+ **Attach (hybrid).** Host-local agent, this session on a
2123
+ VM. Same continuation token, different turn backend.
2124
+
2125
+ ```ts
2126
+ await send("fix the CI failure", {
2127
+ runtime: cloudRuntime({
2128
+ repos: [{ url, startingRef }],
2129
+ }),
2130
+ });
2131
+ ```
2132
+
2133
+ **Overlay.** Host-local agent, this session's host options
2134
+ (PR worktree, etc.). Today's `send({ workspaceDir })`.
2135
+
2136
+ ```ts
2137
+ await send("review this PR", {
2138
+ runtime: localRuntime({ workspaceDir: worktree }),
2139
+ });
2140
+ ```
2141
+
2142
+ `virtualRuntime` is not a send argument (`virtualRuntime`
2143
+ returns `LocalRuntime`, but a new tree mid-session is
2144
+ refused). Types cannot tell host `localRuntime` from
2145
+ `virtualRuntime`. Virtual + `cloudRuntime` or
2146
+ `localRuntime({ workspaceDir })` fails at session start.
2147
+
2148
+ | Agent default | `send({ runtime })` | |
2149
+ |---|---|---|
2150
+ | host `localRuntime` | `cloudRuntime(opts)` | Attach. Today's hybrid. |
2151
+ | host `localRuntime` | `localRuntime(opts)` | Overlay cwd / sandbox / workspaceDir. |
2152
+ | `virtualRuntime` | `cloudRuntime` / `localRuntime({ workspaceDir })` | Type-ok, refuse at start. |
2153
+ | `cloudRuntime` | `cloudRuntime(opts)` | Replace session cloud payload. No merge. |
2154
+ | `cloudRuntime` | `localRuntime` | Refuse. |
2155
+ | grokbot | `cloudRuntime` / `localRuntime` | Refuse. Today's throw. |
2156
+ | anything | `grokbotRuntime` | Type error. |
2157
+
2158
+ Overlay uses today's cwd resolution: send overlay →
2159
+ agent `localRuntime` opts → `cwd/<sessionId>` → durable
2160
+ session dir. Cloud ignores a `workspaceDir` overlay.
2161
+
2162
+ Preferred `cloudRuntime(opts)` on send is the full session
2163
+ payload. It does not merge over `agent.cloud`. A cloud-default
2164
+ agent that wants today's per-session merge keeps the
2165
+ deprecated shim.
2166
+
2167
+ Deprecated shims: `send({ cloud: opts })` →
2168
+ `cloudRuntime(merge(agent.cloud, opts))`, then the table
2169
+ (so a cloud-default agent still merges; grokbot still
2170
+ refuses). `send({ workspaceDir })` →
2171
+ `localRuntime({ workspaceDir })` when the agent is
2172
+ host-local; ignored on cloud; refused on virtual / grokbot.
2173
+ `send({ runtime, cloud })` and
2174
+ `send({ runtime, workspaceDir })` fail closed.
2175
+
2176
+ This is a session override, not a subagent (`task` /
2177
+ `G12`). A process-wide singleton cannot express this.
2178
+
2179
+ ### 4.6 Workspace is a product surface, not `ResourceAccessor`
2180
+
2181
+ The harness workspace is `ResourceAccessor`
2182
+ (`packages/agent-exec`): read, write/edit, grep, ls, glob (via
2183
+ grep), shell, shell-stdin, MCP, computer-use, and more.
2184
+ `LocalResourceProviderOptions` exposes overrides for **three**
2185
+ of ~fifteen tools. Grep, ls, and glob have no public override.
2186
+
2187
+ `FileSystem` is the public file-verb type: native tool names,
2188
+ native arg schemas (`NativeArgs<T>`). `Workspace` adds `shell`
2189
+ / `root` / `brief`. It is not a drop-in for
2190
+ `ResourceAccessor`. Streaming stdin, MCP, and computer-use
2191
+ stay off the type.
2192
+
2193
+ ### 4.7 The model must call native `read`
2194
+
2195
+ Models are trained on first-class `read` / `ls` / `grep` /
2196
+ `glob`. Change-monitor copies those texts into server tools
2197
+ because there is no supported way to keep the native tool
2198
+ *name* and swap the *implementation* (computer-use.md Gap 2).
2199
+
2200
+ That copy is not the real miss. Agent SDK server tools become
2201
+ Cursor `customTools`. Those are a synthetic MCP server
2202
+ (`custom-user-tools`). Local SDK defaults
2203
+ `mcpMetaToolEnabled` to true, so the model's tool list is
2204
+ `GetMcpTools` and `CallMcpTool`. After discovery the tool on
2205
+ that server is `read`. The call is MCP, not `ReadToolCall`.
2206
+ The stream event is `name: "mcp"`, not `name: "read"`.
2207
+ `custom-user-tools-read` is the internal wire id, not a
2208
+ top-level tool.
2209
+
2210
+ Cursor SDK executor overrides keep the native tool, the
2211
+ native schema, and the native `tool_call`. Only the
2212
+ implementation is swapped. Callers of
2213
+ `virtualRuntime(unionFs(…))` never see the binding. If
2214
+ Workspace only registered the same per-agent server tools
2215
+ change-monitor already ships, the model would still be on
2216
+ MCP. That is not a fix.
2217
+
2218
+ ---
2219
+
2220
+ ## 5. Existing seams (use them; do not invent a fourth)
2221
+
2222
+ ```
2223
+ Agent SDK defineAgent({ runtime }) product selector
2224
+
2225
+ Cursor SDK AgentOptions { local?, cloud? } dispatch
2226
+
2227
+ local-runtime WorkspaceRuntime / SessionRuntime session factory
2228
+
2229
+ local-exec LocalResourceProvider workspace
2230
+
2231
+ agent-exec ResourceAccessor / ExecResource verb registry
2232
+ ```
2233
+
2234
+ Change-monitor sits *above* all of this, as
2235
+ `execution: "server"` tools, because the bottom two layers are
2236
+ not plumbed to `AgentOptions`.
2237
+
2238
+ | Seam | What it is | Why it is not enough today |
2239
+ |---|---|---|
2240
+ | `AgentRunner` | Internal `startServer` turn replacement (`runTurn` / `prewarm`) | Replaces the whole turn; no workspace. Not a `Runtime` arm. |
2241
+ | `RuntimeDispatchingRunner` | grokbot vs SDK | Hard-coded third case |
2242
+ | `AgentOptions.local` / `.cloud` | Parallel config blocks | Presence of `cloud` *is* the runtime until later `Agent.create({ runtime })` |
2243
+ | `LocalResourceProvider` overrides | Swap read/write/shell | Private; 3 of ~15 tools; not on `AgentOptions` |
2244
+ | `ResourceAccessor` | The agent loop's workspace | Correct internal interface; not a public SDK type |
2245
+ | Change-monitor filesystem | Product FS | Consumer-side; duplicated native schemas |
2246
+ | `ctx.host.files` | Durable KV, file-shaped | Explicitly not a filesystem; natives cannot see the hosted sink |
2247
+
2248
+ The first code PR promotes a public `Workspace` on
2249
+ `AgentOptions.local`, bound as executor overrides. Agent SDK
2250
+ then passes that workspace through `virtualRuntime`. Do not add
2251
+ `runtime: "virtual"`. Do not monkey-patch
2252
+ `sessionRuntime.resources` from `@cursor/july`.
2253
+
2254
+ ---
2255
+
2256
+ ## 6. Gaps and risks
2257
+
2258
+ **G1. Cloud cannot honor an injected workspace.**
2259
+ Public `CloudApiClient` is prompt-only. Exec-daemon
2260
+ `ControlService.Exec` exists but needs per-pod credentials a
2261
+ public caller cannot mint (computer-use.md Gap 1). Treat cloud
2262
+ as an opaque loop. Do not ship Workspace methods on
2263
+ `cloudRuntime`.
2264
+
2265
+ **G2. Builtin redirection is not a public SDK feature.**
2266
+ Overrides exist on `LocalResourceProviderOptions` and are not
2267
+ passed through `createDefaultLocalWorkspaceRuntime` or
2268
+ `AgentOptions` (computer-use.md Gap 2). Grep/ls/glob have no
2269
+ override. This is the first code PR: add the missing
2270
+ overrides, put `Workspace` on `AgentOptions.local`, and derive
2271
+ the tools allowlist from method presence. The authoring API
2272
+ in `@cursor/july` waits on that seam. Do not ship
2273
+ `virtualRuntime` on lookalike server tools first.
2274
+
2275
+ **G3. Native tool schemas vs consumer extras.**
2276
+ A redirected `read` must keep the native schema. Verbs that
2277
+ are not Cursor tools (`diff`, change-monitor `search`, CAS
2278
+ `write`) stay authored tools. Do not widen native schemas to
2279
+ carry mount-table prose; that belongs in `brief()` /
2280
+ instructions.md (tools.md §6). Do not add `search` to
2281
+ `FileSystem` for tags or keyword.
2282
+
2283
+ **G4. Hybrid + virtual is easy to get wrong.**
2284
+ A planner with a virtual workspace that also
2285
+ `send({ runtime: cloudRuntime(...) })` would run
2286
+ unrestricted on the VM. Refuse the attach, or require an
2287
+ explicit second agent as the delegation target
2288
+ (tools.md §3). `send({ runtime })` is not an open override.
2289
+
2290
+ **G5. Grokbot is a third Runtime arm, not a workspace.**
2291
+ It has no MCP, no server tools, no model override, no v2
2292
+ architecture. Keep `grokbotRuntime()` as an arm with no
2293
+ `workspace` field.
2294
+
2295
+ **G6. Delivery / `Workspace Path`.**
2296
+ `virtualRuntime` prints `/`. No scratch dir, no
2297
+ `workspaceDir: "/repo"`. A `brief()` that names mounts the
2298
+ `FileSystem` does not have is two maps; `root()` wins.
2299
+
2300
+ **G7. Security: workspace isolation ≠ host isolation.**
2301
+ Injecting a `FileSystem` does not sandbox the serve host. Server tools
2302
+ still run in-process with host credentials. Host `shell` is
2303
+ still host bash. Document the blast radius on each
2304
+ implementation. Prompt-injected writes hit `/host` (or a
2305
+ disposable VM), never the serve host's disk or secrets.
2306
+
2307
+ **G8. `script` is not host `shell`.**
2308
+ A TypeScript isolate that can only call bound Workspace methods
2309
+ is a different trust class from `/bin/bash`. Do not overload
2310
+ one `shell` method for both until two in-tree implementations
2311
+ need a shared `shell` with a `kind`.
2312
+
2313
+ **G9. Two public surfaces.**
2314
+ `@cursor/sdk` owns `FileSystem` / `Workspace` (native schemas).
2315
+ `@cursor/july` owns `virtualRuntime` and helpers that return
2316
+ `FileSystem`. Phase 1 does not put constructors on
2317
+ `Agent.create`. A later SDK deprecation uses the same
2318
+ `Runtime` types. A july-only filesystem cannot re-point
2319
+ natives without the SDK seam. Do not ship the authoring API
2320
+ against lookalikes and flip the binding later.
2321
+
2322
+ **G10. Test and eval story.**
2323
+ A virtual workspace must work in `run` / `eval` without Origin.
2324
+ Change-monitor already has in-memory filesystem fakes; those become
2325
+ `virtualRuntime(memoryFs(…))` in tests.
2326
+
2327
+ **G11. Versioning and published docs.**
2328
+ String `runtime`, sibling `local` / `cloud`, and
2329
+ `send({ cloud })` / `send({ workspaceDir })` are a shipped
2330
+ user contract. Replacement is additive:
2331
+ `localRuntime(opts)` / `cloudRuntime(opts)` plus `@deprecated`
2332
+ on the old selector, sibling bags, and send shims. Do not
2333
+ break existing `defineAgent({ runtime: "cloud", cloud })` or
2334
+ `defineAgent({ local: { workspaceDir } })`. Do not document
2335
+ constructors until they ship (Phase 4 / §9).
2336
+
2337
+ **G12. Subagents keep today's behavior, stored as `Runtime`.**
2338
+ Discovery still loads a child as a Cursor SDK custom
2339
+ subagent (`task`: prompt + description + model). Authored
2340
+ `runtime`, `tools/`, skills, MCP under `agent/subagents/`
2341
+ are warned and ignored. The child gets the harness default
2342
+ toolset on the parent's workspace. Empty july `tools: []`
2343
+ does not mean the child is empty-tooled — those slots are
2344
+ inert.
2345
+
2346
+ Do not change that product. Internally, stop storing
2347
+ `runtime: "local"` + `tools: []` as the child's july
2348
+ config. Express the same forced host-local inherit as
2349
+ `localRuntime()` whose workspace is the parent's (host
2350
+ `localWorkspace` or the virtual tree). Authors do not get
2351
+ a `Runtime` field on subagents in this design.
2352
+
2353
+ A child with its own cwd, own cloud VM, or real july tools
2354
+ is a later product. If that product ever honors a child
2355
+ `Runtime`, default to inheriting the parent workspace so a
2356
+ virtual parent does not spawn host `read`.
2357
+
2358
+ **G13. Independently implemented `read` + `shell`.**
2359
+ Flattening onto Workspace removes the attach-a-different-shell
2360
+ footgun; it does not prove the backends share a namespace.
2361
+ Prefer `virtualRuntime(unionFs(…))` / `localRuntime()`.
2362
+ `virtualWorkspace` stays private.
2363
+
2364
+ ---
2365
+
2366
+ ## 7. Why Cursor SDK first
2367
+
2368
+ A july-only cut can generate server tools from Workspace
2369
+ methods and pin one schema pack. That is change-monitor
2370
+ today, moved into the framework. The model would still call
2371
+ `CallMcpTool` on `custom-user-tools` / `read`.
2372
+ `tools: ["read"]` would still enable host `read`, not virtual
2373
+ `read`. Stream consumers that key on `name === "read"`
2374
+ would still miss the calls.
2375
+
2376
+ That is the problem this work exists to fix. Do not ship
2377
+ `virtualRuntime` on lookalikes and flip the binding later.
2378
+ The first code PR opens `AgentOptions.local.workspace` and
2379
+ rebounds the native executors.
2380
+
2381
+ ---
2382
+
2383
+ ## 8. Plan
2384
+
2385
+ Each phase is its own PR. Today's `runtime` string keeps
2386
+ working until a later deprecation window. Do not start Phase
2387
+ 1 until this document (Phase 0) has been reviewed.
2388
+
2389
+ ### Phase 0 — this document (this PR)
2390
+
2391
+ Publish the design. No product code.
2392
+
2393
+ Reviewers: Agent SDK and cursor-sdk maintainers; change-monitor
2394
+ owners as the first consumer.
2395
+
2396
+ Agree before any code PR:
2397
+
2398
+ - The four goals at the top (compat — old fields deprecated
2399
+ but kept, `localRuntime` default, fail-closed pairs,
2400
+ `Workspace` instead of a longer enum)
2401
+ - `Runtime` is a closed union; `workspace` only on the local
2402
+ arm
2403
+ - **Runtime arm** vs **workspace** vs **delivery**
2404
+ - Workspace is one object, optional methods, one namespace
2405
+ - Cloud/grokbot do not grow a `workspace` field until a
2406
+ machine API exists
2407
+ - Native redirection in Cursor SDK is the first code cut
2408
+ - Change-monitor is the first workspace, in a later PR
2409
+
2410
+ **Review asks** (comment on these):
2411
+
2412
+ 1. Phase 1 adds `Workspace` on **Cursor SDK**
2413
+ `Agent.create({ local: { workspace } })`
2414
+ (`LocalAgentOptions`, next to `cwd` / `dirs`). That is
2415
+ not `defineAgent({ local })`. july constructors stay in
2416
+ Phase 2. Same-shape `Agent.create({ runtime })` is a
2417
+ later SDK deprecation, not Phase 1. Agree?
2418
+ 2. Method presence as the file/shell allowlist: fail-closed
2419
+ when `tools` names a missing method?
2420
+ 3. `virtualRuntime(fs?)` only; no public
2421
+ `virtualRuntime(workspace)`? Empty default, `root()` is `/`.
2422
+ No public `search` method. `FileSystem` is the file-verb
2423
+ type. Agree?
2424
+ 4. Session factory runs once per session, always async
2425
+ (`Promise<FileSystem>`), with `SessionWorkspaceContext`
2426
+ (`sessionId`, `agentName?`, `continuationKey?`, `host?`).
2427
+ Per-wake data stays inside the `FileSystem`. Agree?
2428
+ 5. Rejecting host executors for omitted methods when a
2429
+ workspace is set (defense in depth), or tools-header only?
2430
+ 6. Harness builtins (`webSearch`, `updateTodos`, …) stay off
2431
+ Workspace and off `virtualRuntime`'s default. Opt in via
2432
+ `tools`. Cloud still refuses `tools`. Agree?
2433
+ 7. `edit` / `delete` / `semSearch` / `readLints` on Workspace
2434
+ now (optional). Phase 1 only fail-closed-omits host
2435
+ executors. Virtual bodies wait for a consumer. Agree?
2436
+ 8. `Runtime` is a closed union. `workspace` only on
2437
+ `{ kind: "local" }`. Agree?
2438
+ 9. String `runtime` + sibling `local` / `cloud` stay
2439
+ supported and are marked deprecated. Host options move
2440
+ to `localRuntime(opts)` (`AgentLocalOptions`). Published
2441
+ docs wait for Phase 4 (§9). Agree?
2442
+ 10. `send({ runtime?: CloudRuntime | LocalRuntime })`.
2443
+ Host-local + `cloudRuntime` = attach. Host-local +
2444
+ `localRuntime(opts)` = overlay. Cloud + `cloudRuntime`
2445
+ = replace payload (no merge). Grokbot + cloud refused.
2446
+ `send({ cloud })` / `send({ workspaceDir })` deprecated
2447
+ shims (`send({ cloud })` still merges).
2448
+ `send({ runtime, cloud })` fail-closed. Agree?
2449
+
2450
+ **Out of this PR:** types, executors, discovery, change-monitor
2451
+ code, published user docs.
2452
+
2453
+ ### Phase 1 — Cursor SDK: inject a workspace
2454
+
2455
+ Packages: `cursor-sdk`, `cursor-sdk-local-runtime`,
2456
+ `local-exec`. No `@cursor/july` authoring change.
2457
+
2458
+ `AgentOptions` in this phase is `@cursor/sdk`'s
2459
+ `Agent.create` bag (`packages/cursor-sdk/src/agent/options.ts`).
2460
+ It is not `defineAgent`'s `AgentLocalOptions`. Agent SDK
2461
+ does not grow a `local.workspace` field here. Phase 2's
2462
+ `virtualRuntime` writes the `FileSystem` into this SDK
2463
+ field when july builds `Agent.create({ local })`.
2464
+
2465
+ - Add `FileSystem` / `Workspace` on Cursor SDK
2466
+ `LocalAgentOptions.workspace` (next to `cwd` / `dirs`).
2467
+ Method args are `NativeArgs<T>` matching each tool schema.
2468
+ - Derive `tools` from method presence when the caller omitted
2469
+ `tools`. Intersect fail-closed when they passed one.
2470
+ - Add `overrideGrepExecutor` / `overrideLsExecutor` /
2471
+ `overrideDeleteExecutor` on `LocalResourceProvider`. Glob
2472
+ rides the grep executor. `edit` rides the write executor.
2473
+ `readLints` rides `diagnosticsExecutor`. `semSearch` has
2474
+ no local-exec override today; when a workspace is set and
2475
+ `semSearch` is omitted, do not register the host index
2476
+ tool.
2477
+ - Plumb overrides through
2478
+ `createDefaultLocalWorkspaceRuntime` → the resource
2479
+ provider factory → `createLocalExecutor`.
2480
+ - When a workspace is set, bind present methods and reject
2481
+ omitted path-namespace natives (do not leave host `read` /
2482
+ `edit` / `delete` / `semSearch` / `readLints` / `shell`
2483
+ registered behind a virtual tree). Virtual implementations
2484
+ of `edit` / `delete` / `semSearch` / `readLints` are not
2485
+ required in this PR.
2486
+ - Cloud + `local.workspace` throws, same posture as
2487
+ `tools` + cloud.
2488
+ - Unit tests: a map-backed workspace serves native `read` /
2489
+ `ls` / `grep` / `glob` through the executor adapters. No
2490
+ model turn.
2491
+
2492
+ **Not in this PR:** `defineAgent({ runtime })` object form,
2493
+ constructors on `Agent.create`, `virtualRuntime`,
2494
+ `memoryFs`, change-monitor, published docs. Presence of
2495
+ `cloud` still dispatches.
2496
+
2497
+ Acceptance:
2498
+
2499
+ - `Agent.create({ local: { workspace }, tools })` rebinds
2500
+ natives; a missing method is absent from the toolset and
2501
+ errors if invoked.
2502
+ - Existing local agents (no `workspace`) are unchanged.
2503
+ - `workspace` + `cloud` is a `ConfigurationError`.
2504
+
2505
+ ### Phase 2 — Agent SDK: `runtime` accepts a `Runtime` object
2506
+
2507
+ Packages: `@cursor/july` (`packages/agent-serve`). Depends on
2508
+ Phase 1.
2509
+
2510
+ ```ts
2511
+ runtime?: AgentRuntime | Runtime;
2512
+ local?: AgentLocalOptions; // still supported
2513
+ cloud?: AgentCloudOptions; // still supported
2514
+ ```
2515
+
2516
+ `"local"` / omitted → `localRuntime(agent.local)` (`{ kind:
2517
+ "local", workspace: localWorkspace(opts) }`; today's
2518
+ default). `"cloud"` → `cloudRuntime(agent.cloud)`.
2519
+ `"grokbot"` → `grokbotRuntime()`. Those constructors return
2520
+ the `Runtime` union in §1. JSDoc marks the string and
2521
+ sibling `local` / `cloud` `@deprecated` in favor of
2522
+ `localRuntime(opts)` / `cloudRuntime(opts)`. The fields
2523
+ stay.
2524
+
2525
+ `virtualRuntime(fs?)` lifts a `FileSystem` (or an async
2526
+ per-session factory) to `AgentOptions.local.workspace`. Empty
2527
+ `virtualRuntime()` is `/` and no file methods. Omit `tools`
2528
+ so workspace methods are the file/shell allowlist; do not
2529
+ keep `tools: []`. Discovery validates the composition rules
2530
+ in §1. `brief(ctx)` folds into the prompt. `<user_info>`
2531
+ Workspace Path is `/`. No `materializeWorkspace`.
2532
+ `local.workspaceDir` + virtual fails closed.
2533
+ `SendMessageOptions.runtime?: CloudRuntime | LocalRuntime`.
2534
+ Host-local + `cloudRuntime` attaches. Host-local +
2535
+ `localRuntime(opts)` overlays cwd / sandbox / workspaceDir.
2536
+ Virtual + either fails closed. Deprecated `send({ cloud })`
2537
+ still merges over `agent.cloud`. Deprecated
2538
+ `send({ workspaceDir })` still overlays a host-local agent.
2539
+
2540
+ `memoryFs` and `skillsFs` ship here as `FileSystem`
2541
+ helpers. The latter is opt-in.
2542
+
2543
+ **Not in this PR:** change-monitor cutover, `unionFs` unless
2544
+ a test needs it, published user-guide rewrite.
2545
+
2546
+ Acceptance:
2547
+
2548
+ - Existing string agents unchanged.
2549
+ - A fixture agent with
2550
+ `virtualRuntime(memoryFs({ "/a.txt": "hi" }))` can
2551
+ `agent-sdk call read` and get `hi` from the native `read`
2552
+ tool.
2553
+ - `virtualRuntime(...)` + `send({ runtime: cloudRuntime(...) })`
2554
+ still fails closed.
2555
+ - `send({ cloud })` still attaches a local agent to cloud
2556
+ (merge). On a cloud-default agent it still merges.
2557
+ `send({ runtime: cloudRuntime(opts) })` replaces, no merge.
2558
+ - Grokbot + `send({ cloud })` / `send({ runtime: cloudRuntime })`
2559
+ still refuses.
2560
+ - `send({ runtime: localRuntime({ workspaceDir }) })` overlays
2561
+ a host-local agent. `send({ workspaceDir })` still works.
2562
+ - `send({ runtime, cloud })` fails closed.
2563
+ - Discovery matrix: object-form runtimes produce the same
2564
+ fail-closed errors as today's string (tools + cloud,
2565
+ advertiseTools + cloud, grokbot + server tools).
2566
+
2567
+ ### Phase 3 — change-monitor adopts `virtualRuntime`
2568
+
2569
+ Depends on Phase 2. Session binding is the hard part:
2570
+ planner/executor filesystem is per-session (PR, SHA, which mounts
2571
+ exist), and `defineAgent` runs before that is known.
2572
+
2573
+ Replace `tools: []` + copied `src/vfs/tools.ts` schemas for
2574
+ `ls` / `glob` / `read` / `grep` with
2575
+ `virtualRuntime(async (ctx) => unionFs({ … }))`. Mount
2576
+ `skillsFs()` if they still want `/agent/skills`. Writable
2577
+ mounts implement native `write` / `edit` and omit
2578
+ `delete` / `semSearch` / `readLints` unless a mount earns
2579
+ them. Keep authored tools that have no FileSystem method
2580
+ (`diff`, CAS extras, plugin telemetry, tag `search` if they
2581
+ still want it). Drop `workspaceDir: "/repo"`. `root()` is
2582
+ `/`.
2583
+
2584
+ Acceptance:
2585
+
2586
+ - Change-monitor does not ship a second copy of native
2587
+ schemas.
2588
+ - Planner/executor still have no host shell and no checkout.
2589
+ - Native `read` / `ls` / `grep` / `glob` hit the mounted `FileSystem`.
2590
+
2591
+ ### Phase 4 — docs and templates
2592
+
2593
+ Ship the published-docs plan in §9. Constructors become the
2594
+ authoring vocabulary. The string and sibling `local` /
2595
+ `cloud` stay in the pages as deprecated-but-supported.
2596
+ Do this after Phase 2 is real. Do not document constructors
2597
+ that have not shipped.
2598
+
2599
+ ### After Phase 4 — not a "v2" of this type
2600
+
2601
+ This design is the end state of the authoring type.
2602
+ `Workspace` already lists every path-namespace verb. Phases
2603
+ 1–4 ship that type and the first workspaces. There is no
2604
+ second abstraction waiting behind "v1."
2605
+
2606
+ What remains is implementation of methods a given workspace
2607
+ omits, or **other products** that plug into the same
2608
+ constructors.
2609
+
2610
+ **Already in this type; a workspace implements when it can**
2611
+
2612
+ | Gap | Who fills it | Not |
2613
+ |---|---|---|
2614
+ | Virtual `edit` / `write` | Change-monitor writable mounts (Phase 3) | A new Workspace field |
2615
+ | Virtual `delete` | A mount that can delete | Host `delete` behind a virtual tree |
2616
+ | Virtual `semSearch` | A `FileSystem` that owns an index of `root()` | Host index of the serve disk |
2617
+ | Virtual `readLints` | A mount that owns diagnostics | Host LSP against a fake cwd |
2618
+ | `unionFs` as a public helper | Phase 3 if the factory needs it; otherwise when a second consumer unions mounts | A new runtime |
2619
+
2620
+ **Other designs, same constructors**
2621
+
2622
+ | Work | Home | How it uses this type |
2623
+ |---|---|---|
2624
+ | anyrun `/workspace` (`cloudFs`) | factory/change-monitor `computer-use.md` | A shell-backed mount inside `virtualRuntime(unionFs(…))`. Not `cloudRuntime`. |
2625
+ | TypeScript `script` isolate | factory/change-monitor `tools.md` §8 | Authored server tool. Not `Workspace.shell`. |
2626
+ | Cloud `tools` allowlist | Cursor cloud agent API | Then `defineAgent({ tools })` means the same on `cloudRuntime`. Fail closed until that API exists. |
2627
+ | Machine without a model (create / exec / read / hibernate) | New backend surface | The only way `cloudRuntime` can accept a `workspace` for inspection. Do not prompt the VM. |
2628
+ | Child with its own `Runtime` | Later product | Today's inherit stays (`G12`). Internally already `localRuntime()` on the parent workspace. No authoring `runtime` on `agent/subagents/` in this design. |
2629
+ | `Agent.create({ runtime })` | `@cursor/sdk` after Phase 4 | Same constructors as july. Sibling `local` / `cloud` deprecated as the dispatcher. Not Phase 1. |
2630
+ | Deprecate `runtime: "local" \| "cloud"` | After Phase 4 has shipped | Additive constructors first. |
2631
+
2632
+ **Not a follow-on. Do not build.**
2633
+
2634
+ - `{ kind: "custom"; runner: AgentRunner }` on `Runtime`
2635
+ - A `Harness` type / `Runtime.webSearch()`
2636
+ - `infer()` on `Runtime`
2637
+ - `virtualRuntime(workspace: Workspace)`
2638
+ - `cloudRuntime` Workspace methods that prompt the VM
2639
+ - Lookalike server tools as a stepping stone
2640
+
2641
+ ---
2642
+
2643
+ ## 9. Documentation
2644
+
2645
+ Phase 4 work. Published Agent SDK docs live under
2646
+ `packages/agent-serve/docs/` (VitePress). They never say
2647
+ `agent-serve`. Product name is Agent SDK; package is
2648
+ `@cursor/july`; CLI is `agent-sdk`. This design file stays
2649
+ internal (`srcExclude: ["design/**"]`).
2650
+
2651
+ Do not rewrite user docs in Phases 0–3. Constructors that
2652
+ have not shipped must not appear on cursor.com / the
2653
+ VitePress site.
2654
+
2655
+ ### How we talk about runtime
2656
+
2657
+ Lead with what the reader is choosing: where the turn runs,
2658
+ and (for local) which files the model sees. Constructors
2659
+ are the vocabulary.
2660
+
2661
+ | Say | Do not say |
2662
+ |---|---|
2663
+ | `localRuntime()` runs turns on this machine | "the local loop" / `kind: "local"` |
2664
+ | `sandboxRuntime()` runs those turns inside Cursor's local sandbox | `localRuntime({ sandbox: true })` as the preferred form |
2665
+ | `cloudRuntime({ repos })` runs turns on a Cursor cloud agent | "cloud is the other filesystem" / `runtime: "cloud"` as the only form |
2666
+ | `virtualRuntime(fs)` runs locally against a filesystem you pass | `runtime: "virtual"` / `Workspace` / `Computer` |
2667
+ | String `runtime` and sibling `local` / `cloud` still work | That they are gone, or that they are the preferred form |
2668
+
2669
+ Omit internals: `Runtime` union arms, `kind`, `FileSystem` vs
2670
+ `Workspace` lift, `AgentRunner`, `virtualWorkspace`,
2671
+ `AgentOptions.local.workspace`, MCP lookalikes, executor
2672
+ overrides. Those belong in this design file and in JSDoc for
2673
+ SDK maintainers, not in the user guide.
2674
+
2675
+ `virtualRuntime` is a real user feature (tests, hosted
2676
+ trees, change-monitor-style agents). Document it as "pass
2677
+ the files the agent may see." Show `memoryFs` for tests.
2678
+ Show a mount table only when `unionFs` has shipped. Do not
2679
+ teach readers to implement `FileSystem` from scratch on the
2680
+ first page.
2681
+
2682
+ Deprecated fields get one short note and a link to the
2683
+ constructor, not a second tutorial:
2684
+
2685
+ > `runtime: "cloud"` is still accepted. Prefer
2686
+ > `cloudRuntime({ repos })`.
2687
+
2688
+ Document host options on `localRuntime({ cwd, workspaceDir,
2689
+ sandbox })`, next to `cloudRuntime({ repos })`. Sibling
2690
+ `local` / `cloud` get a deprecated note and a link.
2691
+
2692
+ ### Pages to update (Phase 4)
2693
+
2694
+ **Own the contract (rewrite the runtime sections)**
2695
+
2696
+ | Page | Change |
2697
+ |---|---|
2698
+ | [reference/agent-config.md](/docs/reference/agent-config.md) | `runtime` type becomes constructors **or** the deprecated string. Table + "Choose a runtime" show `localRuntime(opts)` / `cloudRuntime(opts)` / `virtualRuntime(fs)` first. Move cwd / sandbox / workspaceDir onto the `localRuntime` section. Mark sibling `local` / `cloud` and string `runtime` deprecated. |
2699
+ | [guides/cloud-runtime.md](/docs/guides/cloud-runtime.md) | Open with `cloudRuntime({ repos })`. Keep `runtime: "cloud"` as the still-supported form. Cloud is a turn host, not a filesystem. Hybrid is `send({ runtime: cloudRuntime(opts) })`; local overlay is `send({ runtime: localRuntime(opts) })`. `send({ cloud })` / `send({ workspaceDir })` deprecated. |
2700
+ | [concepts.md](/docs/concepts.md) | Local vs cloud table: same facts, constructor names. Add one line that `virtualRuntime` is local-with-your-files, not a third host. |
2701
+
2702
+ **New page**
2703
+
2704
+ | Page | Change |
2705
+ |---|---|
2706
+ | `guides/virtual-runtime.md` (new) | When to use `virtualRuntime`: no host checkout, tests (`memoryFs`), composed mounts. Empty default (`/`, no methods). Skills only if you mount them. No `workspaceDir`. Fail-closed hybrid cloud attach. Link from agent-config and concepts. |
2707
+
2708
+ **Fix examples and cross-links only**
2709
+
2710
+ | Page | Change |
2711
+ |---|---|
2712
+ | [quickstart.md](/docs/quickstart.md) | Default scaffold stays `localRuntime()` or omit `runtime` (same default). Do not mention virtual. |
2713
+ | [scaffolding-agents.md](/docs/scaffolding-agents.md) | Same. |
2714
+ | [reference/instructions.md](/docs/reference/instructions.md) / [reference/skills.md](/docs/reference/skills.md) | "On the cloud runtime" → "On `cloudRuntime`" where it is a code choice; keep the delivery facts. |
2715
+ | [reference/tools.md](/docs/reference/tools.md) | `tools` + cloud still fail closed. Mention it works the same on `cloudRuntime`. |
2716
+ | [reference/cli.md](/docs/reference/cli.md) | `agent-sdk call read` against a virtual fixture if we add one in Phase 2. |
2717
+ | [troubleshooting.md](/docs/troubleshooting.md) | Discovery errors: string+incoherent pair, `virtualRuntime` + `workspaceDir`, hybrid attach or local overlay on virtual. |
2718
+ | `templates/*.md` (e.g. [code-wiki.md](/docs/templates/code-wiki.md)) | Examples that set `runtime: "cloud"` get a constructor form; leave a one-line deprecated equivalent if the template is copy-paste for existing agents. |
2719
+ | [building-with-agents.md](/docs/building-with-agents.md) / [deployment.md](/docs/deployment.md) | Wording only if they name the string as the way to choose a host. |
2720
+
2721
+ **Do not document here**
2722
+
2723
+ - Cursor SDK `AgentOptions.local.workspace` (SDK maintainer JSDoc).
2724
+ - Change-monitor mount tables (their repo docs).
2725
+ - Grok Bot unless a public template already ships `grokbot`.
2726
+ - `kind`, `Workspace`, `FileSystem` method lists (JSDoc on
2727
+ `@cursor/sdk` is enough for authors who implement a mount).
2728
+
2729
+ ### JSDoc (Phase 2, with the types)
2730
+
2731
+ On `@cursor/july`:
2732
+
2733
+ - `runtime?: AgentRuntime | Runtime` — constructors first.
2734
+ String values `@deprecated Use localRuntime(opts) / cloudRuntime(opts) / grokbotRuntime()`.
2735
+ - `local?` — `@deprecated Use localRuntime(opts)`. Still
2736
+ read when `runtime` is omitted or `"local"`. Error with
2737
+ `virtualRuntime` or `cloudRuntime`.
2738
+ - `cloud?` — `@deprecated Use cloudRuntime(opts)`.
2739
+ - `SendMessageOptions.cloud` — `@deprecated Use
2740
+ send({ runtime: cloudRuntime(opts) })`. Compat shim still
2741
+ merges over `agent.cloud`.
2742
+ - `SendMessageOptions.workspaceDir` — `@deprecated Use
2743
+ send({ runtime: localRuntime({ workspaceDir }) })`.
2744
+ - `SendMessageOptions.runtime?: CloudRuntime | LocalRuntime`
2745
+ — attach, overlay, or replace cloud payload. Virtual +
2746
+ either, and grokbot + cloud, fail at start. Together with
2747
+ sibling `cloud` / `workspaceDir` fails closed.
2748
+
2749
+ On `@cursor/sdk` (Phase 1):
2750
+
2751
+ - New `local.workspace` documented as the injection seam.
2752
+ - Existing `local` / `cloud` blocks stay unmarked. Same-shape
2753
+ `Agent.create({ runtime })` is after Phase 4.
2754
+
2755
+ ### Deprecation window
2756
+
2757
+ Phase 4 publishes constructors and marks the string
2758
+ deprecated. A later PR, after that has shipped, may set a
2759
+ removal date. This design does not pick one.
2760
+
2761
+ ---
2762
+
2763
+ ## 10. Non-goals
2764
+
2765
+ - Rewriting the cloud agent API or making `tools` work on `bc-`
2766
+ agents. Fail closed stays until the API can honor it.
2767
+ - Unifying Grok Bot into Workspace methods.
2768
+ - Promoting `AgentRunner` onto `defineAgent` / `Runtime`.
2769
+ Replay and eval fakes keep `StartServerInternalOptions.runner`.
2770
+ - A user-authored `infer()` on `Runtime`.
2771
+ - `Runtime.instructions()` or `Runtime.read()` — those belong
2772
+ on Workspace, or they re-bundle the string.
2773
+ - A `Shell` type the caller attaches next to `FileSystem`.
2774
+ `shell` stays on `Workspace`.
2775
+ - `virtualRuntime(workspace: Workspace)`. The lift is
2776
+ internal; authors pass a `FileSystem`. Change-monitor
2777
+ `search` / tags stay authored if needed.
2778
+ - Moving server tools, MCP, or `ctx.host.files` behind
2779
+ Workspace.
2780
+ - A `runtime: "virtual"` string.
2781
+ - `SendMessageOptions.runtime?: Runtime` (the full union,
2782
+ including grokbot). The field is `CloudRuntime |
2783
+ LocalRuntime`. Honoring every pair is still a non-goal.
2784
+ - Removing string `runtime` or sibling `local` / `cloud` in
2785
+ Phases 1–4. Deprecate in docs and JSDoc; delete later.
2786
+ - Letting `cloudRuntime` pretend to implement Workspace methods
2787
+ by prompting the VM.
2788
+ - Shipping lookalike server tools as the first binding.
2789
+ - One PR that lands SDK plumbing, july constructors, and
2790
+ change-monitor together.
2791
+ - A `Harness` / `Runtime.webSearch()` surface so todos and
2792
+ search look like Workspace methods. Availability is
2793
+ `defineAgent({ tools })`. Rebind later via local executor
2794
+ overrides if a consumer needs a stub.
2795
+
2796
+ ---
2797
+
2798
+ ## 11. Proof each code PR owes
2799
+
2800
+ Phase 0 has no experiment. Later PRs prove the seam they
2801
+ open, not the whole stack.
2802
+
2803
+ 1. **Phase 1.** Cursor SDK unit test: `overrideReadExecutor`
2804
+ (and ls/grep) serve a map. `Agent.create` with an injected
2805
+ workspace advertises only the present methods.
2806
+ 2. **Phase 2.** Fixture agent with
2807
+ `virtualRuntime(memoryFs({ "/a.txt": "hi" }))`.
2808
+ `agent-sdk call read` returns `hi` from native `read`.
2809
+ Discovery matrix matches today's fail-closed errors.
2810
+ 3. **Phase 3.** Change-monitor planner/executor typecheck
2811
+ against `virtualRuntime(unionFs(…))`. No second schema copy.
2812
+ No host checkout.
2813
+
2814
+ ---
2815
+
2816
+ ## 12. Decision summary
2817
+
2818
+ | Question | Answer |
2819
+ |---|---|
2820
+ | Design goals? | Compat with today's `defineAgent` / `AgentOptions` shape. Default `localRuntime()`. Fail-closed incoherent pairs. Extensibility via `Workspace`, not a longer enum. |
2821
+ | What replaces the string? | Closed `Runtime` union. `workspace` only on `{ kind: "local" }`. `localRuntime(opts)` / `cloudRuntime(opts)` take today's option bags. String + sibling `local` / `cloud` stay, deprecated. |
2822
+ | Where do file/shell verbs live? | `FileSystem` in `@cursor/sdk` (native tool names + `NativeArgs<T>`). `Workspace` adds `shell` / `root` / `brief`. No public `search` method. |
2823
+ | How do new files get created? | Native **Write** tool (`write` on FileSystem). Str-replace `edit` cannot create. Apply-patch `edit` can via `Add File`. |
2824
+ | Empty `virtualRuntime()`? | `/`, no methods, no mounts, no skills, no materialization. Application unions what it needs (`skillsFs()` is opt-in). |
2825
+ | Session filesystem? | `virtualRuntime(async (ctx) => fs)` once per session. Always `Promise<FileSystem>`. `SessionWorkspaceContext`: `sessionId`, `agentName?`, `continuationKey?`, `host?`. Per-wake data is a lazy layer. |
2826
+ | Effect / approvals? | Unchanged defaults. Gate wraps the tool call, then Workspace runs. Host path allowlists stay on `localWorkspace` only. |
2827
+ | Does method presence enable tools? | Yes, for Workspace methods. `tools` may only narrow, fail-closed. |
2828
+ | Where do `webSearch` / `updateTodos` live? | Off Workspace. Loop-owned harness builtins. `tools` opts them in on local/virtual. Cloud already has them and cannot select. No `Harness` type. |
2829
+ | Should those methods live on `Runtime`? | No. That re-bundles the arm and the workspace. |
2830
+ | Should `Runtime.instructions()` exist? | No. Workspace gets `root()` + optional session-scoped `brief()`. |
2831
+ | Should inference live on `Runtime`? | No. |
2832
+ | What about Grok Bot? | `grokbotRuntime()` — a Runtime arm, no `workspace` field. |
2833
+ | How does change-monitor fit? | `virtualRuntime(async (ctx) => unionFs({ /repo, /host, /agent/skills: skillsFs(), … }))`. |
2834
+ | How does computer-use.md's VM fit? | A `cloudFs` mount in that same union. |
2835
+ | How does `runtime: "cloud"` fit? | `cloudRuntime(opts)` — opaque Runtime arm that owns its tree. |
2836
+ | `send({ runtime })`? | `CloudRuntime \| LocalRuntime`. Host-local + cloud = attach. Host-local + `localRuntime` = overlay. Cloud + `cloudRuntime` = replace (no merge). Grokbot + cloud refused. Virtual + either refused. `send({ cloud })` still merges (deprecated). |
2837
+ | What stays off `Runtime`? | `model`, `tools`, `builtinTools`, `hosting`, `architecture`, `workspaceFiles`. |
2838
+ | Subagents? | Behavior unchanged (`G12`). Internally `localRuntime()` on the parent workspace. No authoring `Runtime` on children. |
2839
+ | Can this ship without Cursor SDK changes? | No. Lookalikes put the model on MCP `CallMcpTool`, not native `read`. |
2840
+ | First PR? | This design document. |
2841
+ | First code PR? | Cursor SDK `local.workspace` + executor overrides (Phase 1). |
2842
+ | Then? | Agent SDK object form (Phase 2), change-monitor (Phase 3), published docs (Phase 4 / §9). After that: `Agent.create({ runtime })` on the SDK; other products plug into this type; see §8. |
2843
+
2844
+ The string was a convenient name for a bundle that no longer
2845
+ bundles cleanly. The replacement is a local Runtime arm you
2846
+ can point at any workspace, and two remote arms that have no
2847
+ `workspace` field until those products grow a machine API.
2848
+ Native tools bind to the injected workspace. That seam opens
2849
+ in Cursor SDK before `defineAgent` grows constructors.
1498
2850
 
1499
2851
  ---
1500
2852
 
@@ -2795,8 +4147,9 @@ Source: /docs/guides/mcp-oauth.md
2795
4147
  Use host MCP OAuth when your agent talks to a remote MCP server that
2796
4148
  speaks OAuth, and you want credentials on the serve host (or the hosted
2797
4149
  engine) instead of a Cursor account connector. Local login writes tokens
2798
- next to your Cursor credentials. `--store` copies them onto the
2799
- deployment as secrets so prod can reconnect after a redeploy.
4150
+ next to your Cursor credentials. `--store` copies those tokens onto the
4151
+ deployment as secrets so prod can reconnect after a redeploy. Hosted
4152
+ Connect lets the current process retry.
2800
4153
 
2801
4154
  The companion skill is
2802
4155
  [`skills/mcp-auth/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/mcp-auth/SKILL.md).
@@ -2807,8 +4160,8 @@ The companion skill is
2807
4160
  flow (`agent-sdk mcp oauth <connection>`)
2808
4161
  - Keep tokens in `mcp-auth.json` under the CLI config directory, bound
2809
4162
  to that connection's resource URL
2810
- - Upsert deployment secrets with `--store` so hosted engines seed the
2811
- same tokens from env
4163
+ - Upsert deployment secrets with `--store` so the next deploy seeds the
4164
+ same tokens from env. Hosted Connect lets the current process retry.
2812
4165
  - Use `advertiseTools: true` when local turns should call the server by
2813
4166
  name. Host tools can still call it through `ctx.host.mcp`.
2814
4167
 
@@ -2888,7 +4241,16 @@ connection URL (tokens are bound to the resource URL).
2888
4241
 
2889
4242
  ## How do I store credentials on a hosted deployment?
2890
4243
 
2891
- Authorize once, then push secrets to the deployment:
4244
+ Authorize once. Then get the tokens onto the deployment.
4245
+
4246
+ On a hosted engine, finishing Connect lets the current process retry.
4247
+ Self-hosted serve writes tokens locally only.
4248
+
4249
+ `--store` from a signed-in laptop is the reliable way to put
4250
+ `MCP_OAUTH_*` on the next deploy. If `agent-sdk secrets list` is
4251
+ empty, run `--store` and redeploy.
4252
+
4253
+ From a laptop, push secrets with the CLI:
2892
4254
 
2893
4255
  ```bash
2894
4256
  agent-sdk mcp oauth inventory --store
@@ -2906,13 +4268,21 @@ agent-sdk mcp oauth inventory --store
2906
4268
  | `MCP_OAUTH_<NAME>_CLIENT_ID` | dynamic client id when registration returned one |
2907
4269
 
2908
4270
  You must be signed in (`agent-sdk login`) with permission to set secrets
2909
- on that slug. Secrets apply on the **next** deploy; run
2910
- `agent-sdk deploy` (or wait for your usual deploy path) after `--store`.
4271
+ on that slug. Secrets apply on the **next** deploy. Run
4272
+ `agent-sdk deploy` after `--store`. A fresh pod sees `MCP_OAUTH_*`
4273
+ only after that deploy.
2911
4274
 
2912
4275
  On the engine, when `mcp-auth.json` is empty, serve seeds the OAuth
2913
4276
  provider from those env vars so host MCP calls work without a browser
2914
4277
  on the pod.
2915
4278
 
4279
+ These secrets are deployment-wide. Anyone who can list or set secrets
4280
+ on that slug, and the hosted engine's runtime env, can use them. That
4281
+ is the same blast radius as `--store`. They are not scoped to the
4282
+ caller who clicked Connect. `cursorAccount: true` tokens stay on the
4283
+ Cursor backend for the deployment service account and do not use
4284
+ `MCP_OAUTH_*`.
4285
+
2916
4286
  ## How do host tools call the server?
2917
4287
 
2918
4288
  ```ts
@@ -2932,7 +4302,7 @@ an allowlist or other deterministic gate.
2932
4302
  | --- | --- |
2933
4303
  | `must be defineConnection({ url, oauth: true })` | Add `oauth: true` on that connection, or pick the right connection name |
2934
4304
  | Callback never completes | Keep port `8787` free; finish the browser login on this machine |
2935
- | Hosted calls unauthorized after `--store` | Confirm secrets with `agent-sdk secrets list <slug>`, then redeploy |
4305
+ | Hosted calls unauthorized after Connect or `--store` | Confirm secrets with `agent-sdk secrets list <slug>`, then redeploy |
2936
4306
  | Tokens ignored after URL change | Expected: resource URL binding drops stale entries. Re-run `mcp oauth` |
2937
4307
 
2938
4308
  ## What's next
@@ -3162,16 +4532,14 @@ Source: /docs/guides/slack.md
3162
4532
 
3163
4533
  # Slack agents
3164
4534
 
3165
- The Slack channel puts your agent in Slack as its own Socket Mode bot.
3166
- `agent-sdk slack create` opens the dashboard wizard and writes tokens
3167
- to `.env.local`. To own the Slack app yourself, run
3168
- `agent-sdk slack init --manual` and paste the manifests at
3169
- [api.slack.com](https://api.slack.com/apps). Socket Mode has no
3170
- public Request URL. Replies stream in threads, with tool "thinking" steps,
3171
- suggested prompts, and opt-in approval buttons.
4535
+ The Slack channel is a Socket Mode bot. The app connects out, so you
4536
+ don't need a public Request URL. Mentions and DMs start a thread.
4537
+ Replies stream there, with thinking steps and suggested prompts.
3172
4538
 
3173
- The companion skill is
3174
- [`skills/setup-slack/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/setup-slack/SKILL.md).
4539
+ Use `agent-sdk slack create` when Cursor should own the Slack app. Use
4540
+ `agent-sdk slack init --manual` when you own it. Commands and flags live
4541
+ in the [CLI reference](/docs/reference/cli.md#slack). Coding agents should
4542
+ follow [`skills/setup-slack/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/setup-slack/SKILL.md).
3175
4543
 
3176
4544
  ## Define the channel
3177
4545
 
@@ -3181,35 +4549,23 @@ Author `agent/channels/slack.ts` with `slackChannel()` from
3181
4549
  ```ts
3182
4550
  import { slackChannel } from "@cursor/july/channels/slack";
3183
4551
 
3184
- // Single agent: reads SLACK_BOT_TOKEN + SLACK_APP_TOKEN
3185
4552
  export default slackChannel();
3186
-
3187
- // Multi-agent serve: one Slack app (and token pair) per agent
3188
- export default slackChannel({ envPrefix: "WEATHER_AGENT" });
3189
- // → WEATHER_AGENT_SLACK_BOT_TOKEN + WEATHER_AGENT_SLACK_APP_TOKEN
3190
4553
  ```
3191
4554
 
3192
- By default the channel connects over Socket Mode (the app connects
3193
- outward, so it works from behind any firewall) and responds only to
3194
- `app_mention` events and direct messages. It also refuses Slack Connect
3195
- users, guests, and senders from other workspaces. Threads bind to
3196
- sessions with `continuationToken = channelId:threadTs`, so a thread is
3197
- one durable conversation. Replies stream (`chat.startStream` /
3198
- `appendStream` / `stopStream`) with a plain-message fallback, a rotating
3199
- status shows while the agent works, and the pack sets thread titles and
3200
- suggested prompts. When a Slack API is unavailable (`method_not_found`,
3201
- `missing_scope`), the pack degrades to `postMessage` instead of failing.
4555
+ `slackChannel()` reads `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN`. Pass
4556
+ `envPrefix: "WEATHER_AGENT"` to read `WEATHER_AGENT_SLACK_BOT_TOKEN`
4557
+ and `WEATHER_AGENT_SLACK_APP_TOKEN` instead. Use a prefix when one
4558
+ host serves several Slack bots.
3202
4559
 
3203
- Missing tokens leave the channel idle (`channel idle missing
3204
- credentials`) rather than failing `serve`. That's useful when you mount
3205
- many agents and only some have Slack apps.
4560
+ A thread is one conversation. The bot answers mentions and DMs. It
4561
+ refuses Slack Connect users, guests, and people from other workspaces.
4562
+ Missing tokens leave this channel idle. The rest of `serve` still
4563
+ starts.
3206
4564
 
3207
4565
  ## Control who can message the agent
3208
4566
 
3209
- External senders are blocked by default. Slack Connect users, guests, and
3210
- people whose home workspace is not the install team never reach the
3211
- handler. Set `blockExternals: false` only when the agent should serve
3212
- people outside your org:
4567
+ External senders are blocked. Set `blockExternals: false` only when
4568
+ people outside your org should reach the agent:
3213
4569
 
3214
4570
  ```ts
3215
4571
  export default slackChannel({
@@ -3217,9 +4573,8 @@ export default slackChannel({
3217
4573
  });
3218
4574
  ```
3219
4575
 
3220
- Sessions are thread-scoped by default, so anyone in the thread can
3221
- continue. Restrict follow-ups to the person who started the session with
3222
- `respondTo: "author"`:
4576
+ Anyone in the thread can continue it. Restrict follow-ups to the person
4577
+ who started the session with `respondTo: "author"`:
3223
4578
 
3224
4579
  ```ts
3225
4580
  export default slackChannel({
@@ -3229,237 +4584,143 @@ export default slackChannel({
3229
4584
 
3230
4585
  ## Set it up
3231
4586
 
3232
- For a dedicated Socket Mode bot, run `agent-sdk slack create`. The CLI
3233
- opens the signed-in Cursor dashboard wizard. Slack consent, bot name,
3234
- and admin-approval wait happen there. Tokens are stored as deployment
3235
- secrets and written to `.env.local`. They are never shown. Do not paste
3236
- a manifest at [api.slack.com](https://api.slack.com/apps) unless you
3237
- are on the [manual setup](#manual-setup) path.
3238
-
3239
- Bots that already have tokens in `.env.local` keep working. Skip the
3240
- wizard unless you want a Cursor-managed app.
4587
+ `slack create` opens the Cursor dashboard wizard. Finish Slack consent
4588
+ and the bot name there. The CLI writes tokens to `.env.local` and never
4589
+ prints them. Skip the wizard if those tokens are already set, unless
4590
+ you want a Cursor-managed app.
3241
4591
 
3242
4592
  ### Provision with the CLI
3243
4593
 
3244
4594
  ```bash
3245
- agent-sdk slack create --dir . # dev app (default)
3246
- agent-sdk slack create --dir . --prod # prod app
4595
+ agent-sdk slack create --dir .
4596
+ agent-sdk slack create --dir . --prod
3247
4597
  ```
3248
4598
 
3249
- `slack create` needs a signed-in host (`agent-sdk login` or
3250
- `CURSOR_API_KEY`). It scaffolds `agent/channels/slack.ts` when the file
3251
- is missing, opens the dashboard wizard, and waits. Finish **Add Slack
3252
- to this agent** in the browser as the same Cursor account. The CLI
3253
- writes `<PREFIX>_SLACK_BOT_TOKEN` and `<PREFIX>_SLACK_APP_TOKEN` into
3254
- `<dir>/.env.local`, then runs `doctor`. Serve and smoke it as described
3255
- [below](#wire-the-env-and-verify).
4599
+ Sign in first (`agent-sdk login` or `CURSOR_API_KEY`). Finish **Add
4600
+ Slack to this agent** as the same Cursor account. Then run
4601
+ `agent-sdk slack doctor --prefix MY_AGENT` and serve:
3256
4602
 
3257
- If Slack needs a workspace admin to approve the app, keep the CLI
3258
- running. Managed install does not file the request. Open Slack's
3259
- **Request approval** page (the CLI prints the link; the same URL is
3260
- **Send a reminder** after you submit). After an admin approves, click
4603
+ ```bash
4604
+ agent-sdk serve --dir . --dev
4605
+ ```
4606
+
4607
+ Invite the bot and mention it, or DM it. Expect a Thinking status,
4608
+ then a threaded streaming reply.
4609
+
4610
+ If Slack needs a workspace admin, keep the CLI running. Open the
4611
+ **Request approval** link it prints. After an admin approves, click
3261
4612
  **Retry** in the wizard.
3262
4613
 
3263
- Useful companions: `--name` / `--icon` / `--channel-posts` prefill the
3264
- wizard. `--slack-team T0123ABCD` picks a workspace when several are
3265
- connected. A second `slack create` for the same agent and env overwrites
3266
- that Slack app. It keeps the Slack app id and replaces the manifest and
3267
- tokens. `agent-sdk slack destroy` deletes the provisioned app.
3268
- `agent-sdk slack icon <source>` updates the icon later.
4614
+ `slack destroy` deletes the Cursor-managed app. `slack icon` updates
4615
+ its icon. A second `slack create` for the same agent and environment
4616
+ replaces that app's manifest and tokens.
3269
4617
 
3270
4618
  ### Provision from the dashboard
3271
4619
 
3272
4620
  The same wizard is on [Deployed Agents](https://cursor.com/dashboard).
3273
- Open **Integrations** on an agent and click **Add Slack to this agent**,
3274
- or follow the URL `slack create` printed.
4621
+ Open **Integrations** and click **Add Slack to this agent**.
3275
4622
 
3276
- A first-run agent that has no hosted engine yet shows **Not deployed
3277
- yet**. Local `agent-sdk serve` works from `.env.local`. The next
3278
- **Deploy** injects the stored secrets; there is no Slack-only Redeploy.
4623
+ Local `agent-sdk serve` reads `.env.local`. The next deploy injects the
4624
+ stored secrets.
3279
4625
 
3280
- If Slack needs admin approval, the wizard waits. Click **Request
3281
- approval** to file the request in Slack (that same page later sends a
3282
- reminder). After an admin approves, click **Retry**.
4626
+ If Slack needs admin approval, click **Request approval**, then
4627
+ **Retry** after an admin approves.
3283
4628
 
3284
- The app row's menu covers later care: rename, set an icon, rotate
3285
- tokens, remove the app, or disconnect the workspace.
4629
+ The app row's menu covers rename, icon, token rotation, removal, and
4630
+ workspace disconnect.
3286
4631
 
3287
- ### Wire the env and verify
4632
+ ### Create your own Slack app
3288
4633
 
3289
- `agent-sdk slack create` already wrote the tokens to `.env.local`, so
3290
- after provisioning skip straight to `doctor`. `serve` / `dev` /
3291
- `slack doctor` load that file (real environment variables always win).
4634
+ `slack init --manual` is for an app you own. It writes the channel file
4635
+ and Slack manifests. When Slack CLI (`~/.slack/bin/slack`) is logged
4636
+ in, it installs the app. When it isn't, install the app yourself.
3292
4637
 
3293
4638
  ```bash
3294
- agent-sdk slack doctor --prefix MY_AGENT
4639
+ agent-sdk slack init --manual --dir . --name "My Agent"
3295
4640
  ```
3296
4641
 
3297
- Proceed when `app_token`, `connections_open`, `bot_token`, and
3298
- `auth_test` are all green. Then serve and smoke it:
4642
+ Install Slack CLI if you need it:
3299
4643
 
3300
4644
  ```bash
3301
- agent-sdk serve --dir . --dev
3302
- # expect: [agent-sdk/slack] Socket Mode connected
4645
+ curl -fsSL https://downloads.slack-edge.com/slack-cli/install.sh | bash
3303
4646
  ```
3304
4647
 
3305
- Invite the bot to a channel and @mention it (or DM it). Expect the
3306
- Thinking… status, then a threaded streaming reply, with correlated
3307
- server logs (`inbound kind=app_mention`, `session start`,
3308
- `reply delivered via stream|postMessage`).
4648
+ Log in with `slack login --no-prompt`. In Slack, send
4649
+ `/slackauthticket <ticket>`, then
4650
+ `slack login --ticket <ticket> --challenge <code>`. Install the
4651
+ workspace app with `slack app install`. Create a separate production
4652
+ app the same way.
3309
4653
 
3310
- ### Manual setup
4654
+ Or paste `.slack/manifest.dev.json` at
4655
+ [api.slack.com/apps](https://api.slack.com/apps) (**Create New App** →
4656
+ **From a manifest**). Repeat with `.slack/manifest.prod.json`. Dev and
4657
+ prod are separate Slack apps.
3311
4658
 
3312
- `slack init --manual` is for a Slack app you own at api.slack.com. You
3313
- paste the generated manifests and mint tokens yourself. Use this when
3314
- you want an unmanaged app, or when the dashboard wizard is not an
3315
- option.
3316
-
3317
- ```bash
3318
- agent-sdk slack init --manual --dir . --name "My Agent"
3319
- ```
4659
+ Then put a bot token (`xoxb-...`) and an app-level token with
4660
+ `connections:write` (`xapp-...`) in `.env.local`. Use the names
4661
+ `slack init --manual` printed. Run `slack doctor` and serve as above.
3320
4662
 
3321
- That writes `agent/channels/slack.ts`, Slack manifests under the
3322
- project state directory, `env.example`, and `setup-status.json`.
3323
- `--no-prefix` uses shared `SLACK_*` variables on
3324
- a single-agent host. `--prefix CUSTOM` overrides the directory-derived
3325
- prefix. `--channel-posts` subscribes the manifests to channel-post
3326
- events.
3327
-
3328
- `slack manifest --env both` regenerates the JSON files without
3329
- touching the channel file.
3330
-
3331
- #### Create the apps
3332
-
3333
- This phase needs someone with permission to create Slack apps.
3334
-
3335
- 1. Open [api.slack.com/apps](https://api.slack.com/apps) → **Create New
3336
- App** → **From a manifest**.
3337
- 2. Paste `manifest.dev.json` and create the dev app.
3338
- 3. Repeat with `manifest.prod.json`. Dev and prod are separate Slack
3339
- apps with separate tokens.
3340
-
3341
- #### Install and mint tokens
3342
-
3343
- For each app: **Install to Workspace** and copy the Bot User OAuth Token
3344
- (`xoxb-…`). Then under **Basic Information → App-Level Tokens** create a
3345
- token with scope `connections:write` and copy it (`xapp-…`).
3346
-
3347
- Put the pair in `.env.local` using the names from `env.example`, then
3348
- run `slack doctor` and serve as [above](#wire-the-env-and-verify).
4663
+ Serve this app with Agent SDK. Do not run `slack deploy`.
3349
4664
 
3350
4665
  ## Choose when the agent engages
3351
4666
 
3352
- Mentions and DMs are on by default. Watching channels means dispatching
3353
- on new posts without a mention. That's an explicit opt-in:
4667
+ Mentions and DMs are on. Watching channel posts without a mention is
4668
+ opt-in:
3354
4669
 
3355
4670
  ```ts
3356
4671
  export default slackChannel({
3357
4672
  envPrefix: "TRIAGE",
3358
4673
  engagement: {
3359
- // mentions / directMessages default to true
3360
4674
  channelPosts: {
3361
- allow: ["#triage-alerts"], // use ["*"] for every joined channel
3362
- posts: "top-level", // default: thread replies never dispatch
3363
- debounceMs: 15_000, // optional: let rapid edits settle
3364
- includeBotPosts: false, // default: bot-authored posts never dispatch
4675
+ allow: ["#triage-alerts"],
4676
+ posts: "top-level",
4677
+ debounceMs: 15_000,
3365
4678
  },
3366
4679
  },
3367
- onChannelPost: async (ctx, message) => {
3368
- // same contract as onAppMention: return null to skip
4680
+ onChannelPost: async (_ctx, message) => {
3369
4681
  return message.markdown.length > 20 ? {} : null;
3370
4682
  },
3371
4683
  });
3372
4684
  ```
3373
4685
 
3374
- Channel watching needs the `message.channels` / `message.groups` events
3375
- on the Slack app. Pass `--channel-posts` on `slack create` or
3376
- `slack init --manual`. The bot must also be a member of each watched
3377
- channel.
3378
-
3379
- Set `allow: ["*"]` to watch every channel the bot has joined.
4686
+ Pass `--channel-posts` on `slack create` or `slack init --manual` so
4687
+ the Slack app receives those events. The bot must be in each watched
4688
+ channel. `allow: ["*"]` watches every joined channel.
3380
4689
 
3381
- Set `includeBotPosts: true` when the posts worth watching come from bots:
3382
- alert feeds, webhook integrations, or other agents posting notes. The
3383
- watching app's own posts stay dropped either way, matched by the `bot_id`
3384
- and bot user id from `auth.test`, so an agent can never dispatch on its
3385
- own replies. Use this for a bot-fed alerts channel.
4690
+ Set `includeBotPosts: true` for alert feeds and other bots. This app
4691
+ never wakes on its own posts.
3386
4692
 
3387
4693
  ## Prepare work on the host
3388
4694
 
3389
4695
  Mention and DM handlers may return a prepared `message`,
3390
- `workspaceFiles`, or `cloud` block. It's the same host-prep pattern as
4696
+ `workspaceFiles`, or `cloud` block. Use the same host-prep pattern as
3391
4697
  [custom channels](/docs/guides/webhooks.md#prepare-on-the-host-then-hand-off).
3392
- PR agents use it: extract a PR URL from the mention text and run the
3393
- same host path as the HTTP channel.
3394
4698
 
3395
- Slack file uploads are attached automatically. Images become vision input.
4699
+ Slack file uploads attach automatically. Images become vision input.
3396
4700
  Supported documents become workspace files for the turn.
3397
4701
 
3398
4702
  ## Add approval buttons
3399
4703
 
3400
- Tools with `needsApproval` park until a person decides. Route that
3401
- through Slack with one flag:
4704
+ Tools with `needsApproval` park until a person decides. Post those
4705
+ decisions in Slack with one flag:
3402
4706
 
3403
4707
  ```ts
3404
4708
  export default slackChannel({
3405
- toolApprovals: true, // posts Block Kit Approve/Deny cards + routes clicks
4709
+ toolApprovals: true,
3406
4710
  });
3407
4711
  ```
3408
4712
 
3409
- Approval cards need interactivity on the Slack app. Recreate with
3410
- `slack create` if clicks do nothing. Composing events by hand: spread
3411
- `buildToolApprovalEvents({ credentials })` into `events` and set
3412
- `interactivity: true` on the channel so Socket Mode routes the clicks.
3413
-
3414
- Cards show redacted, truncated arguments (Block Kit size limits);
3415
- execution still uses the full validated input, so review sensitive tools
3416
- in the playground when the arguments may exceed the card. Approvals
3417
- exist for `execution: "server"` tools on the local runtime only, and
3418
- parked calls do not survive a host restart. The full lifecycle is in
4713
+ The Slack app needs interactivity. Recreate it with `slack create` if
4714
+ clicks do nothing. Cards show redacted, truncated arguments. Execution
4715
+ still uses the full input, so review sensitive tools in the playground
4716
+ when the card may hide fields. The lifecycle is in
3419
4717
  [Human-in-the-loop](/docs/guides/human-in-the-loop.md).
3420
4718
 
3421
4719
  ## Run several agents on one host
3422
4720
 
3423
- One Slack app and token pair per agent. Never share a pair across
3424
- agents in the same process. `envPrefix` keeps them apart
3425
- (`WEATHER_AGENT_SLACK_*`, `TRIAGE_SLACK_*`, …), and agents without tokens mount
3426
- with their Slack channel idle while everything else serves normally.
3427
-
3428
- ## Keep the channel healthy
3429
-
3430
- Two habits matter most.
3431
-
3432
- - Don't `await` long work inside Slack dispatch handlers. The pack
3433
- dispatches through `waitUntil` and streams as the turn progresses.
3434
- - In `--dev` (loopback) or `--allow-anonymous` (trusted shared host), the
3435
- playground can list and stream Slack sessions and resolve their parked
3436
- approvals (the audit trail records the HTTP caller). Bearer-auth hosts
3437
- stay strict: Slack approvals must come from Slack interactivity or a
3438
- matching principal.
3439
-
3440
- ## CLI reference
3441
-
3442
- The `slack` subcommands cover setup end to end.
3443
-
3444
- ```bash
3445
- agent-sdk slack setup # printed setup guide
3446
- agent-sdk slack create --dir . # dashboard wizard (dev app)
3447
- agent-sdk slack create --dir . --prod # prod app
3448
- agent-sdk slack destroy --dir . # delete the provisioned app
3449
- agent-sdk slack icon ./icon.png --dir . # set the provisioned app's icon
3450
- agent-sdk slack init --manual --dir . # manifests to paste at api.slack.com
3451
- agent-sdk slack manifest --env both # regenerate those JSON files
3452
- agent-sdk slack doctor --prefix MY_AGENT # token / connectivity checks
3453
- ```
3454
-
3455
- ## What's next
3456
-
3457
- Continue with these pages:
3458
-
3459
- - [Human-in-the-loop](/docs/guides/human-in-the-loop.md): the approval lifecycle
3460
- behind `toolApprovals`
3461
- - [Webhooks and custom channels](/docs/guides/webhooks.md): the mechanism this
3462
- pack is built on
4721
+ Give each agent its own Slack app and token pair. Set `envPrefix` so
4722
+ the names don't collide (`WEATHER_AGENT_SLACK_*`, `TRIAGE_SLACK_*`).
4723
+ Agents without tokens stay idle. The others still serve.
3463
4724
 
3464
4725
  ---
3465
4726
 
@@ -4058,7 +5319,7 @@ npx @cursor/july docs
4058
5319
  | Building a new agent with Cursor | [Scaffold an agent with Cursor](/docs/scaffolding-agents.md) |
4059
5320
  | Turning a Cursor Automation into a project | [Convert a Cursor Automation](/docs/guides/convert-automation.md) |
4060
5321
  | Wiring an agent to Slack | [Slack guide](/docs/guides/slack.md) |
4061
- | Starting from a packaged template | [Demo](/docs/templates/demo.md), [Code wiki](/docs/templates/code-wiki.md), [Security reviewer](/docs/templates/security-reviewer.md), [Security help](/docs/templates/security-help.md), [Triage](/docs/templates/triage.md), or [Agentic Owners](/docs/templates/agentic-owners.md) |
5322
+ | Starting from a packaged template | [Demo](/docs/templates/demo.md), [Code wiki](/docs/templates/code-wiki.md), [Living AGENTS.md](/docs/templates/agents-md.md), [Security reviewer](/docs/templates/security-reviewer.md), [Security help](/docs/templates/security-help.md), [Triage](/docs/templates/triage.md), or [Agentic Owners](/docs/templates/agentic-owners.md) |
4062
5323
  | Wiring an agent to GitHub webhooks | [GitHub guide](/docs/guides/github.md) |
4063
5324
  | Driving PRs from a cloud VM | [PR autofixer template](/docs/templates/pr-autofixer.md) |
4064
5325
  | Driving an agent from Linear (or another tracker) | [Webhooks guide: Linear example](/docs/guides/webhooks.md#example-linear-as-the-control-plane) |
@@ -4086,6 +5347,8 @@ npx @cursor/july docs
4086
5347
  collects the PR, the model records, then comments.
4087
5348
  - [Keep a code wiki current after merge](/docs/templates/code-wiki.md):
4088
5349
  update in-repo `wiki/` pages when a pull request merges.
5350
+ - [Keep AGENTS.md current from last week's work](/docs/templates/agents-md.md):
5351
+ pull rules out of merged PRs and Slack, then open a follow-up PR.
4089
5352
  - [Security reviewer](/docs/templates/security-reviewer.md): review pull
4090
5353
  requests for exploitable bugs and post one comment.
4091
5354
  - [Answer security questions in Slack](/docs/templates/security-help.md):
@@ -4119,8 +5382,8 @@ npx @cursor/july docs
4119
5382
  - [Human-in-the-loop approvals](/docs/guides/human-in-the-loop.md): park a
4120
5383
  tool call until a person signs off.
4121
5384
  - [Host MCP OAuth](/docs/guides/mcp-oauth.md): authorize
4122
- `oauth: true` connections, store tokens locally, and `--store` them
4123
- on hosted deployments.
5385
+ `oauth: true` connections, store tokens locally, and persist them
5386
+ on hosted deployments (`--store`).
4124
5387
  - [Agent-to-agent](/docs/guides/agent-to-agent.md): every agent is an MCP
4125
5388
  server; agents can delegate to each other.
4126
5389
  - [Cloud runtime](/docs/guides/cloud-runtime.md): run turns on Cursor cloud
@@ -4727,8 +5990,8 @@ When every slot is busy, newly admitted turns queue FIFO instead of
4727
5990
  failing: the stream records a durable `turn.queued` event with the
4728
5991
  queue position, `GET /v1/sessions` reports `queued: true`, and each
4729
5992
  queued turn starts as soon as a slot frees. A queued turn still counts
4730
- as running for busy semantics: follow-ups preempt it, and direct tool
4731
- calls get `409 session_busy`. Omit for unlimited.
5993
+ as running for busy semantics: follow-ups preempt it, and direct
5994
+ write-effect tool calls get `409 session_busy`. Omit for unlimited.
4732
5995
 
4733
5996
  ```ts
4734
5997
  export default defineAgent({
@@ -5507,8 +6770,9 @@ the value before execution. A local call needs no inference credential.
5507
6770
  A hosted call still needs Cursor credentials to reach the deployment.
5508
6771
 
5509
6772
  `--session` runs the tool inside an existing session and records it on
5510
- the event stream. If a model turn is active or pending, the server
5511
- returns `session_busy`; retry after the turn finishes. The command
6773
+ the event stream. If a model turn is active or pending, a read-effect
6774
+ tool runs alongside it and a write-effect tool gets `session_busy`;
6775
+ retry after the turn finishes. The command
5512
6776
  prints the server's JSON response and exits non-zero unless the HTTP
5513
6777
  response succeeds with `ok: true`. See
5514
6778
  [Tools](/docs/reference/tools.md#call-a-tool-without-a-model-turn).
@@ -5580,6 +6844,7 @@ non-zero when the reconstructed trajectory failed.
5580
6844
  agent-sdk init ./my-agent # scaffold package.json, tsconfig.json, agent/ + a demo tool
5581
6845
  agent-sdk init ./my-demo --template demo # record a PR walkthrough
5582
6846
  agent-sdk init ./code-wiki --template code-wiki # keep wiki pages current after merge
6847
+ agent-sdk init ./agents-md --template agents-md # keep AGENTS.md current from last week's PRs and Slack
5583
6848
  agent-sdk init ./my-reviewer --template security-reviewer # review PRs for security bugs
5584
6849
  agent-sdk init ./security-help --template security-help # answer security questions in Slack
5585
6850
  agent-sdk init ./my-triage --template triage-linear # comment on Linear issues
@@ -5597,8 +6862,9 @@ agent-sdk init # no directory: print the setup guide
5597
6862
  `@cursor/july` resolves for `dev` and `run`.
5598
6863
 
5599
6864
  Templates may ship `init.json`. On a TTY, `init` asks those questions
5600
- before writing files. `code-wiki`, `pr-autofixer`, and `security-help`
5601
- ask for GitHub repos. Repeat
6865
+ before writing files. `code-wiki`, `pr-autofixer`, `security-help`, and
6866
+ `agents-md` ask for GitHub repos. `agents-md` also asks for Slack
6867
+ channels. Repeat
5602
6868
  `--var id=value` to answer without a prompt.
5603
6869
  `--json` and non-TTY hosts skip the interview unless `--var` is set.
5604
6870
 
@@ -5756,7 +7022,8 @@ commands with an explicit `--json` flag skip this automatic check.
5756
7022
  agent-sdk deploy [--dir <path>] [--slug <slug> | --all] [--team <id>]
5757
7023
  [--repo <https-url>] [--ref <git-ref>] [--path <agent-path>]
5758
7024
  [--cursor-events-repo owner/name]...
5759
- [--allow-domain <domain>]... [--no-wait] [--json]
7025
+ [--allow-domain <domain>]...
7026
+ [--no-wait] [--json]
5760
7027
  ```
5761
7028
 
5762
7029
  Managed hosting requires team-admin permission and the team's
@@ -5909,6 +7176,8 @@ URL connections run a browser PKCE flow. Tokens are written to
5909
7176
  `mcp-auth.json` under the CLI config directory (override with
5910
7177
  `AGENT_SERVE_CONFIG_DIR`). Pass `--store` to upsert matching
5911
7178
  `MCP_OAUTH_<CONNECTION>_*` secrets on the hosted deployment.
7179
+ `--store` is the path for the next deploy. Hosted Connect lets the
7180
+ current process retry.
5912
7181
 
5913
7182
  Cursor-account connections authorize the hosted deployment's service
5914
7183
  account through the Cursor backend's connector consent flow. Those
@@ -5987,6 +7256,7 @@ agent-sdk slack icon <https-url-or-file> [--dir <path>] [--prod]
5987
7256
  [--slack-team <T…>] [--team <id>] [--json]
5988
7257
  agent-sdk slack init --manual [--dir <path>] [--name <name>]
5989
7258
  [--prefix <prefix> | --no-prefix] [--channel-posts]
7259
+ [--install | --no-install] [--slack-team <T…>] [--prod]
5990
7260
  agent-sdk slack manifest [--dir <path>] [--name <name>]
5991
7261
  [--env dev|prod|both] [--channel-posts] [--print]
5992
7262
  agent-sdk slack doctor [--dir <path>] [--prefix <prefix> | --no-prefix] [--json]
@@ -6015,17 +7285,26 @@ a local png, jpg, or gif file of at most 512KB.
6015
7285
 
6016
7286
  `slack init` without `--manual` exits non-zero and writes no files. Use
6017
7287
  `slack create` for the dashboard wizard. `slack init --manual` writes
6018
- the channel file, development and production manifests, `env.example`,
6019
- and setup status under the project. You paste those manifests at
6020
- api.slack.com. The command refuses to overwrite a target file. If a
6021
- collision occurs, it exits non-zero; files created earlier in the run
6022
- remain. The token prefix defaults to the directory basename normalized
6023
- to uppercase snake case. Explicit `--prefix` values use the same
6024
- normalization. For example, `pr-approver` becomes
6025
- `PR_APPROVER_SLACK_BOT_TOKEN`. `--no-prefix` uses shared
6026
- `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN`. `--channel-posts` subscribes
6027
- the manifests to channel-post events. The command always prints a JSON
6028
- summary.
7288
+ the channel file, development and production manifests, a Slack CLI
7289
+ `.slack/` project (hook + manifests, committed with the repo). When
7290
+ Slack CLI is logged in, it installs the app. Otherwise `next` asks
7291
+ you to install it. `--install` requires that install. `--no-install`
7292
+ skips it. `--prod` selects the deployed app.
7293
+ `--slack-team` picks the workspace. Slack CLI keeps install tokens in
7294
+ that process; copy `xoxb` and mint `xapp` (`connections:write`) into
7295
+ `.env.local`. Tokens that appear in `.env` during that install are
7296
+ copied onto the prefixed names. Paste `.slack/manifest.dev.json` at
7297
+ api.slack.com when the Slack CLI is missing. Do not run
7298
+ `slack deploy`. The command refuses to overwrite the channel file.
7299
+ It updates the Slack CLI hook and manifests when `.slack/` already
7300
+ exists. If a collision occurs, it exits non-zero; files created
7301
+ earlier in the run remain. The token prefix defaults to the directory
7302
+ basename normalized to uppercase snake case.
7303
+ Explicit `--prefix` values use the same normalization. For example,
7304
+ `pr-approver` becomes `PR_APPROVER_SLACK_BOT_TOKEN`. `--no-prefix`
7305
+ uses shared `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN`.
7306
+ `--channel-posts` subscribes the manifests to channel-post events.
7307
+ The command always prints a JSON summary.
6029
7308
 
6030
7309
  `slack manifest` regenerates selected manifest files. `--env` defaults
6031
7310
  to `both`, and `--name` defaults to the directory name. `--print` writes
@@ -6156,8 +7435,10 @@ Tokens come from env vars. Never hardcode them in the file.
6156
7435
  ## Host MCP OAuth
6157
7436
 
6158
7437
  For servers that speak OAuth, set `oauth: true` and authorize with the
6159
- CLI. Tokens live in `mcp-auth.json` under the CLI config directory. `--store`
6160
- copies them onto the hosted deployment as `MCP_OAUTH_<NAME>_*` secrets.
7438
+ CLI or mid-run Connect. Tokens live in `mcp-auth.json` under the CLI
7439
+ config directory. `--store` copies them onto the deployment as
7440
+ `MCP_OAUTH_<NAME>_*` secrets. Hosted Connect lets the current
7441
+ process retry.
6161
7442
 
6162
7443
  ```ts
6163
7444
  export default defineConnection({
@@ -6169,6 +7450,7 @@ export default defineConnection({
6169
7450
  ```bash
6170
7451
  agent-sdk mcp oauth inventory # browser PKCE → local mcp-auth.json
6171
7452
  agent-sdk mcp oauth inventory --store # also upsert deployment secrets
7453
+ # Hosted Connect retries this process. Self-hosted stays file-only.
6172
7454
  ```
6173
7455
 
6174
7456
  Full walkthrough: [Host MCP OAuth](/docs/guides/mcp-oauth.md). Companion
@@ -6387,7 +7669,7 @@ lazily on first use.
6387
7669
 
6388
7670
  Continue with these pages:
6389
7671
 
6390
- - [Host MCP OAuth](/docs/guides/mcp-oauth.md): `mcp oauth`, `--store`
7672
+ - [Host MCP OAuth](/docs/guides/mcp-oauth.md): `mcp oauth`, Connect, `--store`
6391
7673
  - [Agent-to-agent](/docs/guides/agent-to-agent.md): peers in depth
6392
7674
  - [Tools](/docs/reference/tools.md): authored tools that wrap MCP connections
6393
7675
  - [Webhooks](/docs/guides/webhooks.md): calling MCP connections from handlers
@@ -6548,6 +7830,7 @@ and `playgroundUrl` deep-links the session in the playground.
6548
7830
  | `message` | Required user message |
6549
7831
  | `title` | Session title |
6550
7832
  | `dryRun` | Run read tools and stub write tools |
7833
+ | `asOf` | ISO-8601 instant with a timezone, frozen at create; the prompt states it, `ctx.now()` returns it, and tools declaring `timeArgs` refuse calls not bounded at or before it. `400` when unusable |
6551
7834
  | `workspaceFiles` | UTF-8 files written into the session workspace |
6552
7835
  | `cloud` | Per-session cloud options merged over the agent defaults |
6553
7836
 
@@ -6629,7 +7912,8 @@ curl -X POST http://127.0.0.1:3000/<slug>/v1/tools/inspect_pr \
6629
7912
  It runs an authored server tool in-process: schema-validated, no model
6630
7913
  turn. An optional `"sessionId"` in the body runs it inside an existing
6631
7914
  session and records it on that session's stream (`409 session_busy`
6632
- while a turn runs). Agent-execution tools are rejected with `400`, and
7915
+ for a write-effect call while a turn runs; reads run alongside the
7916
+ turn). Agent-execution tools are rejected with `400`, and
6633
7917
  unknown tools with `404` and the list of available names. For the
6634
7918
  semantics, see [Tools](/docs/reference/tools.md#call-a-tool-without-a-model-turn).
6635
7919
 
@@ -7345,8 +8629,10 @@ message. Interrupted turns record `turn.failed` with
7345
8629
  Slack `stop` / `@agent stop` does the same for that thread and clears
7346
8630
  pending coalesced nudges.
7347
8631
 
7348
- Session-bound deterministic tool calls share the same execution lock.
7349
- They return `409 session_busy` while a model turn is running.
8632
+ Session-bound deterministic tool calls share the lock only for writes: a
8633
+ write-effect call returns `409 session_busy` while a model turn is
8634
+ running, a read-effect call runs alongside the turn (see
8635
+ [Tools](/docs/reference/tools.md#call-a-tool-without-a-model-turn)).
7350
8636
 
7351
8637
  ## Which events can I stream?
7352
8638
 
@@ -7753,6 +9039,31 @@ export default defineTool({
7753
9039
  `dryRunResult` keeps the result shape stable. Without it, a stubbed write
7754
9040
  returns `"Operation acknowledged."`. Traces mark the result as stubbed.
7755
9041
 
9042
+ ### Bound time arguments under `asOf`
9043
+
9044
+ Declare `timeArgs` with the input properties that carry time bounds. In a
9045
+ session created with `asOf`, every named argument must be an absolute
9046
+ ISO-8601 instant with a timezone, no later than the session's frozen
9047
+ instant. A call that omits one, or supplies a relative or later value, is
9048
+ refused with an error telling the model to retry with absolute bounds.
9049
+ Sessions without `asOf` ignore the declaration.
9050
+
9051
+ ```ts
9052
+ export default defineTool({
9053
+ description: "Query telemetry between two instants.",
9054
+ inputSchema: z.object({ since: z.string(), until: z.string() }),
9055
+ effect: "read",
9056
+ timeArgs: ["since", "until"],
9057
+ async execute({ since, until }) {
9058
+ return queryTelemetry(since, until);
9059
+ },
9060
+ });
9061
+ ```
9062
+
9063
+ Every name in `timeArgs` must exist in the input schema; a session with
9064
+ `asOf` refuses to start otherwise, because a misspelled name would silently
9065
+ check nothing.
9066
+
7756
9067
  ## Define an agent tool
7757
9068
 
7758
9069
  Set `execution: "agent"` and the tool materializes as a shell script
@@ -7869,7 +9180,14 @@ when the call returns. Pass a `sessionId` (a body field over
7869
9180
  HTTP, `--session` on the CLI, `options.sessionId` programmatically) to
7870
9181
  run inside an existing session instead: the tool sees that session's
7871
9182
  workspace, and the call is recorded on the session's event stream.
7872
- Session-bound calls return `409 session_busy` while a turn runs. When the
9183
+ While a model turn is running, a session-bound call is admitted by its
9184
+ effect: a read-effect call (a declared `effect: "read"`, or an advertised
9185
+ MCP tool whose server annotates it read-only) runs alongside the turn,
9186
+ reading the workspace as the turn has left it, and is recorded under its
9187
+ own per-call `turnId` so trajectories keep it apart from the turn's own
9188
+ calls; a write-effect call — including an undeclared tool, which counts
9189
+ as a write — returns `409 session_busy` until the turn finishes, because
9190
+ a running turn owns the workspace. When the
7873
9191
  session's harness cwd cannot be materialized, a read-effect call runs
7874
9192
  in a scratch workspace instead and the outcome carries
7875
9193
  `scratchWorkspace: true`; a write-effect call fails with
@@ -7879,7 +9197,7 @@ A session can also be addressed by its continuation token: an optional
7879
9197
  `continuationToken` (`<channelId>:<key>`, as `/v1/sessions` lists it;
7880
9198
  mutually exclusive with `sessionId`). A token that maps to a live
7881
9199
  session behaves exactly like passing that session's id — same ownership
7882
- check, same `409 session_busy`, same event recording. A token with no
9200
+ check, same busy semantics, same event recording. A token with no
7883
9201
  session behind it runs the call scratch-bound with the token's channel
7884
9202
  id and continuation key as the call's session identity, so a deployment
7885
9203
  whose tools resolve state from the continuation key can serve it with
@@ -8333,6 +9651,116 @@ sessions skip GitHub writes.
8333
9651
 
8334
9652
  ---
8335
9653
 
9654
+ Source: /docs/templates/agents-md.md
9655
+
9656
+ # Keep AGENTS.md current from last week's work
9657
+
9658
+ This agent runs every Monday at 09:00 UTC. It reads last week's merged
9659
+ pull requests and the Slack channels you list. When a durable rule
9660
+ showed up, `apply_agents` parks the write until a person approves it,
9661
+ then opens a draft follow-up pull request against the default branch.
9662
+ Chores and rules the file already states stay quiet.
9663
+
9664
+ This template is one model turn: read the week, then call
9665
+ `apply_agents`.
9666
+
9667
+ Init once per app. Pass a different repo and channel list each time.
9668
+
9669
+ ## Scaffold
9670
+
9671
+ ```bash
9672
+ npx @cursor/july init ./agents-md --template agents-md
9673
+ ```
9674
+
9675
+ `init` asks which GitHub repo to watch and which Slack channels to
9676
+ read. Pass `--var repos=acme/api --var slack=#eng-api,#api-private`
9677
+ to skip the prompt. Use `--var slack=none` when Slack is out of
9678
+ scope.
9679
+
9680
+ The answers land in `agent/lib/config.ts`. Edit that file when the
9681
+ set changes.
9682
+
9683
+ ## Connect GitHub and Slack
9684
+
9685
+ Connect GitHub in Cursor for that repository (Settings or
9686
+ [cursor.com/dashboard](https://cursor.com/dashboard)). Sign the host
9687
+ in:
9688
+
9689
+ ```bash
9690
+ cd agents-md
9691
+ agent-sdk login
9692
+ ```
9693
+
9694
+ `cursorAccount` mints a short-lived, repo-scoped credential with
9695
+ `contents-write` so the agent can open the follow-up PR. See the
9696
+ [GitHub guide](/docs/guides/github.md).
9697
+
9698
+ Create the Slack app and invite the bot to each listed channel,
9699
+ including private ones:
9700
+
9701
+ ```bash
9702
+ agent-sdk slack create --dir .
9703
+ ```
9704
+
9705
+ See the [Slack guide](/docs/guides/slack.md).
9706
+
9707
+ ## First weekly run
9708
+
9709
+ ```bash
9710
+ agent-sdk dev
9711
+ ```
9712
+
9713
+ Schedules never fire under `dev`. Dispatch the same path production
9714
+ uses:
9715
+
9716
+ ```bash
9717
+ curl -X POST http://127.0.0.1:3000/v1/dev/schedules/weekly
9718
+ ```
9719
+
9720
+ The playground can run `/queue/run` too.
9721
+
9722
+ The host writes `week/prs.md`, `week/slack.md`, `week/window.txt`,
9723
+ and `week/tree/**.txt` snapshots of the current `AGENTS.md` files,
9724
+ then starts a model turn. The
9725
+ model calls `apply_agents` once. A skip finishes. An update parks in
9726
+ the playground until someone approves; then a draft PR opens against
9727
+ the repository default branch. Later updates to the same base stack
9728
+ on `agents-md/<base>`. Pull requests from those branches are ignored
9729
+ so the follow-up cannot feed the next week.
9730
+
9731
+ Chat replies stay in the playground. `apply_agents` records a skip,
9732
+ or parks the write. Approve there or with
9733
+ `POST /v1/session/:id/approvals/:callId`.
9734
+
9735
+ ## What to edit
9736
+
9737
+ - `agent/lib/config.ts`: repo and Slack channels
9738
+ - `agent/instructions.md`: what counts as an update
9739
+ - `evals/agents.eval.ts`: smoke cases
9740
+
9741
+ ## Evals
9742
+
9743
+ Four smoke cases: a lint-only week, a billing rule that parks the
9744
+ write, the same rule already in `AGENTS.md`, and an empty week.
9745
+
9746
+ ```bash
9747
+ agent-sdk eval
9748
+ ```
9749
+
9750
+ Eval sessions skip the GitHub write.
9751
+
9752
+ ## Deploy
9753
+
9754
+ ```bash
9755
+ agent-sdk deploy
9756
+ ```
9757
+
9758
+ Run it from the agent's git checkout. The command infers repository,
9759
+ ref, path, and slug. Production fires the Monday cron. The deployment
9760
+ uses the team's Cursor GitHub connection for the watched repo.
9761
+
9762
+ ---
9763
+
8336
9764
  Source: /docs/templates/code-wiki.md
8337
9765
 
8338
9766
  # Keep a code wiki current after merge
@@ -8939,7 +10367,7 @@ not on `PATH`, use `npx @cursor/july`.
8939
10367
  | What you see | What to do |
8940
10368
  | --- | --- |
8941
10369
  | `409` on a follow-up message | Refresh the `continuationToken` or confirm the session is a chat session. Task sessions do not accept follow-ups. |
8942
- | `409 session_busy` on `call --session` | Wait for the model turn to finish, or omit `--session` for a one-off call. |
10370
+ | `409 session_busy` on `call --session` | The tool is a write (or declares no effect) and a model turn is running. Wait for the turn to finish, omit `--session` for a one-off call, or declare `effect: "read"` on a tool that only reads so it runs alongside turns. |
8943
10371
  | `403` on stream or follow-up | Use the same auth identity that created the session. Off localhost, pass `--bearer-token` and send it on every request. |
8944
10372
  | Works on localhost; blocked through a tunnel or LAN | Default auth allows only direct loopback callers. Share the host with `--bearer-token <secret>` (or authored `bearerAuth`). Use `--allow-anonymous` only on a trusted private network. |
8945
10373
  | A channel route fails to compile with a schema type error | `GET` routes need a Zod `querySchema`. `POST` / `PUT` / `PATCH` need a Zod `bodySchema`. Use `z.object({})` or `z.unknown()` for open shapes. |
@@ -8967,7 +10395,7 @@ not on `PATH`, use `npx @cursor/july`.
8967
10395
  | What you see | What to do |
8968
10396
  | --- | --- |
8969
10397
  | `must be defineConnection({ url, oauth: true })` | The connection file needs `oauth: true`, or you passed the wrong connection name to `agent-sdk mcp oauth`. |
8970
- | Local auth works; hosted calls unauthorized | Run `agent-sdk mcp oauth <name> --store`, confirm names with `agent-sdk secrets list <slug>`, then redeploy. |
10398
+ | Local auth works; hosted calls unauthorized | Finish Connect on the hosted engine, or run `agent-sdk mcp oauth <name> --store`. Confirm names with `agent-sdk secrets list <slug>`, then redeploy. |
8971
10399
  | Model asks for `mcp_auth` or IDE MCP for a connector it already has | Attached MCP is behind meta-tools. Set `advertiseTools: true` for named tools on local turns, or call it from a host tool via `ctx.host.mcp`. |
8972
10400
 
8973
10401
  See [Host MCP OAuth](/docs/guides/mcp-oauth.md) and