@cursor/july 0.1.97 → 0.1.100

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 (337) hide show
  1. package/AGENTS.md +5 -3
  2. package/dist/bin/agent-serve.js +6 -7
  3. package/dist/channels/github/defaults.d.ts.map +1 -1
  4. package/dist/channels/github/defaults.js +28 -3
  5. package/dist/channels/github/github-channel.d.ts.map +1 -1
  6. package/dist/channels/github/github-channel.js +16 -0
  7. package/dist/channels/github/progress.d.ts +7 -0
  8. package/dist/channels/github/progress.d.ts.map +1 -1
  9. package/dist/channels/origin/origin-channel.d.ts +2 -0
  10. package/dist/channels/origin/origin-channel.d.ts.map +1 -1
  11. package/dist/channels/origin/origin-channel.js +37 -4
  12. package/dist/channels/slack/api.d.ts +16 -7
  13. package/dist/channels/slack/api.d.ts.map +1 -1
  14. package/dist/channels/slack/api.js +29 -13
  15. package/dist/channels/slack/constants.d.ts +6 -0
  16. package/dist/channels/slack/constants.d.ts.map +1 -1
  17. package/dist/channels/slack/constants.js +6 -0
  18. package/dist/channels/slack/defaults.d.ts +9 -4
  19. package/dist/channels/slack/defaults.d.ts.map +1 -1
  20. package/dist/channels/slack/defaults.js +221 -122
  21. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  22. package/dist/channels/slack/dispatch.js +3 -3
  23. package/dist/channels/slack/inbound.d.ts +9 -0
  24. package/dist/channels/slack/inbound.d.ts.map +1 -1
  25. package/dist/channels/slack/inbound.js +14 -0
  26. package/dist/channels/slack/index.d.ts +1 -0
  27. package/dist/channels/slack/index.d.ts.map +1 -1
  28. package/dist/channels/slack/index.js +1 -0
  29. package/dist/channels/slack/live-delivery.d.ts +6 -1
  30. package/dist/channels/slack/live-delivery.d.ts.map +1 -1
  31. package/dist/channels/slack/live-delivery.js +110 -20
  32. package/dist/channels/slack/progress-delivery.d.ts +28 -0
  33. package/dist/channels/slack/progress-delivery.d.ts.map +1 -0
  34. package/dist/channels/slack/progress-delivery.js +115 -0
  35. package/dist/channels/slack/reasoning-card.d.ts +38 -0
  36. package/dist/channels/slack/reasoning-card.d.ts.map +1 -0
  37. package/dist/channels/slack/reasoning-card.js +104 -0
  38. package/dist/channels/slack/redact.d.ts +2 -0
  39. package/dist/channels/slack/redact.d.ts.map +1 -0
  40. package/dist/channels/slack/redact.js +5 -0
  41. package/dist/channels/slack/reply-options.d.ts +40 -0
  42. package/dist/channels/slack/reply-options.d.ts.map +1 -0
  43. package/dist/channels/slack/reply-options.js +150 -0
  44. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  45. package/dist/channels/slack/slack-channel.js +8 -4
  46. package/dist/channels/slack/stream.d.ts +22 -10
  47. package/dist/channels/slack/stream.d.ts.map +1 -1
  48. package/dist/channels/slack/stream.js +9 -16
  49. package/dist/channels/slack/tool-cards.d.ts +22 -0
  50. package/dist/channels/slack/tool-cards.d.ts.map +1 -0
  51. package/dist/channels/slack/tool-cards.js +178 -0
  52. package/dist/channels/slack/types.d.ts +154 -7
  53. package/dist/channels/slack/types.d.ts.map +1 -1
  54. package/dist/docs/404.html +2 -2
  55. package/dist/docs/ab.html +6 -6
  56. package/dist/docs/ab.md +1 -1
  57. package/dist/docs/assets/{ab.md.DJo5r4R-.js → ab.md.mlVgqvSk.js} +1 -1
  58. package/dist/docs/assets/{app.B3rWNYE1.js → app.DZ1e0Ycq.js} +1 -1
  59. package/dist/docs/assets/{building-with-agents.md.DI4mEzlt.js → building-with-agents.md.CUSWxlP_.js} +2 -2
  60. package/dist/docs/assets/chunks/@localSearchIndexroot.j70vvPL4.js +1 -0
  61. package/dist/docs/assets/chunks/{VPLocalSearchBox.C1lhqJJR.js → VPLocalSearchBox.l8omwc6D.js} +1 -1
  62. package/dist/docs/assets/chunks/{theme.B40_SXuv.js → theme.JVcD6gel.js} +2 -2
  63. package/dist/docs/assets/{evals.md.lfJoEVc8.js → evals.md.CPzDAwoH.js} +1 -1
  64. package/dist/docs/assets/{guides_github.md.Cnh2mL4a.js → guides_github.md.BtPr9GaP.js} +1 -1
  65. package/dist/docs/assets/{guides_mcp-oauth.md.CN-6YmTJ.js → guides_mcp-oauth.md.Dp6cDP7f.js} +1 -1
  66. package/dist/docs/assets/{guides_opentelemetry.md.bmPmkvJu.js → guides_opentelemetry.md.BVTXDCRg.js} +2 -2
  67. package/dist/docs/assets/{guides_slack.md.VDXQV3ja.js → guides_slack.md.9oHPye9o.js} +19 -3
  68. package/dist/docs/assets/{guides_slack.md.VDXQV3ja.lean.js → guides_slack.md.9oHPye9o.lean.js} +1 -1
  69. package/dist/docs/assets/hillclimbing.md.CpTGTCle.js +4 -0
  70. package/dist/docs/assets/{index.md.CVeRUOeZ.js → index.md.Bb4k8kUm.js} +1 -1
  71. package/dist/docs/assets/{quickstart.md.Nj_LjW_a.js → quickstart.md.DdQOF7Y8.js} +1 -1
  72. package/dist/docs/assets/{reference_cli.md.RyZf5OTE.js → reference_cli.md.DfoeyvL0.js} +4 -4
  73. package/dist/docs/assets/{reference_cli.md.RyZf5OTE.lean.js → reference_cli.md.DfoeyvL0.lean.js} +1 -1
  74. package/dist/docs/assets/{reference_connections.md.DxldvyIB.js → reference_connections.md.BiGoBAk2.js} +1 -1
  75. package/dist/docs/assets/{reference_http-api.md.5zOAbV86.js → reference_http-api.md.BEJx9XVj.js} +1 -1
  76. package/dist/docs/assets/{reference_tools.md.B84gw9Ii.js → reference_tools.md.CvAHsdSp.js} +10 -2
  77. package/dist/docs/assets/{reference_tools.md.B84gw9Ii.lean.js → reference_tools.md.CvAHsdSp.lean.js} +1 -1
  78. package/dist/docs/assets/scaffolding-agents.md.em43xlY1.js +1 -0
  79. package/dist/docs/assets/skills_ab.md.CsFNatVx.js +26 -0
  80. package/dist/docs/assets/skills_ab.md.CsFNatVx.lean.js +1 -0
  81. package/dist/docs/assets/skills_create-agent.md.BVoWPcan.js +8 -0
  82. package/dist/docs/assets/skills_create-agent.md.BVoWPcan.lean.js +1 -0
  83. package/dist/docs/assets/skills_debug.md.CDbPhHfg.js +1 -0
  84. package/dist/docs/assets/skills_debug.md.CDbPhHfg.lean.js +1 -0
  85. package/dist/docs/assets/skills_evals.md.723kpUmA.js +25 -0
  86. package/dist/docs/assets/skills_evals.md.723kpUmA.lean.js +1 -0
  87. package/dist/docs/assets/skills_framework-map.md.BTi817yv.js +1 -0
  88. package/dist/docs/assets/skills_framework-map.md.BTi817yv.lean.js +1 -0
  89. package/dist/docs/assets/skills_github.md.D0JahM8c.js +16 -0
  90. package/dist/docs/assets/skills_github.md.D0JahM8c.lean.js +1 -0
  91. package/dist/docs/assets/skills_hillclimb.md.B_zJerxA.js +7 -0
  92. package/dist/docs/assets/skills_hillclimb.md.B_zJerxA.lean.js +1 -0
  93. package/dist/docs/assets/skills_index.md.DKwIxzGg.js +1 -0
  94. package/dist/docs/assets/skills_index.md.DKwIxzGg.lean.js +1 -0
  95. package/dist/docs/assets/skills_mcp-auth.md.DGvFP3HE.js +18 -0
  96. package/dist/docs/assets/skills_mcp-auth.md.DGvFP3HE.lean.js +1 -0
  97. package/dist/docs/assets/skills_otel.md.CgiZryR3.js +8 -0
  98. package/dist/docs/assets/skills_otel.md.CgiZryR3.lean.js +1 -0
  99. package/dist/docs/assets/skills_setup-slack.md.BBgx8lUz.js +20 -0
  100. package/dist/docs/assets/skills_setup-slack.md.BBgx8lUz.lean.js +1 -0
  101. package/dist/docs/assets/{troubleshooting.md.DCiPBhYs.js → troubleshooting.md.Cus_YZga.js} +1 -1
  102. package/dist/docs/building-with-agents.html +6 -6
  103. package/dist/docs/building-with-agents.md +14 -11
  104. package/dist/docs/concepts.html +4 -4
  105. package/dist/docs/deployment.html +4 -4
  106. package/dist/docs/design/runtime-abstraction.md +1757 -0
  107. package/dist/docs/evals.html +6 -6
  108. package/dist/docs/evals.md +1 -1
  109. package/dist/docs/guides/agent-to-agent.html +4 -4
  110. package/dist/docs/guides/cloud-runtime.html +4 -4
  111. package/dist/docs/guides/convert-automation.html +4 -4
  112. package/dist/docs/guides/github.html +5 -5
  113. package/dist/docs/guides/github.md +1 -1
  114. package/dist/docs/guides/human-in-the-loop.html +4 -4
  115. package/dist/docs/guides/mcp-oauth.html +5 -5
  116. package/dist/docs/guides/mcp-oauth.md +1 -1
  117. package/dist/docs/guides/opentelemetry.html +7 -7
  118. package/dist/docs/guides/opentelemetry.md +2 -2
  119. package/dist/docs/guides/slack.html +22 -6
  120. package/dist/docs/guides/slack.md +80 -1
  121. package/dist/docs/guides/webhooks.html +5 -5
  122. package/dist/docs/hashmap.json +1 -1
  123. package/dist/docs/hillclimbing.html +6 -6
  124. package/dist/docs/hillclimbing.md +5 -5
  125. package/dist/docs/index.html +6 -6
  126. package/dist/docs/index.md +1 -1
  127. package/dist/docs/llms-full.txt +2830 -37
  128. package/dist/docs/llms.txt +15 -0
  129. package/dist/docs/quickstart.html +5 -5
  130. package/dist/docs/quickstart.md +1 -1
  131. package/dist/docs/reference/agent-config.html +4 -4
  132. package/dist/docs/reference/artifacts.html +4 -4
  133. package/dist/docs/reference/channels.html +4 -4
  134. package/dist/docs/reference/cli.html +7 -7
  135. package/dist/docs/reference/cli.md +1 -7
  136. package/dist/docs/reference/connections.html +6 -6
  137. package/dist/docs/reference/connections.md +1 -1
  138. package/dist/docs/reference/hooks.html +4 -4
  139. package/dist/docs/reference/http-api.html +6 -6
  140. package/dist/docs/reference/http-api.md +1 -0
  141. package/dist/docs/reference/instructions.html +4 -4
  142. package/dist/docs/reference/playground.html +4 -4
  143. package/dist/docs/reference/project-layout.html +4 -4
  144. package/dist/docs/reference/prompt.html +4 -4
  145. package/dist/docs/reference/schedules.html +4 -4
  146. package/dist/docs/reference/sessions.html +4 -4
  147. package/dist/docs/reference/skills.html +4 -4
  148. package/dist/docs/reference/subagents.html +4 -4
  149. package/dist/docs/reference/tools.html +14 -6
  150. package/dist/docs/reference/tools.md +25 -0
  151. package/dist/docs/scaffolding-agents.html +5 -5
  152. package/dist/docs/scaffolding-agents.md +3 -3
  153. package/dist/docs/skills/ab.html +52 -0
  154. package/dist/docs/skills/ab.md +50 -0
  155. package/dist/docs/skills/create-agent.html +34 -0
  156. package/dist/docs/skills/create-agent.md +160 -0
  157. package/dist/docs/skills/debug.html +27 -0
  158. package/dist/docs/skills/debug.md +36 -0
  159. package/dist/docs/skills/evals.html +51 -0
  160. package/dist/docs/skills/evals.md +99 -0
  161. package/dist/docs/skills/framework-map.html +27 -0
  162. package/dist/docs/skills/framework-map.md +95 -0
  163. package/dist/docs/skills/github.html +42 -0
  164. package/dist/docs/skills/github.md +93 -0
  165. package/dist/docs/skills/hillclimb.html +33 -0
  166. package/dist/docs/skills/hillclimb.md +55 -0
  167. package/dist/docs/skills/index.html +27 -0
  168. package/dist/docs/skills/index.md +21 -0
  169. package/dist/docs/skills/mcp-auth.html +44 -0
  170. package/dist/docs/skills/mcp-auth.md +76 -0
  171. package/dist/docs/skills/otel.html +34 -0
  172. package/dist/docs/skills/otel.md +48 -0
  173. package/dist/docs/skills/setup-slack.html +46 -0
  174. package/dist/docs/skills/setup-slack.md +141 -0
  175. package/dist/docs/storage.html +4 -4
  176. package/dist/docs/templates/agentic-owners.html +4 -4
  177. package/dist/docs/templates/agents-md.html +4 -4
  178. package/dist/docs/templates/code-wiki.html +4 -4
  179. package/dist/docs/templates/demo.html +4 -4
  180. package/dist/docs/templates/pr-autofixer.html +4 -4
  181. package/dist/docs/templates/security-help.html +4 -4
  182. package/dist/docs/templates/security-reviewer.html +4 -4
  183. package/dist/docs/templates/triage.html +4 -4
  184. package/dist/docs/troubleshooting.html +5 -5
  185. package/dist/docs/troubleshooting.md +1 -1
  186. package/dist/files-backends/cursor-hosted.d.ts +9 -2
  187. package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
  188. package/dist/files-backends/cursor-hosted.js +9 -11
  189. package/dist/index.d.ts +1 -0
  190. package/dist/index.d.ts.map +1 -1
  191. package/dist/index.js +1 -0
  192. package/dist/internal/as-of.d.ts +19 -0
  193. package/dist/internal/as-of.d.ts.map +1 -0
  194. package/dist/internal/as-of.js +82 -0
  195. package/dist/internal/cli-deploy.d.ts +2 -1
  196. package/dist/internal/cli-deploy.d.ts.map +1 -1
  197. package/dist/internal/cli-deploy.js +102 -7
  198. package/dist/internal/conversation-mirror.d.ts.map +1 -1
  199. package/dist/internal/conversation-mirror.js +3 -4
  200. package/dist/internal/cursor/hosted-store-secrets.d.ts +15 -0
  201. package/dist/internal/cursor/hosted-store-secrets.d.ts.map +1 -0
  202. package/dist/internal/cursor/hosted-store-secrets.js +48 -0
  203. package/dist/internal/cursor-event-relay.d.ts.map +1 -1
  204. package/dist/internal/cursor-event-relay.js +5 -6
  205. package/dist/internal/deploy-client.d.ts +6 -6
  206. package/dist/internal/deploy-client.js +7 -7
  207. package/dist/internal/discovery.d.ts.map +1 -1
  208. package/dist/internal/discovery.js +11 -1
  209. package/dist/internal/framework-file-storage.d.ts +5 -4
  210. package/dist/internal/framework-file-storage.d.ts.map +1 -1
  211. package/dist/internal/framework-file-storage.js +6 -4
  212. package/dist/internal/framework-storage-selection.d.ts +13 -11
  213. package/dist/internal/framework-storage-selection.d.ts.map +1 -1
  214. package/dist/internal/framework-storage-selection.js +28 -23
  215. package/dist/internal/hosted-admission-adapter.d.ts +28 -0
  216. package/dist/internal/hosted-admission-adapter.d.ts.map +1 -0
  217. package/dist/internal/hosted-admission-adapter.js +7 -0
  218. package/dist/internal/hosted-delivery-protocol.d.ts +6 -1
  219. package/dist/internal/hosted-delivery-protocol.d.ts.map +1 -1
  220. package/dist/internal/hosted-delivery-protocol.js +1 -1
  221. package/dist/internal/hosted-delivery.d.ts +15 -1
  222. package/dist/internal/hosted-delivery.d.ts.map +1 -1
  223. package/dist/internal/hosted-delivery.js +47 -21
  224. package/dist/internal/hosted-execution-diag.d.ts +11 -0
  225. package/dist/internal/hosted-execution-diag.d.ts.map +1 -1
  226. package/dist/internal/hosted-execution-diag.js +39 -2
  227. package/dist/internal/http-channel.d.ts.map +1 -1
  228. package/dist/internal/http-channel.js +4 -1
  229. package/dist/internal/server.d.ts +3 -0
  230. package/dist/internal/server.d.ts.map +1 -1
  231. package/dist/internal/server.js +82 -19
  232. package/dist/internal/session-engine.d.ts.map +1 -1
  233. package/dist/internal/session-engine.js +24 -4
  234. package/dist/internal/session-run-log.d.ts +120 -0
  235. package/dist/internal/session-run-log.d.ts.map +1 -0
  236. package/dist/internal/session-run-log.js +358 -0
  237. package/dist/internal/tool-policy.d.ts +20 -8
  238. package/dist/internal/tool-policy.d.ts.map +1 -1
  239. package/dist/internal/tool-policy.js +11 -0
  240. package/dist/playground/assets/index-BszoQDc6.css +1 -0
  241. package/dist/playground/assets/index-DVs98vPL.js +69 -0
  242. package/dist/playground/index.html +2 -2
  243. package/dist/storage-backends/cursor-hosted-v2.d.ts +83 -0
  244. package/dist/storage-backends/cursor-hosted-v2.d.ts.map +1 -0
  245. package/dist/storage-backends/cursor-hosted-v2.js +164 -0
  246. package/dist/storage-backends/cursor-hosted.d.ts +6 -0
  247. package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
  248. package/dist/storage-backends/cursor-hosted.js +6 -1
  249. package/dist/storage-protocol.d.ts +8 -0
  250. package/dist/storage-protocol.d.ts.map +1 -1
  251. package/dist/storage-protocol.js +8 -0
  252. package/dist/tools.d.ts +12 -2
  253. package/dist/tools.d.ts.map +1 -1
  254. package/dist/types.d.ts +30 -0
  255. package/dist/types.d.ts.map +1 -1
  256. package/docs/README.md +1 -1
  257. package/docs/ab.md +1 -1
  258. package/docs/building-with-agents.md +14 -11
  259. package/docs/design/runtime-abstraction.md +1757 -0
  260. package/docs/evals.md +1 -1
  261. package/docs/guides/github.md +1 -1
  262. package/docs/guides/mcp-oauth.md +1 -1
  263. package/docs/guides/opentelemetry.md +2 -2
  264. package/docs/guides/slack.md +80 -1
  265. package/docs/hillclimbing.md +5 -5
  266. package/docs/quickstart.md +1 -1
  267. package/docs/reference/cli.md +1 -7
  268. package/docs/reference/connections.md +1 -1
  269. package/docs/reference/http-api.md +1 -0
  270. package/docs/reference/tools.md +25 -0
  271. package/docs/scaffolding-agents.md +3 -3
  272. package/docs/skills/index.md +26 -0
  273. package/docs/troubleshooting.md +1 -1
  274. package/package.json +1 -1
  275. package/src/bin/agent-serve.ts +6 -7
  276. package/src/channels/github/defaults.ts +36 -3
  277. package/src/channels/github/github-channel.ts +17 -0
  278. package/src/channels/github/progress.ts +7 -0
  279. package/src/channels/origin/origin-channel.ts +74 -10
  280. package/src/channels/slack/api.ts +35 -17
  281. package/src/channels/slack/constants.ts +6 -0
  282. package/src/channels/slack/defaults.ts +253 -144
  283. package/src/channels/slack/dispatch.ts +10 -1
  284. package/src/channels/slack/inbound.ts +17 -0
  285. package/src/channels/slack/index.ts +1 -0
  286. package/src/channels/slack/live-delivery.ts +135 -22
  287. package/src/channels/slack/progress-delivery.ts +133 -0
  288. package/src/channels/slack/reasoning-card.ts +140 -0
  289. package/src/channels/slack/redact.ts +6 -0
  290. package/src/channels/slack/reply-options.ts +229 -0
  291. package/src/channels/slack/slack-channel.ts +7 -2
  292. package/src/channels/slack/stream.ts +31 -24
  293. package/src/channels/slack/tool-cards.ts +221 -0
  294. package/src/channels/slack/types.ts +168 -7
  295. package/src/files-backends/cursor-hosted.ts +18 -7
  296. package/src/index.ts +4 -0
  297. package/src/internal/as-of.ts +107 -0
  298. package/src/internal/cli-deploy.ts +130 -10
  299. package/src/internal/conversation-mirror.ts +3 -8
  300. package/src/internal/cursor/hosted-store-secrets.ts +72 -0
  301. package/src/internal/cursor-event-relay.ts +5 -6
  302. package/src/internal/deploy-client.ts +10 -10
  303. package/src/internal/discovery.ts +9 -1
  304. package/src/internal/framework-file-storage.ts +6 -4
  305. package/src/internal/framework-storage-selection.ts +28 -25
  306. package/src/internal/hosted-admission-adapter.ts +42 -0
  307. package/src/internal/hosted-delivery-protocol.ts +7 -2
  308. package/src/internal/hosted-delivery.ts +88 -36
  309. package/src/internal/hosted-execution-diag.ts +45 -0
  310. package/src/internal/http-channel.ts +8 -0
  311. package/src/internal/server.ts +100 -2
  312. package/src/internal/session-engine.ts +32 -1
  313. package/src/internal/session-run-log.ts +579 -0
  314. package/src/internal/tool-policy.ts +31 -9
  315. package/src/storage-backends/cursor-hosted-v2.ts +219 -0
  316. package/src/storage-backends/cursor-hosted.ts +6 -1
  317. package/src/storage-protocol.ts +10 -0
  318. package/src/tools.ts +12 -2
  319. package/src/types.ts +30 -0
  320. package/dist/docs/assets/chunks/@localSearchIndexroot.BU9nHdnQ.js +0 -1
  321. package/dist/docs/assets/hillclimbing.md.DhESf3OO.js +0 -4
  322. package/dist/docs/assets/scaffolding-agents.md.D7UUkWw0.js +0 -1
  323. package/dist/playground/assets/index-DrkI6y5O.js +0 -88
  324. package/dist/playground/assets/index-DzNGwm7q.css +0 -1
  325. /package/dist/docs/assets/{ab.md.DJo5r4R-.lean.js → ab.md.mlVgqvSk.lean.js} +0 -0
  326. /package/dist/docs/assets/{building-with-agents.md.DI4mEzlt.lean.js → building-with-agents.md.CUSWxlP_.lean.js} +0 -0
  327. /package/dist/docs/assets/{evals.md.lfJoEVc8.lean.js → evals.md.CPzDAwoH.lean.js} +0 -0
  328. /package/dist/docs/assets/{guides_github.md.Cnh2mL4a.lean.js → guides_github.md.BtPr9GaP.lean.js} +0 -0
  329. /package/dist/docs/assets/{guides_mcp-oauth.md.CN-6YmTJ.lean.js → guides_mcp-oauth.md.Dp6cDP7f.lean.js} +0 -0
  330. /package/dist/docs/assets/{guides_opentelemetry.md.bmPmkvJu.lean.js → guides_opentelemetry.md.BVTXDCRg.lean.js} +0 -0
  331. /package/dist/docs/assets/{hillclimbing.md.DhESf3OO.lean.js → hillclimbing.md.CpTGTCle.lean.js} +0 -0
  332. /package/dist/docs/assets/{index.md.CVeRUOeZ.lean.js → index.md.Bb4k8kUm.lean.js} +0 -0
  333. /package/dist/docs/assets/{quickstart.md.Nj_LjW_a.lean.js → quickstart.md.DdQOF7Y8.lean.js} +0 -0
  334. /package/dist/docs/assets/{reference_connections.md.DxldvyIB.lean.js → reference_connections.md.BiGoBAk2.lean.js} +0 -0
  335. /package/dist/docs/assets/{reference_http-api.md.5zOAbV86.lean.js → reference_http-api.md.BEJx9XVj.lean.js} +0 -0
  336. /package/dist/docs/assets/{scaffolding-agents.md.D7UUkWw0.lean.js → scaffolding-agents.md.em43xlY1.lean.js} +0 -0
  337. /package/dist/docs/assets/{troubleshooting.md.DCiPBhYs.lean.js → troubleshooting.md.Cus_YZga.lean.js} +0 -0
@@ -336,7 +336,7 @@ Continue with these pages:
336
336
  `ab.assigned` event and durable log
337
337
  - [Playground](/docs/reference/playground.md): the A/Bs tab
338
338
  - [HTTP API](/docs/reference/http-api.md): `GET /v1/abs`
339
- - [Live A/B metrics skill](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/ab/SKILL.md): have a coding agent
339
+ - [Live A/B metrics skill](/docs/skills/ab.md): have a coding agent
340
340
  wire an experiment
341
341
 
342
342
  ---
@@ -367,7 +367,7 @@ and verify the result without reading terminal prose.
367
367
  ## How do I create an agent with the built-in skill?
368
368
 
369
369
  Have the coding agent read
370
- [`skills/create-agent/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/create-agent/SKILL.md) and
370
+ [`skills/create-agent/SKILL.md`](/docs/skills/create-agent.md) and
371
371
  follow it.
372
372
 
373
373
  The skill asks about your agent's purpose, runtime, model, channels, MCP
@@ -386,18 +386,20 @@ the first end-to-end turn works.
386
386
 
387
387
  ## Which built-in skill should I use?
388
388
 
389
- The package ships task-specific guides under [`skills/`](https://github.com/cursor/cursor/tree/main/packages/agent-serve/skills/):
389
+ The package ships task-specific guides under [`skills/`](/docs/skills/index.md):
390
390
 
391
391
  | What you want to do | Skill |
392
392
  | --- | --- |
393
- | Understand the project layout and runtimes | [`framework-map`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/framework-map/SKILL.md) |
394
- | Create and verify a new agent | [`create-agent`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/create-agent/SKILL.md) |
395
- | Write fixtures and regression checks | [`evals`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/evals/SKILL.md) |
396
- | Live A/B metrics on traffic (`defineAB`) | [`ab`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/ab/SKILL.md) |
397
- | Improve an agent against fixed inputs | [`hillclimb`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/hillclimb/SKILL.md) |
398
- | Add GitHub webhooks and replay events | [`github`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/github/SKILL.md) |
399
- | Connect an agent to Slack | [`setup-slack`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/setup-slack/SKILL.md) |
400
- | Diagnose a local run | [`debug`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/debug/SKILL.md) |
393
+ | Understand the project layout and runtimes | [`framework-map`](/docs/skills/framework-map.md) |
394
+ | Create and verify a new agent | [`create-agent`](/docs/skills/create-agent.md) |
395
+ | Write fixtures and regression checks | [`evals`](/docs/skills/evals.md) |
396
+ | Live A/B metrics on traffic (`defineAB`) | [`ab`](/docs/skills/ab.md) |
397
+ | Export OpenTelemetry traces | [`otel`](/docs/skills/otel.md) |
398
+ | Improve an agent against fixed inputs | [`hillclimb`](/docs/skills/hillclimb.md) |
399
+ | Add GitHub webhooks and replay events | [`github`](/docs/skills/github.md) |
400
+ | Connect an agent to Slack | [`setup-slack`](/docs/skills/setup-slack.md) |
401
+ | Authorize host MCP OAuth | [`mcp-auth`](/docs/skills/mcp-auth.md) |
402
+ | Diagnose a local run | [`debug`](/docs/skills/debug.md) |
401
403
 
402
404
  Point your coding agent at the matching `SKILL.md`. The guide contains
403
405
  the workflow, commands, and common mistakes for that task.
@@ -450,7 +452,7 @@ smoke turn passes, give the hillclimb skill:
450
452
  evals that must stay unchanged
451
453
 
452
454
  Have the coding agent read
453
- [`skills/hillclimb/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/hillclimb/SKILL.md). It measures
455
+ [`skills/hillclimb/SKILL.md`](/docs/skills/hillclimb.md). It measures
454
456
  the current run, proposes one change, remeasures the same fixtures, and
455
457
  adds an eval for each kept improvement.
456
458
 
@@ -458,6 +460,7 @@ adds an eval for each kept improvement.
458
460
 
459
461
  - [Create your first agent](/docs/quickstart.md)
460
462
  - [Scaffold an agent with Cursor](/docs/scaffolding-agents.md)
463
+ - [Coding-agent skills](/docs/skills/index.md)
461
464
  - [Evals](/docs/evals.md)
462
465
  - [Hillclimbing](/docs/hillclimbing.md)
463
466
  - [CLI reference](/docs/reference/cli.md)
@@ -1088,6 +1091,1768 @@ Continue with these pages:
1088
1091
 
1089
1092
  ---
1090
1093
 
1094
+ Source: /docs/design/runtime-abstraction.md
1095
+
1096
+ # Replacing `runtime: "local" | "cloud"` with a code interface
1097
+
1098
+ > **Status:** design proposal for review. No product code in this PR.
1099
+ > Implementation is split across later PRs (see §8). Do not start those
1100
+ > until this document has been reviewed.
1101
+ > **Audience:** Agent SDK and cursor-sdk maintainers; change-monitor as the
1102
+ > first consumer that is already faking a third runtime.
1103
+ > **Companions:** [cloud-runtime.md](/docs/guides/cloud-runtime.md) (today's
1104
+ > user-facing contract),
1105
+ > [factory/change-monitor/docs/tools.md](https://github.com/cursor/cursor/blob/main/factory/change-monitor/docs/tools.md)
1106
+ > (why change-monitor is pinned to local),
1107
+ > [factory/change-monitor/docs/computer-use.md](https://github.com/cursor/cursor/blob/main/factory/change-monitor/docs/computer-use.md)
1108
+ > (why a cloud VM as the *workspace* is not `runtime: "cloud"`).
1109
+ > **Checked against:** `@cursor/july` (`packages/agent-serve`) and
1110
+ > `@cursor/sdk` (`packages/cursor-sdk`) on `origin/main` as of 2026-08-31.
1111
+
1112
+ ## Goals
1113
+
1114
+ 1. **Stay compatible with agents that already ship.**
1115
+ The current `defineAgent` / `AgentOptions` shape keeps
1116
+ working: string `runtime`, sibling `local`, sibling
1117
+ `cloud`. Existing files do not need a rewrite.
1118
+ Constructors are additive (`localRuntime(opts)` takes
1119
+ today's `AgentLocalOptions`). The old fields are
1120
+ **deprecated and still supported** — JSDoc `@deprecated`
1121
+ plus published-docs notes — not removed in Phases 1–4.
1122
+
1123
+ 2. **Default to `localRuntime()`.**
1124
+ That is today's default and the one developers mean when
1125
+ they omit `runtime`: local loop, host workspace, full
1126
+ harness toolset. `defineAgent({})` and
1127
+ `defineAgent({ runtime: "local" })` both become
1128
+ `localRuntime()`.
1129
+
1130
+ 3. **Refuse incoherent pairs at discovery.**
1131
+ The string let authors write `runtime: "cloud"` next to
1132
+ `local: { sandbox, cwd, … }`, or `tools` on a cloud
1133
+ agent, and only some of those failed. `Runtime` is a
1134
+ closed union. `workspace` exists only on the local arm,
1135
+ so `cloudRuntime` cannot take one. `virtualRuntime`
1136
+ refuses `send({ runtime: cloudRuntime(...) })`.
1137
+ Combinations an arm cannot honor are errors, not
1138
+ ignored fields.
1139
+
1140
+ 4. **Replace the enum with an extensible interface.**
1141
+ External users of `@cursor/july` / `@cursor/sdk` should
1142
+ be able to bring their own workspace (a `FileSystem`, a test
1143
+ map, a remote machine mount) without waiting for a new
1144
+ string value. `Workspace` is that interface.
1145
+ Do not grow `"local" | "cloud" | "virtual" | …`.
1146
+
1147
+ The Agent SDK exposes execution as a string on `defineAgent`:
1148
+
1149
+ ```ts
1150
+ runtime?: "local" | "cloud" | "grokbot";
1151
+ ```
1152
+
1153
+ `"local"` and `"cloud"` look like two places the same agent can run.
1154
+ They are not. The string selects a **loop backend**, a **workspace**, and
1155
+ an **artifact-delivery path** at once, and it already has a third value
1156
+ (`"grokbot"`) that fits neither name. Change-monitor then takes
1157
+ `"local"` and hollows it out: native shell off, native `read`/`grep`/
1158
+ `glob`/`ls` replaced by server tools over a virtual filesystem. That is
1159
+ a virtual workspace on the local loop, forced to claim it is local
1160
+ because there is no other legal value.
1161
+
1162
+ This document replaces the string with a closed `Runtime` union.
1163
+ The local arm carries a **workspace**. Cloud and grokbot do not.
1164
+ The workspace is one object whose optional methods (`read`,
1165
+ `ls`, `shell`, …) are the capabilities. Presence enables the
1166
+ matching tool. All methods share one path namespace.
1167
+ `workspace` exists only on the local arm, so `cloudRuntime`
1168
+ cannot take one.
1169
+
1170
+ The first implementation cut is native redirection in Cursor SDK
1171
+ (`AgentOptions.local.workspace` rebound through local-exec). Agent
1172
+ SDK then authors that workspace as `virtualRuntime(fs)`.
1173
+
1174
+ Lookalike server tools are not good enough. Change-monitor's
1175
+ `read` is an Agent SDK server tool, which the local harness
1176
+ exposes as MCP (`custom-user-tools`). The model gets
1177
+ `GetMcpTools` / `CallMcpTool`, not a builtin `read`. That is
1178
+ the bug. Copying native schemas does not fix the tool the
1179
+ model is offered. The SDK change exists so the model calls
1180
+ harness `read` against the injected workspace.
1181
+
1182
+ ---
1183
+
1184
+ ## 1. Chosen abstraction
1185
+
1186
+ ```ts
1187
+ /**
1188
+ * Native tool args, open for fields the harness adds later.
1189
+ * Known keys match the model-facing input schema. Extra keys
1190
+ * are forwarded, not stripped.
1191
+ */
1192
+ type NativeArgs<T> = T & { [key: string]: unknown };
1193
+
1194
+ /**
1195
+ * Path-namespace verbs. Method names match `@cursor/sdk`
1196
+ * `ToolName`s (`read`, `semSearch`, `readLints`). The model
1197
+ * sees the harness names (`Read`, `SemanticSearch`,
1198
+ * `ReadLints`). `write` is the exception: the model tool is
1199
+ * `Write`, but proto folded it into `edit_tool_call` — there
1200
+ * is no public `tools: ["write"]`. Args and results are those
1201
+ * tools' input/output schemas. `@cursor/sdk` owns this type.
1202
+ */
1203
+ interface FileSystem {
1204
+ ls?(args: NativeArgs<LsArgs>): Promise<LsResult>;
1205
+ glob?(args: NativeArgs<GlobArgs>): Promise<GlobResult>;
1206
+ read?(args: NativeArgs<ReadArgs>): Promise<ReadResult>;
1207
+ grep?(args: NativeArgs<GrepArgs>): Promise<GrepResult>;
1208
+ /** Native Write tool: create or overwrite a whole file. */
1209
+ write?(args: NativeArgs<WriteArgs>): Promise<WriteResult>;
1210
+ /** Native Edit tool: str-replace / multi-replace / apply-patch. */
1211
+ edit?(args: NativeArgs<EditArgs>): Promise<EditResult>;
1212
+ delete?(args: NativeArgs<DeleteArgs>): Promise<DeleteResult>;
1213
+ semSearch?(args: NativeArgs<SemSearchArgs>): Promise<SemSearchResult>;
1214
+ readLints?(args: NativeArgs<ReadLintsArgs>): Promise<ReadLintsResult>;
1215
+ }
1216
+
1217
+ /**
1218
+ * One namespace. File verbs and shell are optional methods —
1219
+ * presence is the capability. They must share `root()`.
1220
+ * Do not implement `read` against Origin and `shell` against
1221
+ * host bash.
1222
+ */
1223
+ interface Workspace extends FileSystem {
1224
+ shell?(args: NativeArgs<ShellArgs>): Promise<ShellResult>;
1225
+ root(): string;
1226
+ brief?(ctx: WorkspaceBriefContext): string;
1227
+ }
1228
+
1229
+ /**
1230
+ * Closed union. `kind` is internal dispatch. Authors call
1231
+ * constructors. Each constructor returns its arm, not
1232
+ * `Runtime`, so `send({ runtime })` can require
1233
+ * `CloudRuntime | LocalRuntime` and exclude grokbot.
1234
+ * `workspace` exists only on the local arm.
1235
+ */
1236
+ type LocalRuntime = { kind: "local"; workspace: Workspace };
1237
+ type CloudRuntime = { kind: "cloud"; cloud: AgentCloudOptions };
1238
+ type GrokbotRuntime = { kind: "grokbot" };
1239
+ type Runtime = LocalRuntime | CloudRuntime | GrokbotRuntime;
1240
+
1241
+ function localRuntime(opts?: AgentLocalOptions): LocalRuntime {
1242
+ return { kind: "local", workspace: localWorkspace(opts) };
1243
+ }
1244
+
1245
+ /**
1246
+ * Host-local with Cursor's local sandbox on. Same
1247
+ * `kind: "local"` family as `localRuntime` / `virtualRuntime`.
1248
+ * `cwd` / `workspaceDir` still configure the host tree.
1249
+ * The workspace omits `shell`: this object cannot honor
1250
+ * `sandboxOptions`. Host+sandbox turns keep the SDK's
1251
+ * `LocalShellExecutor` and do not inject the workspace.
1252
+ */
1253
+ function sandboxRuntime(
1254
+ opts?: Omit<AgentLocalOptions, "sandbox">
1255
+ ): LocalRuntime {
1256
+ return localRuntime({ ...opts, sandbox: true });
1257
+ }
1258
+
1259
+ /**
1260
+ * Local arm + a virtual workspace. Default is empty: `root()`
1261
+ * is `/`, no file/shell methods, no materialization, no
1262
+ * skills mount. Pass a `FileSystem` (or an async per-session
1263
+ * factory) to compose mounts. Authors pass a `FileSystem`,
1264
+ * not a `Workspace` — the lift is not a knob.
1265
+ */
1266
+ function virtualRuntime(
1267
+ fs?: FileSystem | ((ctx: SessionWorkspaceContext) => Promise<FileSystem>)
1268
+ ): LocalRuntime {
1269
+ return { kind: "local", workspace: virtualWorkspace(fs) };
1270
+ }
1271
+
1272
+ function cloudRuntime(cloud: AgentCloudOptions): CloudRuntime {
1273
+ return { kind: "cloud", cloud };
1274
+ }
1275
+
1276
+ function grokbotRuntime(): GrokbotRuntime {
1277
+ return { kind: "grokbot" };
1278
+ }
1279
+ ```
1280
+
1281
+ `cwd` / `workspaceDir` are arguments to `localRuntime` and
1282
+ `sandboxRuntime`, same as `repos` on `cloudRuntime`. They
1283
+ configure the host tree `localWorkspace(opts)` wraps. They
1284
+ are not a `Workspace` and they are not on `virtualRuntime`.
1285
+ Prefer `sandboxRuntime()` when the harness should run
1286
+ sandboxed. `localRuntime({ sandbox: true })` and sibling
1287
+ `local.sandbox` stay as equivalent deprecated sugar.
1288
+ The injected object on the Cursor SDK is
1289
+ `AgentOptions.local.workspace`. Cursor SDK's existing
1290
+ `WorkspaceRuntime` is the session factory, not `Workspace`.
1291
+ Host+sandbox turns do not inject: a Workspace that claimed
1292
+ `shell` would replace `LocalShellExecutor`.
1293
+
1294
+ Authoring:
1295
+
1296
+ ```ts
1297
+ export default defineAgent({
1298
+ runtime: localRuntime(),
1299
+ });
1300
+
1301
+ export default defineAgent({
1302
+ runtime: sandboxRuntime({ workspaceDir: "/repo" }),
1303
+ });
1304
+
1305
+ export default defineAgent({
1306
+ runtime: virtualRuntime(async (ctx) =>
1307
+ unionFs({
1308
+ "/repo": originRepoFs(bindingFor(ctx)),
1309
+ "/host": hostFilesFs(),
1310
+ "/agent/skills": skillsFs(),
1311
+ })
1312
+ ),
1313
+ });
1314
+
1315
+ export default defineAgent({
1316
+ runtime: cloudRuntime({
1317
+ repos: [{ url: "https://github.com/org/repo", startingRef: "main" }],
1318
+ }),
1319
+ });
1320
+ ```
1321
+
1322
+ `localRuntime` / `sandboxRuntime` / `virtualRuntime` /
1323
+ `cloudRuntime` / `grokbotRuntime` are constructors over that
1324
+ union. They are not five implementations of one `fs()` +
1325
+ `shell()` pair. `sandboxRuntime` stays on the local arm.
1326
+
1327
+ ### Backward compatibility
1328
+
1329
+ Today's fields stay on `defineAgent` and on Cursor
1330
+ `AgentOptions`. Constructors sit beside them. Nothing that
1331
+ compiles today is broken by Phase 2.
1332
+
1333
+ ```ts
1334
+ // Still valid. Deprecated. Same agent as localRuntime(opts).
1335
+ defineAgent({
1336
+ runtime: "local",
1337
+ local: { workspaceDir: "/repo", sandbox: true },
1338
+ });
1339
+
1340
+ // Preferred.
1341
+ defineAgent({
1342
+ runtime: sandboxRuntime({ workspaceDir: "/repo" }),
1343
+ });
1344
+
1345
+ // Still valid. Deprecated. Same agent as cloudRuntime(opts).
1346
+ defineAgent({
1347
+ runtime: "cloud",
1348
+ cloud: { repos: [{ url: "https://github.com/org/repo", startingRef: "main" }] },
1349
+ });
1350
+
1351
+ // Preferred.
1352
+ defineAgent({
1353
+ runtime: cloudRuntime({
1354
+ repos: [{ url: "https://github.com/org/repo", startingRef: "main" }],
1355
+ }),
1356
+ });
1357
+ ```
1358
+
1359
+ | Today's field | Still does | Preferred |
1360
+ |---|---|---|
1361
+ | omit `runtime` / `runtime: "local"` | `localRuntime(agent.local)` | `runtime: localRuntime(opts)` |
1362
+ | `runtime: "cloud"` | `cloudRuntime(agent.cloud)` | `runtime: cloudRuntime(opts)` |
1363
+ | `runtime: "grokbot"` | `grokbotRuntime()` | `runtime: grokbotRuntime()` |
1364
+ | `local: { cwd, workspaceDir, sandbox }` | Host options when the string / omitted runtime is local | `localRuntime(opts)` or `sandboxRuntime(opts)`. Same `AgentLocalOptions`. |
1365
+ | `cloud: { repos, … }` | Payload for `runtime: "cloud"` | `cloudRuntime(opts)` |
1366
+ | `send({ cloud })` | This session becomes cloud; options merge over agent `cloud` | `send({ runtime: cloudRuntime(opts) })`. No merge. |
1367
+ | `send({ workspaceDir })` | Harness cwd for this local session | `send({ runtime: localRuntime({ workspaceDir }) })` |
1368
+
1369
+ Runtime payloads live on constructors: `AgentLocalOptions`
1370
+ on `localRuntime`, `AgentCloudOptions` on `cloudRuntime`.
1371
+ These stay on `defineAgent` / `send` / `Agent.create`, not
1372
+ on `Runtime`: `model`, `tools`, `builtinTools`, `hosting`,
1373
+ `architecture`, `workspaceFiles`.
1374
+
1375
+ `virtualRuntime` does not take `AgentLocalOptions`. There is
1376
+ no host cwd to point. `local.workspaceDir` / `sandbox` +
1377
+ `virtualRuntime` is still a discovery error.
1378
+
1379
+ Cursor SDK stays on `AgentOptions.local` / `.cloud` for
1380
+ Phases 1–4. Presence of `cloud` still dispatches. Phase 1
1381
+ **adds** `local.workspace`. july `localRuntime(opts)` writes
1382
+ cwd / sandbox / workspaceDir into that bag. Constructors on
1383
+ `Agent.create` are a later SDK deprecation, after july has
1384
+ shipped the same types. Do not leave “`cloud` means cloud”
1385
+ as the forever SDK contract — just do not do that cut in
1386
+ Phase 1.
1387
+
1388
+ Deprecation is documentation and JSDoc (`@deprecated` with
1389
+ the constructor equivalent). Do not delete the string or
1390
+ the sibling fields in Phases 1–4. Removal is a later
1391
+ window after Phase 4 has taught constructors. Subagents
1392
+ keep today's behavior, stored internally as `Runtime`
1393
+ (`G12`).
1394
+
1395
+ If a constructor and a sibling disagree (`localRuntime(a)`
1396
+ plus `local: b`; `cloudRuntime(a)` plus `cloud: b`;
1397
+ `virtualRuntime` plus `local.workspaceDir`), fail closed.
1398
+ If only the deprecated form is present, accept it.
1399
+
1400
+ `kind` is internal dispatch. Authors never write it; they
1401
+ call a constructor. `AgentRunner` stays on
1402
+ `StartServerInternalOptions` for replay and eval fakes. It
1403
+ is not a `Runtime` arm and not a `defineAgent` field.
1404
+
1405
+ `virtualRuntime` is an empty local+virtual workspace until the
1406
+ author unions mounts in. `root()` is always `/`. Nothing is
1407
+ materialized onto a host scratch dir; every path is served
1408
+ lazily from the `FileSystem`. The framework does not inject
1409
+ skills, `/repo`, or a fake cwd. `skillsFs()` is a july
1410
+ helper that returns a `FileSystem` the application mounts if
1411
+ it wants skills on the workspace.
1412
+
1413
+ Change-monitor's `search` (regex = `grep`, tags on
1414
+ `/issues`, reserved keyword / semantic) stays a consumer
1415
+ authored tool if they still want it. The framework does not
1416
+ grow a `search` method. `grep` and optional `semSearch`
1417
+ cover retrieval.
1418
+
1419
+ `virtualWorkspace` is an internal lift: take the `FileSystem`,
1420
+ set `root()` to `/`, derive `brief()` from the mount table
1421
+ when there is one, omit `shell` unless a mount is
1422
+ shell-backed. Authors do not compose that lift, and they
1423
+ cannot pass `localWorkspace()` in.
1424
+
1425
+ **Session factory.** `defineAgent` has no PR/SHA. A function
1426
+ argument runs **once per session**, not at discovery and not
1427
+ per turn. It is always async (`Promise<FileSystem>`). The
1428
+ returned `FileSystem` is reused for every turn and disposed
1429
+ with the session. Per-wake data (tenant plugin skills) is a
1430
+ lazy layer *inside* that filesystem, not a new Workspace. A
1431
+ stable tree (`memoryFs({ "/a.txt": "hi" })`) is passed
1432
+ directly and needs no factory.
1433
+
1434
+ ```ts
1435
+ interface SessionWorkspaceContext {
1436
+ sessionId: string;
1437
+ agentName?: string;
1438
+ /** Channel continuation key when the session is addressable. */
1439
+ continuationKey?: string;
1440
+ /** Host services already on tool context. */
1441
+ host?: { files: HostFilesApi; reminders?: ReminderHostApi };
1442
+ }
1443
+ ```
1444
+
1445
+ `sessionId` is the session when one exists. Unscoped
1446
+ `callTool` / `agent-sdk call` has no session row; the
1447
+ framework must not invent a shared default id that would
1448
+ collapse distinct callers onto one factory-backed store.
1449
+
1450
+ ### One workspace, one namespace
1451
+
1452
+ File verbs and `shell` live on **`Workspace`**, not on `Runtime`.
1453
+ The local arm holds that object. Cloud and grokbot have no
1454
+ `workspace` field. Putting `read()` on `Runtime` re-bundles
1455
+ the string this document takes apart.
1456
+
1457
+ `FileSystem` is the shared file-verb type (`@cursor/sdk`).
1458
+ `Workspace` adds `shell` / `root` / `brief`. July authors pass
1459
+ a `FileSystem`; helpers (`memoryFs`, `unionFs`, `skillsFs`)
1460
+ return one. That is not an attachable `Shell`: `shell` stays
1461
+ on `Workspace` so it cannot be pointed at a different tree
1462
+ than `read`. Change-monitor avoids the split today by having
1463
+ no shell. computer-use.md avoids it by deriving every
1464
+ `/workspace` verb from one `RemoteMachine.exec`.
1465
+
1466
+ `unionFs(mounts)` composes FileSystems under absolute
1467
+ prefixes. Paths resolve by **longest prefix**. Method
1468
+ presence is the **union**: `write` is offered if any mount
1469
+ implements it. `shell` is not a FileSystem method and is
1470
+ not synthesized.
1471
+
1472
+ Unifying the type does **not** enforce the coupling. Two methods
1473
+ on one object can still have two backends. The rule is semantic:
1474
+
1475
+ **All Workspace methods share one path namespace, rooted at
1476
+ `root()`.** For `virtualRuntime`, that root is `/`.
1477
+ `ls({ path: "/repo" })` and `shell({ command: "ls /repo" })`
1478
+ see the same entries. Same tree, not the same formatter: the
1479
+ `ls` tool's ignore/dotfile rules and `shell({ command: "ls -la" })`
1480
+ may still render differently, as they do on a real machine.
1481
+
1482
+ The framework cannot typecheck that. The public constructors keep
1483
+ the invariant by not taking a raw `Workspace`:
1484
+
1485
+ ```ts
1486
+ // Host. File verbs and shell wrap the same cwd.
1487
+ localRuntime(opts) → localWorkspace(opts)
1488
+
1489
+ // Host + Cursor sandbox. Workspace omits shell.
1490
+ sandboxRuntime(opts) → localWorkspace({ ...opts, sandbox: true })
1491
+
1492
+ // Empty virtual. `/`, no methods, no mounts, no skills.
1493
+ virtualRuntime()
1494
+
1495
+ // Composed virtual. Application unions what it needs.
1496
+ virtualRuntime(unionFs({
1497
+ "/repo": originRepoFs(binding),
1498
+ "/host": hostFilesFs(),
1499
+ "/agent/skills": skillsFs(),
1500
+ "/workspace": cloudFs(remoteMachine),
1501
+ }))
1502
+
1503
+ // Tests.
1504
+ virtualRuntime(memoryFs({ "/a.txt": "hi" }))
1505
+ ```
1506
+
1507
+ A shell-primary mount (`cloudFs`) is a `FileSystem`
1508
+ implementation, not a second runtime argument. You union it
1509
+ in. `shell` appears on the lifted Workspace only when that
1510
+ mount (or the union) is shell-backed — same tree as `ls` /
1511
+ `read` under `/workspace`. A TypeScript isolate (tools.md §8)
1512
+ is an authored `script` tool, not a `{ script: true }` flag
1513
+ on `virtualRuntime`.
1514
+
1515
+ Do not put `shell` on a separate type the caller attaches later.
1516
+ Do not take `Workspace` as a public argument to `virtualRuntime`:
1517
+ that is how `virtualRuntime(localWorkspace())` happens.
1518
+
1519
+ ### Capability is method presence
1520
+
1521
+ Every Workspace method above except `root` is optional.
1522
+ The framework exposes the matching tool iff the method is
1523
+ implemented. A missing method is a missing tool. This is
1524
+ change-monitor's optional-write rule ("optionality is earned")
1525
+ applied to the whole workspace.
1526
+
1527
+ | Method present | Model-facing tool | Method absent |
1528
+ |---|---|---|
1529
+ | `read` | `Read` | not offered |
1530
+ | `ls` | `LS` | not offered |
1531
+ | `glob` | `Glob` | not offered |
1532
+ | `grep` | `Grep` | not offered |
1533
+ | `write` | `Write` | not offered |
1534
+ | `edit` | `StrReplace` | not offered |
1535
+ | `delete` | `Delete` | not offered |
1536
+ | `semSearch` | `SemanticSearch` | not offered |
1537
+ | `readLints` | `ReadLints` | not offered |
1538
+ | `shell` | `Shell` | not offered |
1539
+
1540
+ Those are the latest/default harness names. Some prompt
1541
+ versions rename them (`read_file`, `search_replace`,
1542
+ `list_dir`, `codebase_search`, `run_terminal_cmd`). The
1543
+ binding does not invent a fourth name.
1544
+
1545
+ `@cursor/sdk` `AgentOptions.tools` and stream `tool_call`
1546
+ events stay camelCase (`read`, `edit`, `semSearch`,
1547
+ `readLints`, `shell`, …). There is no public
1548
+ `tools: ["write"]`. `Write` and `StrReplace` both travel as
1549
+ proto `edit_tool_call`.
1550
+
1551
+ `tools: ["edit"]` means the **envelope**, not
1552
+ `workspace.edit`. It is satisfied if `write` **or** `edit`
1553
+ is present. A write-only workspace with `tools: ["edit"]`
1554
+ is valid and offers `Write` only. `tools: ["edit"]` on a
1555
+ workspace with neither method is a discovery error. Method
1556
+ presence still decides which of `Write` / `StrReplace` are
1557
+ registered. A harness that already offers `MultiStrReplace`
1558
+ or `ApplyPatch` for that model keeps doing so when `edit`
1559
+ is present, and omits them when it is not.
1560
+
1561
+ Cursor SDK binding: derive `AgentOptions.tools` from the
1562
+ presence set (`write` or `edit` contributes `edit` on the
1563
+ allowlist), then re-point those natives at the workspace.
1564
+ The model calls harness `Read`, not MCP `read` on
1565
+ `custom-user-tools`.
1566
+
1567
+ **Presence means a function, not a stub.**
1568
+ `read() { throw new Error("unsupported") }` still enables `read`.
1569
+ Omit the method.
1570
+
1571
+ **This gates the file/shell family only.**
1572
+ Do not grow Workspace to enumerate every harness builtin.
1573
+ `updateTodos` and `webSearch` do not share `root()`. Putting
1574
+ them on Workspace breaks the namespace rule and still does not
1575
+ make them work on `cloudRuntime` (that arm has no workspace
1576
+ field). Availability is not injectability.
1577
+
1578
+ **`tools` may only narrow, fail-closed.**
1579
+
1580
+ ```
1581
+ workspaceCaps = methodsPresent(workspace) // { read, ls, grep, … }
1582
+ fileRequested = agent.tools?.filter(isFileOrShell)
1583
+ harnessRequested = agent.tools?.filter(isHarnessBuiltin)
1584
+
1585
+ fileExposed =
1586
+ fileRequested === undefined ? workspaceCaps : fileRequested
1587
+
1588
+ harnessExposed =
1589
+ agent.tools === undefined
1590
+ ? (virtualRuntime ? ∅ : runtime.defaultHarnessTools)
1591
+ : harnessRequested ∩ runtime.harnessTools
1592
+ ```
1593
+
1594
+ If `agent.tools` names `grep` and `workspace.grep` is missing,
1595
+ refuse at discovery — same posture as today's "restriction that
1596
+ silently does not apply." `tools: ["edit"]` is the exception:
1597
+ it names the envelope, so refuse only when both `write` and
1598
+ `edit` are missing. If it names `webSearch` and the loop
1599
+ does not have that builtin (grokbot), refuse the same way.
1600
+ If `agent.tools` is unset, the workspace *is* the file/shell
1601
+ allowlist. `"mcp"` is still granted when server tools or
1602
+ connections exist; it is transport, not a file verb and not a
1603
+ harness builtin.
1604
+
1605
+ Host `localRuntime()` implements every file/shell method and
1606
+ keeps today's default harness set, so the toolset does not
1607
+ shrink. `virtualRuntime` with `read` + `ls` + `grep` + `glob`
1608
+ and no `shell` is change-monitor without a redundant
1609
+ `tools: ["read", …]` line. Harness builtins stay off that
1610
+ default; opt in:
1611
+
1612
+ ```ts
1613
+ defineAgent({
1614
+ runtime: virtualRuntime(fs),
1615
+ tools: ["read", "ls", "grep", "glob", "webSearch", "updateTodos"],
1616
+ });
1617
+ ```
1618
+
1619
+ `tools` is one allowlist. Listing only `["webSearch"]` turns
1620
+ file/shell natives off, even if the workspace implements them.
1621
+
1622
+ `tools: []` is an empty allowlist, not "legacy suppress
1623
+ natives." `[] ∩ workspaceCaps = ∅`. Phase 2 must omit
1624
+ `tools` on `virtualRuntime` agents so workspace methods
1625
+ become the file/shell allowlist. List harness builtins
1626
+ only to opt those in. Change-monitor drops today's
1627
+ `tools: []` when it cuts over (Phase 3).
1628
+
1629
+ ### Three tool families
1630
+
1631
+ | Family | Examples | Owner | How the model gets the native tool |
1632
+ |---|---|---|---|
1633
+ | **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. |
1634
+ | **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. |
1635
+ | **Transport / spawn** | `mcp`, `task` | Runtime arm | `mcp` when server tools or connections exist. `task` is subagents (`G12`). |
1636
+
1637
+ **Framework server tools.** Agent SDK `builtinTools`
1638
+ (`reminders`, …) are `execution: "server"`. Local
1639
+ in-process; cloud via HTTP MCP; grokbot blocked. They are
1640
+ not Cursor `ToolName`s and not Workspace methods.
1641
+
1642
+ No `Harness` interface on `Runtime`. `webSearch` is the
1643
+ backend; there is nothing to inject. `updateTodos` is
1644
+ conversation state the local harness already keeps. A third
1645
+ object would re-bundle the string (Runtime arm + workspace +
1646
+ "the rest") and still could not be honored on cloud.
1647
+
1648
+ If a later consumer needs a custom todo store or a stub
1649
+ `webSearch` in evals, add executor overrides on
1650
+ `AgentOptions.local` the same way Workspace landed — a rebind
1651
+ seam, not a constructor argument.
1652
+
1653
+ ### What each constructor actually offers
1654
+
1655
+ `virtual` and `local` share the **local loop**. Harness
1656
+ builtins are the same implementations: backend `webSearch`,
1657
+ in-process todos. The workspace is what changes. Cloud is a
1658
+ different harness that already includes those builtins and
1659
+ cannot select them.
1660
+
1661
+ | | `localRuntime()` | `virtualRuntime(fs)` | `cloudRuntime(opts)` | `grokbotRuntime()` |
1662
+ |---|---|---|---|---|
1663
+ | File/shell natives | Host workspace; `tools` narrows | Workspace methods only | VM workspace; cannot restrict | Box workspace; cannot restrict |
1664
+ | `webSearch` / `webFetch` | Default on; `tools` narrows | Off until listed in `tools` | On (VM default); `tools` refused | Fail closed if listed |
1665
+ | `updateTodos` / `readTodos` | Same | Off until listed | On (VM default); `tools` refused | Fail closed if listed |
1666
+ | `task` | Default on | Off until listed; children inherit parent workspace (`G12`) | On; cannot restrict | Blocked |
1667
+ | `mcp` | Granted for server tools / connections | Same (local loop) | Bridged HTTP MCP when configured | Blocked |
1668
+ | `edit` / `delete` / `semSearch` / `readLints` | Default on (`localWorkspace`) | Off unless the `FileSystem` implements the method | On (VM tree) | Box default |
1669
+
1670
+ `sandboxRuntime()` is the same local loop as `localRuntime()`,
1671
+ with Cursor sandbox on. The workspace omits `shell`. Host+sandbox
1672
+ turns keep `LocalShellExecutor` and do not inject the workspace.
1673
+
1674
+ One agent file cannot express the same *selected* toolset on
1675
+ all three constructors until the cloud API honors `tools`
1676
+ (non-goal, §10). `tools: ["webSearch", "updateTodos"]` on
1677
+ `cloudRuntime` is still a discovery error: we cannot prove the
1678
+ VM will offer only those, and we must not run unrestricted
1679
+ and pretend we selected.
1680
+
1681
+ What *is* true without that API:
1682
+
1683
+ - Local (including today's `tools: []` agents): list the
1684
+ harness names. That already works. `virtualRuntime` does
1685
+ not unlock `updateTodos` / `webSearch`; the allowlist does.
1686
+ - Cloud: those names are already on the VM. Authors who need
1687
+ them on a cloud agent omit `tools` and accept the rest of
1688
+ the VM workspace.
1689
+ - Do not special-case "harness-only `tools` + cloud" as
1690
+ silently honored. That is the hybrid footgun with extra
1691
+ steps.
1692
+
1693
+ **Do not synthesize omitted verbs.**
1694
+ Change-monitor's current mounts require `grep` on every *mount* because
1695
+ it is derivable from `glob` + `read`. That is a mount-quality
1696
+ rule, not a Workspace-to-tool rule. If the workspace omits `grep`,
1697
+ the model does not get a generated grep that secretly scans via
1698
+ `read`. An implementation that has `glob` and `read` should
1699
+ usually implement `grep` (delegate to `scanGrep`); the framework
1700
+ does not invent it.
1701
+
1702
+ **`write` and `edit` are two native tools, not one.**
1703
+
1704
+ Str-replace `edit` cannot create a file (`oldText: ""`
1705
+ errors). The harness still offers a **Write** tool
1706
+ (`{ path, fileText }`) for create-or-overwrite. Apply-patch
1707
+ `edit` can create via `*** Add File:`. Proto `WriteToolCall`
1708
+ was folded into the `EditToolCall` envelope; the model-facing
1709
+ Write tool remains.
1710
+
1711
+ Workspace keeps both methods, matching those two tools:
1712
+
1713
+ - `write` — native Write schema (`path`, `fileText`, open for
1714
+ later fields). Create or replace a whole file.
1715
+ - `edit` — native Edit schema (`path`, plus `oldText` /
1716
+ `newText`, `edits[]`, or `patchContent`). Change an
1717
+ existing file; apply-patch may add.
1718
+ - `grep` — native result kinds are `"content"` | `"files"` |
1719
+ `"count"` (not `"files_with_matches"`).
1720
+
1721
+ Presence of one does not synthesize the other. A
1722
+ `FileSystem` that implements `write` and omits `edit` offers
1723
+ Write only. That workspace may still list `tools: ["edit"]`
1724
+ (envelope). It must not register `StrReplace`.
1725
+ Change-monitor's CAS `write` / authored str-replace stay
1726
+ authored tools until the mount implements these native
1727
+ schemas. Do not keep a third write contract.
1728
+
1729
+ **`edit`, `delete`, `semSearch`, `readLints` are Workspace
1730
+ methods.** They see `root()`. Optional: `localWorkspace()`
1731
+ implements what the host harness already has;
1732
+ `virtualWorkspace(fs)` omits a method until that
1733
+ `FileSystem` can honor it. The type includes them now. A
1734
+ tree that cannot index or lint simply leaves the method off.
1735
+
1736
+ - `delete` — path verb. `DeleteExecutor` exists; Phase 1 adds
1737
+ the override and fail-closed omission.
1738
+ - `edit` — same tree as `write`. Native schema stays native.
1739
+ Phase 1 fail-closed-omits host `edit` when the method is
1740
+ missing so a virtual tree cannot be patched on disk.
1741
+ Implementing the virtual body can wait for a writable
1742
+ mount (change-monitor Phase 3).
1743
+ - `semSearch` — native schema. Host walks a **codebase index**,
1744
+ then `read`s snippets. A virtual `FileSystem` omits it
1745
+ until that tree has an index. Do not leave host
1746
+ `semSearch` registered against a virtual workspace. Do not
1747
+ alias change-monitor's authored `search` (tags / regex)
1748
+ onto this method.
1749
+ - `readLints` — `diagnosticsExecutor` (LSP against the tree).
1750
+ Same presence rule. A virtual tree has no language service
1751
+ unless a mount provides one.
1752
+
1753
+ ### Effect and approvals stay outside Workspace
1754
+
1755
+ Defaults do not change. `localRuntime()` / no `workspace` is
1756
+ today's `LocalWriteExecutor` + `permissionsService` +
1757
+ pending-decision cards.
1758
+
1759
+ Two planes already exist. Workspace implements neither.
1760
+
1761
+ | Plane | What it is | Who configures it | Virtual |
1762
+ |---|---|---|---|
1763
+ | **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. |
1764
+ | **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`. |
1765
+
1766
+ ```
1767
+ tool call → effect / dry-run → approval policy → Workspace method
1768
+ ```
1769
+
1770
+ Host path allowlists, `.cursorignore`, and worktree guards
1771
+ live on **`localWorkspace` only**. They key on host paths.
1772
+ Virtual paths (`/repo/…`) are a different namespace; wrapping
1773
+ them in `permissionsService.shouldBlockWrite` would block or
1774
+ prompt every virtual call. Mount policy (CAS, publish ladder)
1775
+ stays inside the `FileSystem`.
1776
+
1777
+ Eval `memoryFs` uses the same gates. If the agent's approval
1778
+ policy is off (today's default for many local agents), no new
1779
+ cards appear.
1780
+
1781
+ `@cursor/sdk` exports `FileSystem` and `Workspace`. July
1782
+ authors pass a `FileSystem` into `virtualRuntime`. Helpers
1783
+ return `FileSystem`. Do not add a `search` method: the regex
1784
+ arm is `grep`, `semSearch` is already on `FileSystem`,
1785
+ keyword has no implementation, and tags is an unproven
1786
+ change-monitor issues-mount feature. If they still want tag
1787
+ lookup, it stays an authored tool on their mounts.
1788
+
1789
+ Phase 1 does not need virtual implementations of these four.
1790
+ It does need the omit path: when a workspace is set and the
1791
+ method is absent, the host executor is not registered.
1792
+
1793
+ ### Environment brief is not `Runtime.instructions()`
1794
+
1795
+ The workspace owns facts the model must know — `root()`,
1796
+ mount table, "there is no checkout," grep dialect. Change-monitor
1797
+ currently writes those into `agent/instructions.md` because there
1798
+ is nowhere else to put them. Cloud already injects loop-owned
1799
+ prose the same way (`buildAgentsMdContent` prepends a server-tool
1800
+ MCP catalog and a memory-mount path).
1801
+
1802
+ That is a real injection point. It is not
1803
+ `Runtime.instructions() -> string`.
1804
+
1805
+ `defineAgent({ instructions })` / `instructions.md` is the
1806
+ agent's job: role, steering, what to publish. A method with the
1807
+ same name implies it replaces that file. Today's prompt is
1808
+ already layered, and the layers have different owners:
1809
+
1810
+ | Layer | Owner | When / where |
1811
+ |---|---|---|
1812
+ | `instructions.md` | Agent author | Always; identity of the agent |
1813
+ | `## About you` | Framework | Folded into AGENTS.md / preamble |
1814
+ | Server-tool MCP catalog, "write the script if missing" | **Runtime arm / delivery** | Cloud first prompt; grokbot `createSession({ instructions })` |
1815
+ | `<agentkit_context>` (session, channel, time) | Framework | First user prompt only |
1816
+ | Harness `<user_info>` `Workspace Path` | **Workspace** (today: cwd) | Local harness, every turn |
1817
+ | Mount table, dialects, "no shell" | **Workspace** | Missing as an API; stuffed into instructions.md |
1818
+
1819
+ A string on `Runtime` re-bundles those layers and cannot say
1820
+ which channel to use. Returning prose does not update harness
1821
+ `Workspace Path`. `virtualRuntime` sets `root()` to `/` and
1822
+ does not materialize a scratch cwd. `local.workspaceDir` +
1823
+ virtual is a discovery error. `root()` is the contract; if it
1824
+ disagrees with `brief()`, the model gets two maps.
1825
+
1826
+ Ownership rules:
1827
+
1828
+ 1. **Agent instructions stay authored.** The framework never asks
1829
+ a runtime to supply the agent's job description.
1830
+ 2. **Workspace brief is an addendum.** Mount-table paragraphs move
1831
+ here; "You write a monitoring plan for one PR" does not.
1832
+ 3. **Arm-owned catalogs stay on the arm.** Cloud's
1833
+ `agentsdk-tools` preamble and grokbot's composed blob are
1834
+ delivery, not `Workspace.brief()`.
1835
+ 4. **Brief is session-scoped.** Planner mounts depend on the
1836
+ dispatcher's binding (repo, SHA, which mounts exist).
1837
+ `brief(ctx)` runs when the session workspace is built.
1838
+ 5. **Prefer structure over a blob.** `root()` is what
1839
+ the harness honors. Do not grow `brief()` into a second
1840
+ instructions.md.
1841
+
1842
+ ### What `shell` is allowed to be
1843
+
1844
+ `shell` is "run a program, return stdout/stderr/exit." Language
1845
+ and isolation are implementation details. The namespace is not:
1846
+ `shell` must see the same tree as `ls` / `read`.
1847
+
1848
+ | Implementation | Language | Isolation | How the tree stays one |
1849
+ |---|---|---|---|
1850
+ | Host | bash / zsh / cmd | Serve-host process, optional sandbox | Same cwd as file verbs |
1851
+ | Script isolate (tools.md §8) | TypeScript | Host-side isolate; only bound Workspace methods | `shell` *is* those methods |
1852
+ | Remote machine (computer-use.md) | bash on the pod | anyrun microVM, no host secrets | File verbs are commands over the same `shell` |
1853
+
1854
+ A virtual workspace may omit `shell` (change-monitor today). The
1855
+ loop does not change.
1856
+
1857
+ A TypeScript isolate and host bash are different trust classes.
1858
+ Do not overload one `shell` method for both. Omit `shell` and
1859
+ keep `script` as an authored tool (tools.md §8). A shell-backed
1860
+ mount (`cloudFs`) is the other `shell`. If those two ever
1861
+ need one method, add a `kind` then — not as a staged "v2."
1862
+
1863
+ ### What stays off `Runtime`
1864
+
1865
+ - **Inference.** Local Connect client stays framework-owned.
1866
+ - **`AgentRunner`.** Internal `startServer` test/host seam.
1867
+ Not a `Runtime` arm.
1868
+ - **MCP / OAuth / peer URLs.** Host capabilities, already on
1869
+ `ctx.host` and connection files.
1870
+ - **Server tools / `builtinTools`.** Host-side,
1871
+ `execution: "server"`. They are not workspace verbs.
1872
+ - **`ctx.host.files`.** Durable store. A workspace *may* mount it
1873
+ (change-monitor `/host`); the store API itself is not FS.
1874
+
1875
+ ### Composition rules
1876
+
1877
+ 1. **`localRuntime(opts?)`** → `{ kind: "local", workspace: localWorkspace(opts) }`
1878
+ (today). Sibling `local` is deprecated sugar for `opts`.
1879
+ **`sandboxRuntime(opts?)`** is the named constructor for
1880
+ host-local + Cursor sandbox: `localRuntime({ ...opts, sandbox: true })`.
1881
+ The workspace omits `shell`. Host+sandbox turns do not
1882
+ inject that workspace into the SDK.
1883
+ 2. **`virtualRuntime(fs?)`** → local arm + that workspace.
1884
+ Each implemented method exposes that tool. Authored
1885
+ `execution: "server"` tools stay on the host. Effect and
1886
+ approval policy wrap the tool call, then the Workspace
1887
+ runs. `tools` may only narrow, fail-closed.
1888
+ `local.workspaceDir` / `sandbox` + virtual is a discovery
1889
+ error.
1890
+ 3. **`cloudRuntime` / `grokbotRuntime` have no `workspace`
1891
+ field.** Those arms own their tree. A machine API would
1892
+ add a field on that arm later, not a sibling we then
1893
+ refuse.
1894
+ 4. **`tools` allowlist + cloud/grokbot** → same fail-closed
1895
+ errors as today.
1896
+ 5. **Per-send `runtime?: CloudRuntime | LocalRuntime`.**
1897
+ Host-local + `cloudRuntime` → attach. Host-local +
1898
+ `localRuntime(opts)` / `sandboxRuntime(opts)` → overlay
1899
+ cwd / sandbox / workspaceDir. Cloud + `cloudRuntime` → replace the
1900
+ session cloud payload (no merge). Virtual + either →
1901
+ refuse. Cloud / grokbot + `localRuntime` → refuse.
1902
+ Grokbot + `cloudRuntime` → refuse (today's throw).
1903
+ `grokbotRuntime` is not in the field type. Deprecated
1904
+ `send({ cloud })` / `send({ workspaceDir })` stay as
1905
+ shims. `send({ runtime, cloud })` and
1906
+ `send({ runtime, workspaceDir })` fail closed.
1907
+ 6. **Delivery** is derived from the arm and, on local, which
1908
+ workspace: host → materialize; virtual → no host
1909
+ workspace, `Workspace Path` is `/`, skills only if the
1910
+ author mounted `skillsFs()`; cloud → preamble + store;
1911
+ grokbot → session instructions.
1912
+
1913
+ ---
1914
+
1915
+ ## 2. What the string actually selects
1916
+
1917
+ `AgentRuntime` lives on the Agent SDK config
1918
+ (`packages/agent-serve/src/types.ts`). The Cursor SDK has **no**
1919
+ `runtime` field on `AgentOptions`. It routes on `options.cloud`
1920
+ (or a `bc-` agent id) in `createDefaultAgent` /
1921
+ `resumeDefaultAgent` (`packages/cursor-sdk/src/agent/platform.ts`).
1922
+ The Agent SDK string is a product-level selector that then builds
1923
+ different SDK option bags.
1924
+
1925
+ There are already three values, plus a hybrid, plus a consumer
1926
+ that forges a fourth:
1927
+
1928
+ | | Local loop | Cloud loop | Grok Bot loop | Change-monitor today |
1929
+ |---|---|---|---|---|
1930
+ | **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 |
1931
+ | **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 |
1932
+ | **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 |
1933
+ | **`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 |
1934
+ | **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) |
1935
+ | **Approvals** | Supported | Not supported | N/A | Supported |
1936
+ | **Instructions** | Host `AGENTS.md` (or inline if cwd is borrowed) | First-prompt preamble | `createSession({ instructions })` | Instructions + skills index; cwd is a fake `/repo` |
1937
+ | **Skills** | Materialized into `.cursor/skills/` | Synced to Agent Store | Warned: not on the box | Advertised in instructions, not harness files |
1938
+ | **Sandbox seeds** | Written into the session workspace | Ignored | Ignored | Unused (`tools: []`) |
1939
+ | **MCP** | Loopback / per-send auth | Bridged or forwarded; needs `--public-url` | Blocked | Used (sandbox stays off so MCP does not fail closed) |
1940
+ | **Subagents** | SDK `agents` | SDK `agents` | Blocked | Off (`tools: []` strands `task`) |
1941
+ | **`send({ runtime })`** | Host-local → attach or overlay; cloud → replace payload | Replace payload | Refused | Not used |
1942
+
1943
+ The string is checked in roughly fifteen Agent SDK sites. The
1944
+ concentrated ones:
1945
+
1946
+ - `discovery.ts` — compile-time capability errors/warnings per value.
1947
+ - `cloud-merge.ts` `resolveSessionRuntime` — per-session cloud attach
1948
+ forces `"cloud"`; grokbot throws.
1949
+ - `session-engine.ts` `buildTurnRequest` — one function assembles
1950
+ tools, customTools, MCP, instructions, skills sync, and the
1951
+ fail-closed hybrid guards.
1952
+ - `sdk-runner.ts` `openAgent` — `cloud:` block vs local `cwd` /
1953
+ `sandbox` / `tools`.
1954
+ - `runtime-dispatch-runner.ts` — grokbot vs everything else.
1955
+
1956
+ There is no plugin registry. `StartServerInternalOptions.runner`
1957
+ can replace the whole `AgentRunner`, which is too coarse to inject
1958
+ a filesystem.
1959
+
1960
+ ---
1961
+
1962
+ ## 3. Change-monitor is already a virtual workspace
1963
+
1964
+ Planner, executor, and curator all do the same thing
1965
+ (`factory/change-monitor/{planner,executor,curator}/agent/agent.ts`):
1966
+
1967
+ ```ts
1968
+ export default defineAgent({
1969
+ runtime: "local",
1970
+ tools: [],
1971
+ local: {
1972
+ workspaceDir: "/repo", // does not need to exist
1973
+ sandbox: false, // sandbox makes MCP fail closed
1974
+ },
1975
+ });
1976
+ ```
1977
+
1978
+ Then `src/vfs/tools.ts` reimplements `ls` / `glob` / `read` /
1979
+ `grep` as server tools whose descriptions and parameter schemas
1980
+ are copied **verbatim** from the native harness
1981
+ (`tools.contract.test.ts` pins the equality). Bodies call
1982
+ `mountVfsForSession` and today's consumer filesystem type
1983
+ (they named it `VFS`; Phase 3 replaces it with `FileSystem`):
1984
+
1985
+ ```ts
1986
+ interface VFS {
1987
+ list(dir: string): Promise<VFSEntry[]>;
1988
+ glob(pattern: string, dir?: string): Promise<string[]>;
1989
+ read(path: string): Promise<string>;
1990
+ grep(query: GrepQuery): Promise<GrepResult>;
1991
+ search(query: SearchQuery): Promise<GrepResult>;
1992
+ write?(path: string, contents: string): Promise<void>;
1993
+ }
1994
+ ```
1995
+
1996
+ That type is **not** a july type. `search` (regex = `grep`,
1997
+ tags on issues, reserved keyword / semantic) stays theirs if
1998
+ they still want the authored tool. The framework cutover is
1999
+ `FileSystem` + native tools.
2000
+
2001
+ Mounts (`src/vfs/session.ts`) compose into one namespace: `/repo`
2002
+ (Origin at a pinned SHA), `/host` (`ctx.host.files`), plus plans /
2003
+ issues / checkpoints / skills. The model thinks it has a
2004
+ filesystem. The serve host has no checkout and no shell.
2005
+
2006
+ Two follow-on designs stay on this local-loop + virtual-workspace
2007
+ posture rather than flipping the string:
2008
+
2009
+ - **Tool scripting** (tools.md §8, not implemented): a `script`
2010
+ server tool that runs TypeScript against a capability-bounded
2011
+ tool bridge. Authored tool, still under
2012
+ `virtualRuntime(fs)`. Not a new loop.
2013
+ - **Computer use** (computer-use.md, not implemented): a
2014
+ writable `/workspace` mount (`cloudFs`) whose file verbs are
2015
+ commands on a lazily-provisioned anyrun microVM. Union it into
2016
+ the same `FileSystem`. Explicitly **not** `runtime: "cloud"`: cloud
2017
+ turns cannot restrict tools, cannot run server tools
2018
+ in-process, and cannot park approvals.
2019
+
2020
+ So "virtual" is one constructor with many filesystems. A third
2021
+ string value would repeat the original mistake.
2022
+
2023
+ ---
2024
+
2025
+ ## 4. Constraints the string hid
2026
+
2027
+ ### 4.1 Cloud is not a different workspace on the same loop
2028
+
2029
+ A local turn: the serve host runs the agent loop, the backend
2030
+ runs inference, tools run against a workspace (host or injected).
2031
+
2032
+ A cloud turn: the SDK is a REST + SSE client (`CloudApiClient`).
2033
+ `createAgent` / `createRun` take a prompt. Tool execution happens
2034
+ on the VM; the SDK surfaces `tool_call` events. There is no
2035
+ in-process `read` or `shell` to swap. The public cloud surface has
2036
+ no `Exec` RPC (computer-use.md Gap 1). Driving the VM by
2037
+ prompting it is "execution by persuasion" — rejected as an exec
2038
+ backend.
2039
+
2040
+ `cloudRuntime` growing a `workspace` field would be either:
2041
+
2042
+ 1. **Lies** — methods that do not run, because that arm owns
2043
+ the tree and the SDK cannot reach it, or
2044
+ 2. **A new product** — a machine primitive (create / exec / read /
2045
+ hibernate, no model attached) that does not exist on the
2046
+ public API.
2047
+
2048
+ Until (2) exists, `cloudRuntime` is a **Runtime arm that
2049
+ owns its tree**. It has no `workspace` field.
2050
+
2051
+ ### 4.2 Inference does not belong on `Runtime`
2052
+
2053
+ Local already separates inference from tools:
2054
+ `AgentConnectClient` streams the model, `resources`
2055
+ (`ResourceAccessor`) execute side effects
2056
+ (`packages/cursor-sdk/src/agent/local-executor.ts`). Cloud and
2057
+ Grok Bot couple them inside a remote service.
2058
+
2059
+ Putting `infer()` on `Runtime` would be true for local, false
2060
+ for cloud/grokbot, and unused by change-monitor. Keep inference
2061
+ off the workspace. Replay / eval fakes keep using
2062
+ `StartServerInternalOptions.runner`.
2063
+
2064
+ ### 4.3 Delivery is a third axis
2065
+
2066
+ | Artifact | Local | Cloud | Grok Bot | Virtual |
2067
+ |---|---|---|---|---|
2068
+ | Instructions | `AGENTS.md` in cwd | First-prompt preamble | Session `instructions` | Authored instructions + optional `brief()` |
2069
+ | Skills | `.cursor/skills/` | Agent Store sync | Not on the box | Nothing unless the author mounts `skillsFs()` |
2070
+ | `execution: "agent"` scripts | Files in workspace | Prompt bodies | Prompt bodies | Banned without `shell` |
2071
+ | Sandbox seeds | Copied into cwd | Dropped | Dropped | Skip; put data in the `FileSystem` if the model should see it |
2072
+ | Workspace Path | Host cwd | VM | Box | `/` |
2073
+
2074
+ `virtualRuntime` does not materialize a scratch dir and does
2075
+ not fake `/repo`. Everything is lazy from the `FileSystem`.
2076
+ Empty `virtualRuntime()` is a workspace at `/` with no methods.
2077
+
2078
+ Skills are the sharp case. Authoring is one tree
2079
+ (`agent/skills/`). Discovery loads it the same way on every
2080
+ constructor. After that the pipes diverge. The model does
2081
+ not get the same catalog, the same load path, or the same
2082
+ ambient extras.
2083
+
2084
+ | | How the catalog is advertised | Where `SKILL.md` bytes live | How the model loads the body | Ambient extras the author did not write |
2085
+ |---|---|---|---|---|
2086
+ | **`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 |
2087
+ | **`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 |
2088
+ | **`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 |
2089
+ | **`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 |
2090
+
2091
+ This is why delivery is derived from the **Runtime arm** and,
2092
+ on local, which workspace — not from `Workspace` alone:
2093
+
2094
+ - local + host workspace → materialize `.cursor/skills/` (today).
2095
+ - local + virtual workspace → no host workspace. Skills exist
2096
+ only if the author unions `skillsFs()` (or their own
2097
+ mount) and advertises it. `read` hits `/`.
2098
+ - cloud → store sync. No cwd to write.
2099
+ - grokbot → warn. Do not pretend the box has them.
2100
+
2101
+ Do not put `skills()` on `Workspace`. `skillsFs()` is a
2102
+ `FileSystem` the application may mount. The harness catalog
2103
+ is not pointed at a fake `/repo`.
2104
+
2105
+ ### 4.4 Capability gates stay fail-closed
2106
+
2107
+ `tools` + cloud throws in the Cursor SDK
2108
+ (`assertNoCloudToolsRestriction`). The Agent SDK repeats the
2109
+ guard at discovery and again on hybrid turns
2110
+ (`session-engine.ts` ~2020). `advertiseTools` / per-session MCP
2111
+ auth are local-only for the same reason.
2112
+
2113
+ If a workspace cannot honor an allowlist, the turn is refused.
2114
+ Do not invent a "virtual cloud" that claims `tools: ["read"]`
2115
+ and then runs a VM with a full shell.
2116
+
2117
+ ### 4.5 `send({ runtime })` overlays or attaches
2118
+
2119
+ Two jobs, one field:
2120
+
2121
+ ```ts
2122
+ SendMessageOptions.runtime?: CloudRuntime | LocalRuntime;
2123
+ ```
2124
+
2125
+ **Attach (hybrid).** Host-local agent, this session on a
2126
+ VM. Same continuation token, different turn backend.
2127
+
2128
+ ```ts
2129
+ await send("fix the CI failure", {
2130
+ runtime: cloudRuntime({
2131
+ repos: [{ url, startingRef }],
2132
+ }),
2133
+ });
2134
+ ```
2135
+
2136
+ **Overlay.** Host-local agent, this session's host options
2137
+ (PR worktree, etc.). Today's `send({ workspaceDir })`.
2138
+
2139
+ ```ts
2140
+ await send("review this PR", {
2141
+ runtime: localRuntime({ workspaceDir: worktree }),
2142
+ });
2143
+ ```
2144
+
2145
+ `virtualRuntime` is not a send argument (`virtualRuntime`
2146
+ returns `LocalRuntime`, but a new tree mid-session is
2147
+ refused). Types cannot tell host `localRuntime` from
2148
+ `virtualRuntime`. Virtual + `cloudRuntime` or
2149
+ `localRuntime({ workspaceDir })` fails at session start.
2150
+
2151
+ | Agent default | `send({ runtime })` | |
2152
+ |---|---|---|
2153
+ | host `localRuntime` | `cloudRuntime(opts)` | Attach. Today's hybrid. |
2154
+ | host `localRuntime` | `localRuntime(opts)` | Overlay cwd / sandbox / workspaceDir. |
2155
+ | `virtualRuntime` | `cloudRuntime` / `localRuntime({ workspaceDir })` | Type-ok, refuse at start. |
2156
+ | `cloudRuntime` | `cloudRuntime(opts)` | Replace session cloud payload. No merge. |
2157
+ | `cloudRuntime` | `localRuntime` | Refuse. |
2158
+ | grokbot | `cloudRuntime` / `localRuntime` | Refuse. Today's throw. |
2159
+ | anything | `grokbotRuntime` | Type error. |
2160
+
2161
+ Overlay uses today's cwd resolution: send overlay →
2162
+ agent `localRuntime` opts → `cwd/<sessionId>` → durable
2163
+ session dir. Cloud ignores a `workspaceDir` overlay.
2164
+
2165
+ Preferred `cloudRuntime(opts)` on send is the full session
2166
+ payload. It does not merge over `agent.cloud`. A cloud-default
2167
+ agent that wants today's per-session merge keeps the
2168
+ deprecated shim.
2169
+
2170
+ Deprecated shims: `send({ cloud: opts })` →
2171
+ `cloudRuntime(merge(agent.cloud, opts))`, then the table
2172
+ (so a cloud-default agent still merges; grokbot still
2173
+ refuses). `send({ workspaceDir })` →
2174
+ `localRuntime({ workspaceDir })` when the agent is
2175
+ host-local; ignored on cloud; refused on virtual / grokbot.
2176
+ `send({ runtime, cloud })` and
2177
+ `send({ runtime, workspaceDir })` fail closed.
2178
+
2179
+ This is a session override, not a subagent (`task` /
2180
+ `G12`). A process-wide singleton cannot express this.
2181
+
2182
+ ### 4.6 Workspace is a product surface, not `ResourceAccessor`
2183
+
2184
+ The harness workspace is `ResourceAccessor`
2185
+ (`packages/agent-exec`): read, write/edit, grep, ls, glob (via
2186
+ grep), shell, shell-stdin, MCP, computer-use, and more.
2187
+ `LocalResourceProviderOptions` exposes overrides for **three**
2188
+ of ~fifteen tools. Grep, ls, and glob have no public override.
2189
+
2190
+ `FileSystem` is the public file-verb type: native tool names,
2191
+ native arg schemas (`NativeArgs<T>`). `Workspace` adds `shell`
2192
+ / `root` / `brief`. It is not a drop-in for
2193
+ `ResourceAccessor`. Streaming stdin, MCP, and computer-use
2194
+ stay off the type.
2195
+
2196
+ ### 4.7 The model must call native `read`
2197
+
2198
+ Models are trained on first-class `read` / `ls` / `grep` /
2199
+ `glob`. Change-monitor copies those texts into server tools
2200
+ because there is no supported way to keep the native tool
2201
+ *name* and swap the *implementation* (computer-use.md Gap 2).
2202
+
2203
+ That copy is not the real miss. Agent SDK server tools become
2204
+ Cursor `customTools`. Those are a synthetic MCP server
2205
+ (`custom-user-tools`). Local SDK defaults
2206
+ `mcpMetaToolEnabled` to true, so the model's tool list is
2207
+ `GetMcpTools` and `CallMcpTool`. After discovery the tool on
2208
+ that server is `read`. The call is MCP, not `ReadToolCall`.
2209
+ The stream event is `name: "mcp"`, not `name: "read"`.
2210
+ `custom-user-tools-read` is the internal wire id, not a
2211
+ top-level tool.
2212
+
2213
+ Cursor SDK executor overrides keep the native tool, the
2214
+ native schema, and the native `tool_call`. Only the
2215
+ implementation is swapped. Callers of
2216
+ `virtualRuntime(unionFs(…))` never see the binding. If
2217
+ Workspace only registered the same per-agent server tools
2218
+ change-monitor already ships, the model would still be on
2219
+ MCP. That is not a fix.
2220
+
2221
+ ---
2222
+
2223
+ ## 5. Existing seams (use them; do not invent a fourth)
2224
+
2225
+ ```
2226
+ Agent SDK defineAgent({ runtime }) product selector
2227
+
2228
+ Cursor SDK AgentOptions { local?, cloud? } dispatch
2229
+
2230
+ local-runtime WorkspaceRuntime / SessionRuntime session factory
2231
+
2232
+ local-exec LocalResourceProvider workspace
2233
+
2234
+ agent-exec ResourceAccessor / ExecResource verb registry
2235
+ ```
2236
+
2237
+ Change-monitor sits *above* all of this, as
2238
+ `execution: "server"` tools, because the bottom two layers are
2239
+ not plumbed to `AgentOptions`.
2240
+
2241
+ | Seam | What it is | Why it is not enough today |
2242
+ |---|---|---|
2243
+ | `AgentRunner` | Internal `startServer` turn replacement (`runTurn` / `prewarm`) | Replaces the whole turn; no workspace. Not a `Runtime` arm. |
2244
+ | `RuntimeDispatchingRunner` | grokbot vs SDK | Hard-coded third case |
2245
+ | `AgentOptions.local` / `.cloud` | Parallel config blocks | Presence of `cloud` *is* the runtime until later `Agent.create({ runtime })` |
2246
+ | `LocalResourceProvider` overrides | Swap read/write/shell | Private; 3 of ~15 tools; not on `AgentOptions` |
2247
+ | `ResourceAccessor` | The agent loop's workspace | Correct internal interface; not a public SDK type |
2248
+ | Change-monitor filesystem | Product FS | Consumer-side; duplicated native schemas |
2249
+ | `ctx.host.files` | Durable KV, file-shaped | Explicitly not a filesystem; natives cannot see the hosted sink |
2250
+
2251
+ The first code PR promotes a public `Workspace` on
2252
+ `AgentOptions.local`, bound as executor overrides. Agent SDK
2253
+ then passes that workspace through `virtualRuntime`. Do not add
2254
+ `runtime: "virtual"`. Do not monkey-patch
2255
+ `sessionRuntime.resources` from `@cursor/july`.
2256
+
2257
+ ---
2258
+
2259
+ ## 6. Gaps and risks
2260
+
2261
+ **G1. Cloud cannot honor an injected workspace.**
2262
+ Public `CloudApiClient` is prompt-only. Exec-daemon
2263
+ `ControlService.Exec` exists but needs per-pod credentials a
2264
+ public caller cannot mint (computer-use.md Gap 1). Treat cloud
2265
+ as an opaque loop. Do not ship Workspace methods on
2266
+ `cloudRuntime`.
2267
+
2268
+ **G2. Builtin redirection is not a public SDK feature.**
2269
+ Overrides exist on `LocalResourceProviderOptions` and are not
2270
+ passed through `createDefaultLocalWorkspaceRuntime` or
2271
+ `AgentOptions` (computer-use.md Gap 2). Grep/ls/glob have no
2272
+ override. This is the first code PR: add the missing
2273
+ overrides, put `Workspace` on `AgentOptions.local`, and derive
2274
+ the tools allowlist from method presence. The authoring API
2275
+ in `@cursor/july` waits on that seam. Do not ship
2276
+ `virtualRuntime` on lookalike server tools first.
2277
+
2278
+ **G3. Native tool schemas vs consumer extras.**
2279
+ A redirected `read` must keep the native schema. Verbs that
2280
+ are not Cursor tools (`diff`, change-monitor `search`, CAS
2281
+ `write`) stay authored tools. Do not widen native schemas to
2282
+ carry mount-table prose; that belongs in `brief()` /
2283
+ instructions.md (tools.md §6). Do not add `search` to
2284
+ `FileSystem` for tags or keyword.
2285
+
2286
+ **G4. Hybrid + virtual is easy to get wrong.**
2287
+ A planner with a virtual workspace that also
2288
+ `send({ runtime: cloudRuntime(...) })` would run
2289
+ unrestricted on the VM. Refuse the attach, or require an
2290
+ explicit second agent as the delegation target
2291
+ (tools.md §3). `send({ runtime })` is not an open override.
2292
+
2293
+ **G5. Grokbot is a third Runtime arm, not a workspace.**
2294
+ It has no MCP, no server tools, no model override, no v2
2295
+ architecture. Keep `grokbotRuntime()` as an arm with no
2296
+ `workspace` field.
2297
+
2298
+ **G6. Delivery / `Workspace Path`.**
2299
+ `virtualRuntime` prints `/`. No scratch dir, no
2300
+ `workspaceDir: "/repo"`. A `brief()` that names mounts the
2301
+ `FileSystem` does not have is two maps; `root()` wins.
2302
+
2303
+ **G7. Security: workspace isolation ≠ host isolation.**
2304
+ Injecting a `FileSystem` does not sandbox the serve host. Server tools
2305
+ still run in-process with host credentials. Host `shell` is
2306
+ still host bash. Document the blast radius on each
2307
+ implementation. Prompt-injected writes hit `/host` (or a
2308
+ disposable VM), never the serve host's disk or secrets.
2309
+
2310
+ **G8. `script` is not host `shell`.**
2311
+ A TypeScript isolate that can only call bound Workspace methods
2312
+ is a different trust class from `/bin/bash`. Do not overload
2313
+ one `shell` method for both until two in-tree implementations
2314
+ need a shared `shell` with a `kind`.
2315
+
2316
+ **G9. Two public surfaces.**
2317
+ `@cursor/sdk` owns `FileSystem` / `Workspace` (native schemas).
2318
+ `@cursor/july` owns `virtualRuntime` and helpers that return
2319
+ `FileSystem`. Phase 1 does not put constructors on
2320
+ `Agent.create`. A later SDK deprecation uses the same
2321
+ `Runtime` types. A july-only filesystem cannot re-point
2322
+ natives without the SDK seam. Do not ship the authoring API
2323
+ against lookalikes and flip the binding later.
2324
+
2325
+ **G10. Test and eval story.**
2326
+ A virtual workspace must work in `run` / `eval` without Origin.
2327
+ Change-monitor already has in-memory filesystem fakes; those become
2328
+ `virtualRuntime(memoryFs(…))` in tests.
2329
+
2330
+ **G11. Versioning and published docs.**
2331
+ String `runtime`, sibling `local` / `cloud`, and
2332
+ `send({ cloud })` / `send({ workspaceDir })` are a shipped
2333
+ user contract. Replacement is additive:
2334
+ `localRuntime(opts)` / `cloudRuntime(opts)` plus `@deprecated`
2335
+ on the old selector, sibling bags, and send shims. Do not
2336
+ break existing `defineAgent({ runtime: "cloud", cloud })` or
2337
+ `defineAgent({ local: { workspaceDir } })`. Do not document
2338
+ constructors until they ship (Phase 4 / §9).
2339
+
2340
+ **G12. Subagents keep today's behavior, stored as `Runtime`.**
2341
+ Discovery still loads a child as a Cursor SDK custom
2342
+ subagent (`task`: prompt + description + model). Authored
2343
+ `runtime`, `tools/`, skills, MCP under `agent/subagents/`
2344
+ are warned and ignored. The child gets the harness default
2345
+ toolset on the parent's workspace. Empty july `tools: []`
2346
+ does not mean the child is empty-tooled — those slots are
2347
+ inert.
2348
+
2349
+ Do not change that product. Internally, stop storing
2350
+ `runtime: "local"` + `tools: []` as the child's july
2351
+ config. Express the same forced host-local inherit as
2352
+ `localRuntime()` whose workspace is the parent's (host
2353
+ `localWorkspace` or the virtual tree). Authors do not get
2354
+ a `Runtime` field on subagents in this design.
2355
+
2356
+ A child with its own cwd, own cloud VM, or real july tools
2357
+ is a later product. If that product ever honors a child
2358
+ `Runtime`, default to inheriting the parent workspace so a
2359
+ virtual parent does not spawn host `read`.
2360
+
2361
+ **G13. Independently implemented `read` + `shell`.**
2362
+ Flattening onto Workspace removes the attach-a-different-shell
2363
+ footgun; it does not prove the backends share a namespace.
2364
+ Prefer `virtualRuntime(unionFs(…))` / `localRuntime()`.
2365
+ `virtualWorkspace` stays private.
2366
+
2367
+ ---
2368
+
2369
+ ## 7. Why Cursor SDK first
2370
+
2371
+ A july-only cut can generate server tools from Workspace
2372
+ methods and pin one schema pack. That is change-monitor
2373
+ today, moved into the framework. The model would still call
2374
+ `CallMcpTool` on `custom-user-tools` / `read`.
2375
+ `tools: ["read"]` would still enable host `read`, not virtual
2376
+ `read`. Stream consumers that key on `name === "read"`
2377
+ would still miss the calls.
2378
+
2379
+ That is the problem this work exists to fix. Do not ship
2380
+ `virtualRuntime` on lookalikes and flip the binding later.
2381
+ The first code PR opens `AgentOptions.local.workspace` and
2382
+ rebounds the native executors.
2383
+
2384
+ ---
2385
+
2386
+ ## 8. Plan
2387
+
2388
+ Each phase is its own PR. Today's `runtime` string keeps
2389
+ working until a later deprecation window. Do not start Phase
2390
+ 1 until this document (Phase 0) has been reviewed.
2391
+
2392
+ ### Phase 0 — this document (this PR)
2393
+
2394
+ Publish the design. No product code.
2395
+
2396
+ Reviewers: Agent SDK and cursor-sdk maintainers; change-monitor
2397
+ owners as the first consumer.
2398
+
2399
+ Agree before any code PR:
2400
+
2401
+ - The four goals at the top (compat — old fields deprecated
2402
+ but kept, `localRuntime` default, fail-closed pairs,
2403
+ `Workspace` instead of a longer enum)
2404
+ - `Runtime` is a closed union; `workspace` only on the local
2405
+ arm
2406
+ - **Runtime arm** vs **workspace** vs **delivery**
2407
+ - Workspace is one object, optional methods, one namespace
2408
+ - Cloud/grokbot do not grow a `workspace` field until a
2409
+ machine API exists
2410
+ - Native redirection in Cursor SDK is the first code cut
2411
+ - Change-monitor is the first workspace, in a later PR
2412
+
2413
+ **Review asks** (comment on these):
2414
+
2415
+ 1. Phase 1 adds `Workspace` on **Cursor SDK**
2416
+ `Agent.create({ local: { workspace } })`
2417
+ (`LocalAgentOptions`, next to `cwd` / `dirs`). That is
2418
+ not `defineAgent({ local })`. july constructors stay in
2419
+ Phase 2. Same-shape `Agent.create({ runtime })` is a
2420
+ later SDK deprecation, not Phase 1. Agree?
2421
+ 2. Method presence as the file/shell allowlist: fail-closed
2422
+ when `tools` names a missing method?
2423
+ 3. `virtualRuntime(fs?)` only; no public
2424
+ `virtualRuntime(workspace)`? Empty default, `root()` is `/`.
2425
+ No public `search` method. `FileSystem` is the file-verb
2426
+ type. Agree?
2427
+ 4. Session factory runs once per session, always async
2428
+ (`Promise<FileSystem>`), with `SessionWorkspaceContext`
2429
+ (`sessionId`, `agentName?`, `continuationKey?`, `host?`).
2430
+ Per-wake data stays inside the `FileSystem`. Agree?
2431
+ 5. Rejecting host executors for omitted methods when a
2432
+ workspace is set (defense in depth), or tools-header only?
2433
+ 6. Harness builtins (`webSearch`, `updateTodos`, …) stay off
2434
+ Workspace and off `virtualRuntime`'s default. Opt in via
2435
+ `tools`. Cloud still refuses `tools`. Agree?
2436
+ 7. `edit` / `delete` / `semSearch` / `readLints` on Workspace
2437
+ now (optional). Phase 1 only fail-closed-omits host
2438
+ executors. Virtual bodies wait for a consumer. Agree?
2439
+ 8. `Runtime` is a closed union. `workspace` only on
2440
+ `{ kind: "local" }`. Agree?
2441
+ 9. String `runtime` + sibling `local` / `cloud` stay
2442
+ supported and are marked deprecated. Host options move
2443
+ to `localRuntime(opts)` (`AgentLocalOptions`). Published
2444
+ docs wait for Phase 4 (§9). Agree?
2445
+ 10. `send({ runtime?: CloudRuntime | LocalRuntime })`.
2446
+ Host-local + `cloudRuntime` = attach. Host-local +
2447
+ `localRuntime(opts)` = overlay. Cloud + `cloudRuntime`
2448
+ = replace payload (no merge). Grokbot + cloud refused.
2449
+ `send({ cloud })` / `send({ workspaceDir })` deprecated
2450
+ shims (`send({ cloud })` still merges).
2451
+ `send({ runtime, cloud })` fail-closed. Agree?
2452
+
2453
+ **Out of this PR:** types, executors, discovery, change-monitor
2454
+ code, published user docs.
2455
+
2456
+ ### Phase 1 — Cursor SDK: inject a workspace
2457
+
2458
+ Packages: `cursor-sdk`, `cursor-sdk-local-runtime`,
2459
+ `local-exec`. No `@cursor/july` authoring change.
2460
+
2461
+ `AgentOptions` in this phase is `@cursor/sdk`'s
2462
+ `Agent.create` bag (`packages/cursor-sdk/src/agent/options.ts`).
2463
+ It is not `defineAgent`'s `AgentLocalOptions`. Agent SDK
2464
+ does not grow a `local.workspace` field here. Phase 2's
2465
+ `virtualRuntime` writes the `FileSystem` into this SDK
2466
+ field when july builds `Agent.create({ local })`.
2467
+
2468
+ - Add `FileSystem` / `Workspace` on Cursor SDK
2469
+ `LocalAgentOptions.workspace` (next to `cwd` / `dirs`).
2470
+ Method args are `NativeArgs<T>` matching each tool schema.
2471
+ - Derive `tools` from method presence when the caller omitted
2472
+ `tools`. Intersect fail-closed when they passed one.
2473
+ - Add `overrideGrepExecutor` / `overrideLsExecutor` /
2474
+ `overrideDeleteExecutor` on `LocalResourceProvider`. Glob
2475
+ rides the grep executor. `edit` rides the write executor.
2476
+ `readLints` rides `diagnosticsExecutor`. `semSearch` has
2477
+ no local-exec override today; when a workspace is set and
2478
+ `semSearch` is omitted, do not register the host index
2479
+ tool.
2480
+ - Plumb overrides through
2481
+ `createDefaultLocalWorkspaceRuntime` → the resource
2482
+ provider factory → `createLocalExecutor`.
2483
+ - When a workspace is set, bind present methods and reject
2484
+ omitted path-namespace natives (do not leave host `read` /
2485
+ `edit` / `delete` / `semSearch` / `readLints` / `shell`
2486
+ registered behind a virtual tree). Virtual implementations
2487
+ of `edit` / `delete` / `semSearch` / `readLints` are not
2488
+ required in this PR.
2489
+ - Cloud + `local.workspace` throws, same posture as
2490
+ `tools` + cloud.
2491
+ - Unit tests: a map-backed workspace serves native `read` /
2492
+ `ls` / `grep` / `glob` through the executor adapters. No
2493
+ model turn.
2494
+
2495
+ **Not in this PR:** `defineAgent({ runtime })` object form,
2496
+ constructors on `Agent.create`, `virtualRuntime`,
2497
+ `memoryFs`, change-monitor, published docs. Presence of
2498
+ `cloud` still dispatches.
2499
+
2500
+ Acceptance:
2501
+
2502
+ - `Agent.create({ local: { workspace }, tools })` rebinds
2503
+ natives; a missing method is absent from the toolset and
2504
+ errors if invoked.
2505
+ - Existing local agents (no `workspace`) are unchanged.
2506
+ - `workspace` + `cloud` is a `ConfigurationError`.
2507
+
2508
+ ### Phase 2 — Agent SDK: `runtime` accepts a `Runtime` object
2509
+
2510
+ Packages: `@cursor/july` (`packages/agent-serve`). Depends on
2511
+ Phase 1.
2512
+
2513
+ ```ts
2514
+ runtime?: AgentRuntime | Runtime;
2515
+ local?: AgentLocalOptions; // still supported
2516
+ cloud?: AgentCloudOptions; // still supported
2517
+ ```
2518
+
2519
+ `"local"` / omitted → `localRuntime(agent.local)` (`{ kind:
2520
+ "local", workspace: localWorkspace(opts) }`; today's
2521
+ default). `"cloud"` → `cloudRuntime(agent.cloud)`.
2522
+ `"grokbot"` → `grokbotRuntime()`. Those constructors return
2523
+ the `Runtime` union in §1. JSDoc marks the string and
2524
+ sibling `local` / `cloud` `@deprecated` in favor of
2525
+ `localRuntime(opts)` / `cloudRuntime(opts)`. The fields
2526
+ stay.
2527
+
2528
+ `virtualRuntime(fs?)` lifts a `FileSystem` (or an async
2529
+ per-session factory) to `AgentOptions.local.workspace`. Empty
2530
+ `virtualRuntime()` is `/` and no file methods. Omit `tools`
2531
+ so workspace methods are the file/shell allowlist; do not
2532
+ keep `tools: []`. Discovery validates the composition rules
2533
+ in §1. `brief(ctx)` folds into the prompt. `<user_info>`
2534
+ Workspace Path is `/`. No `materializeWorkspace`.
2535
+ `local.workspaceDir` + virtual fails closed.
2536
+ `SendMessageOptions.runtime?: CloudRuntime | LocalRuntime`.
2537
+ Host-local + `cloudRuntime` attaches. Host-local +
2538
+ `localRuntime(opts)` overlays cwd / sandbox / workspaceDir.
2539
+ Virtual + either fails closed. Deprecated `send({ cloud })`
2540
+ still merges over `agent.cloud`. Deprecated
2541
+ `send({ workspaceDir })` still overlays a host-local agent.
2542
+
2543
+ `memoryFs` and `skillsFs` ship here as `FileSystem`
2544
+ helpers. The latter is opt-in.
2545
+
2546
+ **Not in this PR:** change-monitor cutover, `unionFs` unless
2547
+ a test needs it, published user-guide rewrite.
2548
+
2549
+ Acceptance:
2550
+
2551
+ - Existing string agents unchanged.
2552
+ - A fixture agent with
2553
+ `virtualRuntime(memoryFs({ "/a.txt": "hi" }))` can
2554
+ `agent-sdk call read` and get `hi` from the native `read`
2555
+ tool.
2556
+ - `virtualRuntime(...)` + `send({ runtime: cloudRuntime(...) })`
2557
+ still fails closed.
2558
+ - `send({ cloud })` still attaches a local agent to cloud
2559
+ (merge). On a cloud-default agent it still merges.
2560
+ `send({ runtime: cloudRuntime(opts) })` replaces, no merge.
2561
+ - Grokbot + `send({ cloud })` / `send({ runtime: cloudRuntime })`
2562
+ still refuses.
2563
+ - `send({ runtime: localRuntime({ workspaceDir }) })` overlays
2564
+ a host-local agent. `send({ workspaceDir })` still works.
2565
+ - `send({ runtime, cloud })` fails closed.
2566
+ - Discovery matrix: object-form runtimes produce the same
2567
+ fail-closed errors as today's string (tools + cloud,
2568
+ advertiseTools + cloud, grokbot + server tools).
2569
+
2570
+ ### Phase 3 — change-monitor adopts `virtualRuntime`
2571
+
2572
+ Depends on Phase 2. Session binding is the hard part:
2573
+ planner/executor filesystem is per-session (PR, SHA, which mounts
2574
+ exist), and `defineAgent` runs before that is known.
2575
+
2576
+ Replace `tools: []` + copied `src/vfs/tools.ts` schemas for
2577
+ `ls` / `glob` / `read` / `grep` with
2578
+ `virtualRuntime(async (ctx) => unionFs({ … }))`. Mount
2579
+ `skillsFs()` if they still want `/agent/skills`. Writable
2580
+ mounts implement native `write` / `edit` and omit
2581
+ `delete` / `semSearch` / `readLints` unless a mount earns
2582
+ them. Keep authored tools that have no FileSystem method
2583
+ (`diff`, CAS extras, plugin telemetry, tag `search` if they
2584
+ still want it). Drop `workspaceDir: "/repo"`. `root()` is
2585
+ `/`.
2586
+
2587
+ Acceptance:
2588
+
2589
+ - Change-monitor does not ship a second copy of native
2590
+ schemas.
2591
+ - Planner/executor still have no host shell and no checkout.
2592
+ - Native `read` / `ls` / `grep` / `glob` hit the mounted `FileSystem`.
2593
+
2594
+ ### Phase 4 — docs and templates
2595
+
2596
+ Ship the published-docs plan in §9. Constructors become the
2597
+ authoring vocabulary. The string and sibling `local` /
2598
+ `cloud` stay in the pages as deprecated-but-supported.
2599
+ Do this after Phase 2 is real. Do not document constructors
2600
+ that have not shipped.
2601
+
2602
+ ### After Phase 4 — not a "v2" of this type
2603
+
2604
+ This design is the end state of the authoring type.
2605
+ `Workspace` already lists every path-namespace verb. Phases
2606
+ 1–4 ship that type and the first workspaces. There is no
2607
+ second abstraction waiting behind "v1."
2608
+
2609
+ What remains is implementation of methods a given workspace
2610
+ omits, or **other products** that plug into the same
2611
+ constructors.
2612
+
2613
+ **Already in this type; a workspace implements when it can**
2614
+
2615
+ | Gap | Who fills it | Not |
2616
+ |---|---|---|
2617
+ | Virtual `edit` / `write` | Change-monitor writable mounts (Phase 3) | A new Workspace field |
2618
+ | Virtual `delete` | A mount that can delete | Host `delete` behind a virtual tree |
2619
+ | Virtual `semSearch` | A `FileSystem` that owns an index of `root()` | Host index of the serve disk |
2620
+ | Virtual `readLints` | A mount that owns diagnostics | Host LSP against a fake cwd |
2621
+ | `unionFs` as a public helper | Phase 3 if the factory needs it; otherwise when a second consumer unions mounts | A new runtime |
2622
+
2623
+ **Other designs, same constructors**
2624
+
2625
+ | Work | Home | How it uses this type |
2626
+ |---|---|---|
2627
+ | anyrun `/workspace` (`cloudFs`) | factory/change-monitor `computer-use.md` | A shell-backed mount inside `virtualRuntime(unionFs(…))`. Not `cloudRuntime`. |
2628
+ | TypeScript `script` isolate | factory/change-monitor `tools.md` §8 | Authored server tool. Not `Workspace.shell`. |
2629
+ | Cloud `tools` allowlist | Cursor cloud agent API | Then `defineAgent({ tools })` means the same on `cloudRuntime`. Fail closed until that API exists. |
2630
+ | 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. |
2631
+ | 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. |
2632
+ | `Agent.create({ runtime })` | `@cursor/sdk` after Phase 4 | Same constructors as july. Sibling `local` / `cloud` deprecated as the dispatcher. Not Phase 1. |
2633
+ | Deprecate `runtime: "local" \| "cloud"` | After Phase 4 has shipped | Additive constructors first. |
2634
+
2635
+ **Not a follow-on. Do not build.**
2636
+
2637
+ - `{ kind: "custom"; runner: AgentRunner }` on `Runtime`
2638
+ - A `Harness` type / `Runtime.webSearch()`
2639
+ - `infer()` on `Runtime`
2640
+ - `virtualRuntime(workspace: Workspace)`
2641
+ - `cloudRuntime` Workspace methods that prompt the VM
2642
+ - Lookalike server tools as a stepping stone
2643
+
2644
+ ---
2645
+
2646
+ ## 9. Documentation
2647
+
2648
+ Phase 4 work. Published Agent SDK docs live under
2649
+ `packages/agent-serve/docs/` (VitePress). They never say
2650
+ `agent-serve`. Product name is Agent SDK; package is
2651
+ `@cursor/july`; CLI is `agent-sdk`. This design file stays
2652
+ internal (`srcExclude: ["design/**"]`).
2653
+
2654
+ Do not rewrite user docs in Phases 0–3. Constructors that
2655
+ have not shipped must not appear on cursor.com / the
2656
+ VitePress site.
2657
+
2658
+ ### How we talk about runtime
2659
+
2660
+ Lead with what the reader is choosing: where the turn runs,
2661
+ and (for local) which files the model sees. Constructors
2662
+ are the vocabulary.
2663
+
2664
+ | Say | Do not say |
2665
+ |---|---|
2666
+ | `localRuntime()` runs turns on this machine | "the local loop" / `kind: "local"` |
2667
+ | `sandboxRuntime()` runs those turns inside Cursor's local sandbox | `localRuntime({ sandbox: true })` as the preferred form |
2668
+ | `cloudRuntime({ repos })` runs turns on a Cursor cloud agent | "cloud is the other filesystem" / `runtime: "cloud"` as the only form |
2669
+ | `virtualRuntime(fs)` runs locally against a filesystem you pass | `runtime: "virtual"` / `Workspace` / `Computer` |
2670
+ | String `runtime` and sibling `local` / `cloud` still work | That they are gone, or that they are the preferred form |
2671
+
2672
+ Omit internals: `Runtime` union arms, `kind`, `FileSystem` vs
2673
+ `Workspace` lift, `AgentRunner`, `virtualWorkspace`,
2674
+ `AgentOptions.local.workspace`, MCP lookalikes, executor
2675
+ overrides. Those belong in this design file and in JSDoc for
2676
+ SDK maintainers, not in the user guide.
2677
+
2678
+ `virtualRuntime` is a real user feature (tests, hosted
2679
+ trees, change-monitor-style agents). Document it as "pass
2680
+ the files the agent may see." Show `memoryFs` for tests.
2681
+ Show a mount table only when `unionFs` has shipped. Do not
2682
+ teach readers to implement `FileSystem` from scratch on the
2683
+ first page.
2684
+
2685
+ Deprecated fields get one short note and a link to the
2686
+ constructor, not a second tutorial:
2687
+
2688
+ > `runtime: "cloud"` is still accepted. Prefer
2689
+ > `cloudRuntime({ repos })`.
2690
+
2691
+ Document host options on `localRuntime({ cwd, workspaceDir,
2692
+ sandbox })`, next to `cloudRuntime({ repos })`. Sibling
2693
+ `local` / `cloud` get a deprecated note and a link.
2694
+
2695
+ ### Pages to update (Phase 4)
2696
+
2697
+ **Own the contract (rewrite the runtime sections)**
2698
+
2699
+ | Page | Change |
2700
+ |---|---|
2701
+ | [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. |
2702
+ | [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. |
2703
+ | [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. |
2704
+
2705
+ **New page**
2706
+
2707
+ | Page | Change |
2708
+ |---|---|
2709
+ | `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. |
2710
+
2711
+ **Fix examples and cross-links only**
2712
+
2713
+ | Page | Change |
2714
+ |---|---|
2715
+ | [quickstart.md](/docs/quickstart.md) | Default scaffold stays `localRuntime()` or omit `runtime` (same default). Do not mention virtual. |
2716
+ | [scaffolding-agents.md](/docs/scaffolding-agents.md) | Same. |
2717
+ | [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. |
2718
+ | [reference/tools.md](/docs/reference/tools.md) | `tools` + cloud still fail closed. Mention it works the same on `cloudRuntime`. |
2719
+ | [reference/cli.md](/docs/reference/cli.md) | `agent-sdk call read` against a virtual fixture if we add one in Phase 2. |
2720
+ | [troubleshooting.md](/docs/troubleshooting.md) | Discovery errors: string+incoherent pair, `virtualRuntime` + `workspaceDir`, hybrid attach or local overlay on virtual. |
2721
+ | `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. |
2722
+ | [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. |
2723
+
2724
+ **Do not document here**
2725
+
2726
+ - Cursor SDK `AgentOptions.local.workspace` (SDK maintainer JSDoc).
2727
+ - Change-monitor mount tables (their repo docs).
2728
+ - Grok Bot unless a public template already ships `grokbot`.
2729
+ - `kind`, `Workspace`, `FileSystem` method lists (JSDoc on
2730
+ `@cursor/sdk` is enough for authors who implement a mount).
2731
+
2732
+ ### JSDoc (Phase 2, with the types)
2733
+
2734
+ On `@cursor/july`:
2735
+
2736
+ - `runtime?: AgentRuntime | Runtime` — constructors first.
2737
+ String values `@deprecated Use localRuntime(opts) / cloudRuntime(opts) / grokbotRuntime()`.
2738
+ - `local?` — `@deprecated Use localRuntime(opts)`. Still
2739
+ read when `runtime` is omitted or `"local"`. Error with
2740
+ `virtualRuntime` or `cloudRuntime`.
2741
+ - `cloud?` — `@deprecated Use cloudRuntime(opts)`.
2742
+ - `SendMessageOptions.cloud` — `@deprecated Use
2743
+ send({ runtime: cloudRuntime(opts) })`. Compat shim still
2744
+ merges over `agent.cloud`.
2745
+ - `SendMessageOptions.workspaceDir` — `@deprecated Use
2746
+ send({ runtime: localRuntime({ workspaceDir }) })`.
2747
+ - `SendMessageOptions.runtime?: CloudRuntime | LocalRuntime`
2748
+ — attach, overlay, or replace cloud payload. Virtual +
2749
+ either, and grokbot + cloud, fail at start. Together with
2750
+ sibling `cloud` / `workspaceDir` fails closed.
2751
+
2752
+ On `@cursor/sdk` (Phase 1):
2753
+
2754
+ - New `local.workspace` documented as the injection seam.
2755
+ - Existing `local` / `cloud` blocks stay unmarked. Same-shape
2756
+ `Agent.create({ runtime })` is after Phase 4.
2757
+
2758
+ ### Deprecation window
2759
+
2760
+ Phase 4 publishes constructors and marks the string
2761
+ deprecated. A later PR, after that has shipped, may set a
2762
+ removal date. This design does not pick one.
2763
+
2764
+ ---
2765
+
2766
+ ## 10. Non-goals
2767
+
2768
+ - Rewriting the cloud agent API or making `tools` work on `bc-`
2769
+ agents. Fail closed stays until the API can honor it.
2770
+ - Unifying Grok Bot into Workspace methods.
2771
+ - Promoting `AgentRunner` onto `defineAgent` / `Runtime`.
2772
+ Replay and eval fakes keep `StartServerInternalOptions.runner`.
2773
+ - A user-authored `infer()` on `Runtime`.
2774
+ - `Runtime.instructions()` or `Runtime.read()` — those belong
2775
+ on Workspace, or they re-bundle the string.
2776
+ - A `Shell` type the caller attaches next to `FileSystem`.
2777
+ `shell` stays on `Workspace`.
2778
+ - `virtualRuntime(workspace: Workspace)`. The lift is
2779
+ internal; authors pass a `FileSystem`. Change-monitor
2780
+ `search` / tags stay authored if needed.
2781
+ - Moving server tools, MCP, or `ctx.host.files` behind
2782
+ Workspace.
2783
+ - A `runtime: "virtual"` string.
2784
+ - `SendMessageOptions.runtime?: Runtime` (the full union,
2785
+ including grokbot). The field is `CloudRuntime |
2786
+ LocalRuntime`. Honoring every pair is still a non-goal.
2787
+ - Removing string `runtime` or sibling `local` / `cloud` in
2788
+ Phases 1–4. Deprecate in docs and JSDoc; delete later.
2789
+ - Letting `cloudRuntime` pretend to implement Workspace methods
2790
+ by prompting the VM.
2791
+ - Shipping lookalike server tools as the first binding.
2792
+ - One PR that lands SDK plumbing, july constructors, and
2793
+ change-monitor together.
2794
+ - A `Harness` / `Runtime.webSearch()` surface so todos and
2795
+ search look like Workspace methods. Availability is
2796
+ `defineAgent({ tools })`. Rebind later via local executor
2797
+ overrides if a consumer needs a stub.
2798
+
2799
+ ---
2800
+
2801
+ ## 11. Proof each code PR owes
2802
+
2803
+ Phase 0 has no experiment. Later PRs prove the seam they
2804
+ open, not the whole stack.
2805
+
2806
+ 1. **Phase 1.** Cursor SDK unit test: `overrideReadExecutor`
2807
+ (and ls/grep) serve a map. `Agent.create` with an injected
2808
+ workspace advertises only the present methods.
2809
+ 2. **Phase 2.** Fixture agent with
2810
+ `virtualRuntime(memoryFs({ "/a.txt": "hi" }))`.
2811
+ `agent-sdk call read` returns `hi` from native `read`.
2812
+ Discovery matrix matches today's fail-closed errors.
2813
+ 3. **Phase 3.** Change-monitor planner/executor typecheck
2814
+ against `virtualRuntime(unionFs(…))`. No second schema copy.
2815
+ No host checkout.
2816
+
2817
+ ---
2818
+
2819
+ ## 12. Decision summary
2820
+
2821
+ | Question | Answer |
2822
+ |---|---|
2823
+ | Design goals? | Compat with today's `defineAgent` / `AgentOptions` shape. Default `localRuntime()`. Fail-closed incoherent pairs. Extensibility via `Workspace`, not a longer enum. |
2824
+ | 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. |
2825
+ | Where do file/shell verbs live? | `FileSystem` in `@cursor/sdk` (native tool names + `NativeArgs<T>`). `Workspace` adds `shell` / `root` / `brief`. No public `search` method. |
2826
+ | How do new files get created? | Native **Write** tool (`write` on FileSystem). Str-replace `edit` cannot create. Apply-patch `edit` can via `Add File`. |
2827
+ | Empty `virtualRuntime()`? | `/`, no methods, no mounts, no skills, no materialization. Application unions what it needs (`skillsFs()` is opt-in). |
2828
+ | Session filesystem? | `virtualRuntime(async (ctx) => fs)` once per session. Always `Promise<FileSystem>`. `SessionWorkspaceContext`: `sessionId`, `agentName?`, `continuationKey?`, `host?`. Per-wake data is a lazy layer. |
2829
+ | Effect / approvals? | Unchanged defaults. Gate wraps the tool call, then Workspace runs. Host path allowlists stay on `localWorkspace` only. |
2830
+ | Does method presence enable tools? | Yes, for Workspace methods. `tools` may only narrow, fail-closed. |
2831
+ | 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. |
2832
+ | Should those methods live on `Runtime`? | No. That re-bundles the arm and the workspace. |
2833
+ | Should `Runtime.instructions()` exist? | No. Workspace gets `root()` + optional session-scoped `brief()`. |
2834
+ | Should inference live on `Runtime`? | No. |
2835
+ | What about Grok Bot? | `grokbotRuntime()` — a Runtime arm, no `workspace` field. |
2836
+ | How does change-monitor fit? | `virtualRuntime(async (ctx) => unionFs({ /repo, /host, /agent/skills: skillsFs(), … }))`. |
2837
+ | How does computer-use.md's VM fit? | A `cloudFs` mount in that same union. |
2838
+ | How does `runtime: "cloud"` fit? | `cloudRuntime(opts)` — opaque Runtime arm that owns its tree. |
2839
+ | `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). |
2840
+ | What stays off `Runtime`? | `model`, `tools`, `builtinTools`, `hosting`, `architecture`, `workspaceFiles`. |
2841
+ | Subagents? | Behavior unchanged (`G12`). Internally `localRuntime()` on the parent workspace. No authoring `Runtime` on children. |
2842
+ | Can this ship without Cursor SDK changes? | No. Lookalikes put the model on MCP `CallMcpTool`, not native `read`. |
2843
+ | First PR? | This design document. |
2844
+ | First code PR? | Cursor SDK `local.workspace` + executor overrides (Phase 1). |
2845
+ | 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. |
2846
+
2847
+ The string was a convenient name for a bundle that no longer
2848
+ bundles cleanly. The replacement is a local Runtime arm you
2849
+ can point at any workspace, and two remote arms that have no
2850
+ `workspace` field until those products grow a machine API.
2851
+ Native tools bind to the injected workspace. That seam opens
2852
+ in Cursor SDK before `defineAgent` grows constructors.
2853
+
2854
+ ---
2855
+
1091
2856
  Source: /docs/evals.md
1092
2857
 
1093
2858
  # Evals
@@ -1515,7 +3280,7 @@ resolution for other file formats).
1515
3280
  `maxConcurrency` limits parallel datapoints. It does not limit model or
1516
3281
  API fan-out inside one datapoint. Materialized fixtures prevent a large
1517
3282
  suite from exhausting provider and GitHub rate limits. The
1518
- [evals skill](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/evals/SKILL.md) has the full fixture workflow.
3283
+ [evals skill](/docs/skills/evals.md) has the full fixture workflow.
1519
3284
 
1520
3285
  ## Keep improvements with regression evals
1521
3286
 
@@ -1977,7 +3742,7 @@ event stream for repos you've connected to Cursor. You still declare a
1977
3742
  `githubChannel` so hooks decide what each event does.
1978
3743
 
1979
3744
  The companion skill for coding agents is
1980
- [`skills/github/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/github/SKILL.md).
3745
+ [`skills/github/SKILL.md`](/docs/skills/github.md).
1981
3746
 
1982
3747
  ## Pull events from Cursor
1983
3748
 
@@ -2390,7 +4155,7 @@ deployment as secrets so prod can reconnect after a redeploy. Hosted
2390
4155
  Connect lets the current process retry.
2391
4156
 
2392
4157
  The companion skill is
2393
- [`skills/mcp-auth/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/mcp-auth/SKILL.md).
4158
+ [`skills/mcp-auth/SKILL.md`](/docs/skills/mcp-auth.md).
2394
4159
 
2395
4160
  ## What can host MCP OAuth do?
2396
4161
 
@@ -2642,7 +4407,7 @@ You can also pass the same object to `serve(dir, { otel })`. Precedence
2642
4407
  is `serve({ otel })` over `agent/otel.ts` over env. An empty
2643
4408
  `defineOtel()` still enables export when `OTEL_EXPORTER_OTLP_*` is set.
2644
4409
 
2645
- The companion skill is [`skills/otel/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/otel/SKILL.md).
4410
+ The companion skill is [`skills/otel/SKILL.md`](/docs/skills/otel.md).
2646
4411
 
2647
4412
  ## What spans does a session produce?
2648
4413
 
@@ -2757,7 +4522,7 @@ through `serve({ otel })` or env.
2757
4522
 
2758
4523
  ## What's next
2759
4524
 
2760
- - [`skills/otel/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/otel/SKILL.md): compact
4525
+ - [`skills/otel/SKILL.md`](/docs/skills/otel.md): compact
2761
4526
  `defineOtel` reference for coding agents
2762
4527
  - [Hooks](/docs/reference/hooks.md): observe the same session event stream
2763
4528
  in-process
@@ -2777,7 +4542,7 @@ Replies stream there, with thinking steps and suggested prompts.
2777
4542
  Use `agent-sdk slack create` when Cursor should own the Slack app. Use
2778
4543
  `agent-sdk slack init --manual` when you own it. Commands and flags live
2779
4544
  in the [CLI reference](/docs/reference/cli.md#slack). Coding agents should
2780
- follow [`skills/setup-slack/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/setup-slack/SKILL.md).
4545
+ follow [`skills/setup-slack/SKILL.md`](/docs/skills/setup-slack.md).
2781
4546
 
2782
4547
  ## Define the channel
2783
4548
 
@@ -2820,6 +4585,85 @@ export default slackChannel({
2820
4585
  });
2821
4586
  ```
2822
4587
 
4588
+ ## Choose how the reply arrives
4589
+
4590
+ Slack gives an agent two places to show progress while a turn runs: the
4591
+ status chip under the thread ("Running grep…") and the reply message
4592
+ itself. It won't fill both at once. While a message is streaming, Slack
4593
+ shows its own "is working…" chip and hides any status text you set. Cards
4594
+ only exist inside a streamed message. `reply.mode` picks which one you get.
4595
+
4596
+ ### `post` (default): status chip, then one message
4597
+
4598
+ Tool calls and the first line of each reasoning block show in the chip as
4599
+ they happen. The answer lands as a single message when the turn finishes.
4600
+
4601
+ ```ts
4602
+ export default slackChannel({
4603
+ reply: {
4604
+ mode: "post",
4605
+ status: {
4606
+ reasoning: true,
4607
+ tools: true,
4608
+ idle: ["Checking the monorepo…", "Poking Datadog…"],
4609
+ },
4610
+ },
4611
+ });
4612
+ ```
4613
+
4614
+ `idle` is the rotation Slack cycles through when nothing more specific is
4615
+ known, up to 5 lines of 50 characters. Pass a function to `tools` to write
4616
+ your own line from the calls in flight.
4617
+
4618
+ ### `stream`: live text, with cards
4619
+
4620
+ The answer streams into one message as the model writes it. Slack owns the
4621
+ chip for the duration, so live feedback inside the message comes from
4622
+ cards. Both are off unless you turn them on.
4623
+
4624
+ ```ts
4625
+ export default slackChannel({
4626
+ reply: {
4627
+ mode: "stream",
4628
+ reasoningCard: true,
4629
+ toolCards: { group: "per-tool" },
4630
+ },
4631
+ toolLabels: { grep: "Searching code", read: "Reading files" },
4632
+ });
4633
+ ```
4634
+
4635
+ The reasoning card is one task card, titled "Thinking", that receives the
4636
+ model's reasoning as it streams and completes when the answer text starts.
4637
+ `maxChars` (default 1500) caps how much it collects. Slack caps a streamed
4638
+ message near 12k characters including cards; when an answer outgrows that,
4639
+ the streamed message closes where it stands and the rest continues in a
4640
+ new message.
4641
+
4642
+ Tool cards default to one card per tool name with a call count in the title
4643
+ (`grep ×3`), updated in place as calls finish. `group: "per-call"` shows one
4644
+ card per call instead.
4645
+
4646
+ Cards are collapsible by default: each call adds one line under the title,
4647
+ the same summary the playground shows in a tool's header (the grep pattern,
4648
+ the file path, the shell command), and the group expands the same way the
4649
+ reasoning card does. `collapsible: false` keeps cards to their titles. On a
4650
+ collapsible card, `details(call)` returns the line to add when a call starts
4651
+ and `output(result)` the text to add when it finishes.
4652
+
4653
+ `toolLabels` renames tools everywhere they appear: the chip, card titles,
4654
+ and the default `Running …` line.
4655
+
4656
+ ### Migrating from `streaming` and `thinkingSteps`
4657
+
4658
+ `streaming: false` is `reply: { mode: "post" }`. `streaming: true` is
4659
+ `reply: { mode: "stream", toolCards: { group: "per-call", collapsible:
4660
+ false } }`, the title-only cards it used to show. `loadingMessages` moves
4661
+ to `reply.status.idle`. The old options still work and `agent-sdk validate`
4662
+ prints the rewrite for each one.
4663
+
4664
+ A channel that set none of these used to stream. It now posts. Set
4665
+ `reply: { mode: "stream" }` to keep streaming.
4666
+
2823
4667
  ## Set it up
2824
4668
 
2825
4669
  `slack create` opens the Cursor dashboard wizard. Finish Slack consent
@@ -3451,7 +5295,7 @@ flowchart LR
3451
5295
 
3452
5296
  ## How do I hillclimb an agent with a coding agent?
3453
5297
 
3454
- Have Cursor read [`skills/hillclimb/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/hillclimb/SKILL.md).
5298
+ Have Cursor read [`skills/hillclimb/SKILL.md`](/docs/skills/hillclimb.md).
3455
5299
 
3456
5300
  Tell it:
3457
5301
 
@@ -3465,10 +5309,10 @@ Other skills cover the edges:
3465
5309
 
3466
5310
  | When you need… | Skill |
3467
5311
  | --- | --- |
3468
- | The measured improvement loop | [`skills/hillclimb/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/hillclimb/SKILL.md) |
3469
- | An eval that locks a kept win | [`skills/evals/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/evals/SKILL.md) |
3470
- | Repeatable GitHub webhook inputs | [`skills/github/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/github/SKILL.md) |
3471
- | A run that misbehaves | [`skills/debug/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/debug/SKILL.md) |
5312
+ | The measured improvement loop | [`skills/hillclimb/SKILL.md`](/docs/skills/hillclimb.md) |
5313
+ | An eval that locks a kept win | [`skills/evals/SKILL.md`](/docs/skills/evals.md) |
5314
+ | Repeatable GitHub webhook inputs | [`skills/github/SKILL.md`](/docs/skills/github.md) |
5315
+ | A run that misbehaves | [`skills/debug/SKILL.md`](/docs/skills/debug.md) |
3472
5316
 
3473
5317
  See [Building agents with agents](/docs/building-with-agents.md) for every framework skill and a good first prompt.
3474
5318
 
@@ -3673,7 +5517,7 @@ Confirm `agent-sdk whoami` shows the expected account.
3673
5517
  ## Related documentation
3674
5518
 
3675
5519
  - Package reference: [`README.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/README.md)
3676
- - Coding-agent workflows: [`skills/`](https://github.com/cursor/cursor/tree/main/packages/agent-serve/skills/)
5520
+ - Coding-agent workflows: [`skills/`](/docs/skills/index.md)
3677
5521
 
3678
5522
  ---
3679
5523
 
@@ -3689,7 +5533,7 @@ review. Add GitHub event handling so pull requests can trigger reviews.
3689
5533
 
3690
5534
  - **Get started with an agent in Cursor:** follow
3691
5535
  [Scaffold an agent with Cursor](/docs/scaffolding-agents.md) and ask Cursor
3692
- to read [`skills/create-agent/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/create-agent/SKILL.md).
5536
+ to read [`skills/create-agent/SKILL.md`](/docs/skills/create-agent.md).
3693
5537
  - **Get started in the CLI:** continue below.
3694
5538
 
3695
5539
  ## Prerequisites
@@ -5260,7 +7104,7 @@ commands with an explicit `--json` flag skip this automatic check.
5260
7104
  agent-sdk deploy [--dir <path>] [--slug <slug> | --all] [--team <id>]
5261
7105
  [--repo <https-url>] [--ref <git-ref>] [--path <agent-path>]
5262
7106
  [--cursor-events-repo owner/name]...
5263
- [--allow-domain <domain>]... [--multi-tenant]
7107
+ [--allow-domain <domain>]...
5264
7108
  [--no-wait] [--json]
5265
7109
  ```
5266
7110
 
@@ -5288,12 +7132,6 @@ be a lowercase hostname with at least two labels and an alphabetic
5288
7132
  top-level domain. One leading `*.` wildcard is allowed. A deployment
5289
7133
  can declare at most 20 domains.
5290
7134
 
5291
- `--multi-tenant` adds a release to an existing Cursor-managed
5292
- (multi-tenant) product such as `security-reviewer`. It is valid only
5293
- for `architecture: "v2"` and cannot create a new multi-tenant
5294
- application — those stay on the operator seed path. Without the flag,
5295
- HTTP deploy stays single-tenant and a managed slug 409s.
5296
-
5297
7135
  By default, the command polls every three seconds for up to ten minutes
5298
7136
  and succeeds only when the deployment reaches `running`. `--no-wait`
5299
7137
  returns after the deployment request is accepted. Multi-agent deploys
@@ -5698,7 +7536,7 @@ agent-sdk mcp oauth inventory --store # also upsert deployment secrets
5698
7536
  ```
5699
7537
 
5700
7538
  Full walkthrough: [Host MCP OAuth](/docs/guides/mcp-oauth.md). Companion
5701
- skill: [`skills/mcp-auth/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/mcp-auth/SKILL.md).
7539
+ skill: [`skills/mcp-auth/SKILL.md`](/docs/skills/mcp-auth.md).
5702
7540
 
5703
7541
  Account MCP (`cursorAccount: true`) is the right choice for connectors
5704
7542
  already linked in the Cursor dashboard. Omit `servers` (or pass `"*"`)
@@ -6074,6 +7912,7 @@ and `playgroundUrl` deep-links the session in the playground.
6074
7912
  | `message` | Required user message |
6075
7913
  | `title` | Session title |
6076
7914
  | `dryRun` | Run read tools and stub write tools |
7915
+ | `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 |
6077
7916
  | `workspaceFiles` | UTF-8 files written into the session workspace |
6078
7917
  | `cloud` | Per-session cloud options merged over the agent defaults |
6079
7918
 
@@ -7282,6 +9121,31 @@ export default defineTool({
7282
9121
  `dryRunResult` keeps the result shape stable. Without it, a stubbed write
7283
9122
  returns `"Operation acknowledged."`. Traces mark the result as stubbed.
7284
9123
 
9124
+ ### Bound time arguments under `asOf`
9125
+
9126
+ Declare `timeArgs` with the input properties that carry time bounds. In a
9127
+ session created with `asOf`, every named argument must be an absolute
9128
+ ISO-8601 instant with a timezone, no later than the session's frozen
9129
+ instant. A call that omits one, or supplies a relative or later value, is
9130
+ refused with an error telling the model to retry with absolute bounds.
9131
+ Sessions without `asOf` ignore the declaration.
9132
+
9133
+ ```ts
9134
+ export default defineTool({
9135
+ description: "Query telemetry between two instants.",
9136
+ inputSchema: z.object({ since: z.string(), until: z.string() }),
9137
+ effect: "read",
9138
+ timeArgs: ["since", "until"],
9139
+ async execute({ since, until }) {
9140
+ return queryTelemetry(since, until);
9141
+ },
9142
+ });
9143
+ ```
9144
+
9145
+ Every name in `timeArgs` must exist in the input schema; a session with
9146
+ `asOf` refuses to start otherwise, because a misspelled name would silently
9147
+ check nothing.
9148
+
7285
9149
  ## Define an agent tool
7286
9150
 
7287
9151
  Set `execution: "agent"` and the tool materializes as a shell script
@@ -7467,7 +9331,7 @@ decision.
7467
9331
  ## What does the create-agent skill do?
7468
9332
 
7469
9333
  The bundled
7470
- [`create-agent` skill](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/create-agent/SKILL.md) turns your goal
9334
+ [`create-agent` skill](/docs/skills/create-agent.md) turns your goal
7471
9335
  into a small working project. Have Cursor read that file and follow it.
7472
9336
 
7473
9337
  Where to find the file depends on how you got the package:
@@ -7482,7 +9346,7 @@ Where to find the file depends on how you got the package:
7482
9346
  - Installed `@cursor/july` as a dependency? The skill also ships inside
7483
9347
  the package at `node_modules/@cursor/july/skills/create-agent/SKILL.md`.
7484
9348
  - Working from this package's source? The skill is at
7485
- [`skills/create-agent/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/create-agent/SKILL.md). Run
9349
+ [`skills/create-agent/SKILL.md`](/docs/skills/create-agent.md). Run
7486
9350
  `agent-sdk install-skills` if you want the same copies in
7487
9351
  `~/.cursor/skills/agentsdk/` (the package postinstall skips the
7488
9352
  source checkout).
@@ -7575,7 +9439,7 @@ without a Cursor credential. Model turns and evals need
7575
9439
 
7576
9440
  Choose one to three fixed inputs, define what should improve, and name
7577
9441
  what must stay unchanged. Then have Cursor follow
7578
- [`skills/hillclimb/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/hillclimb/SKILL.md).
9442
+ [`skills/hillclimb/SKILL.md`](/docs/skills/hillclimb.md).
7579
9443
 
7580
9444
  The hillclimb skill measures a baseline, changes one lever, runs the same
7581
9445
  inputs again, and adds an eval for each improvement you keep.
@@ -7591,6 +9455,935 @@ inputs again, and adds an eval for each improvement you keep.
7591
9455
 
7592
9456
  ---
7593
9457
 
9458
+ Source: /docs/skills/ab.md
9459
+
9460
+ # Agent SDK A/B metrics (`defineAB`)
9461
+
9462
+ Live metrics plug-in. No `agent-sdk ab` CLI. No assertion API.
9463
+ Reference: `docs/ab.md`.
9464
+
9465
+ | | `defineEval` | `defineAB` |
9466
+ | --- | --- | --- |
9467
+ | Job | Gates on frozen fixtures | Metrics on live runs |
9468
+ | Location | `evals/**/*.eval.ts` | `agent/ab.ts` or `agent/ab/<name>.ts` |
9469
+ | How it runs | `agent-sdk eval` | Under `serve` / `run` |
9470
+
9471
+ ```ts
9472
+ import { defineAB, splitBySessionHash } from "@cursor/july/ab";
9473
+
9474
+ export default defineAB({
9475
+ name: "concise-instructions",
9476
+ variants: {
9477
+ control: { label: "Baseline" },
9478
+ treatment: {
9479
+ label: "Shorter",
9480
+ instructions: "Keep replies to one short paragraph.",
9481
+ },
9482
+ },
9483
+ split: splitBySessionHash({ holdout: 0.1 }),
9484
+ derive: {
9485
+ weatherCalls: (event) =>
9486
+ event.type === "action.result" && event.data.toolName === "get_weather"
9487
+ ? 1
9488
+ : null,
9489
+ },
9490
+ onSample(sample) {
9491
+ console.log(sample.variant, sample.metrics.toolCalls, sample.metrics.wallTimeMs);
9492
+ },
9493
+ });
9494
+ ```
9495
+
9496
+ ```ts
9497
+ async execute(input, ctx) {
9498
+ if (ctx.session.abs?.["concise-instructions"] === "treatment") {
9499
+ // treatment-specific behavior
9500
+ }
9501
+ }
9502
+ ```
9503
+
9504
+ Enrollment is at session creation. Eval sessions skip it. Do not
9505
+ use `splitIf` to filter evals. Split helpers and `onSample`
9506
+ fields: `docs/ab.md`.
9507
+
9508
+ Pick a name, arm labels, a split, and a real `onSample` sink. Do
9509
+ not invent credentials.
9510
+
9511
+ ---
9512
+
9513
+ Source: /docs/skills/create-agent.md
9514
+
9515
+ # Create an Agent SDK agent
9516
+
9517
+ 1. **Interview.** Two `AskQuestion` rounds, then a plan gate.
9518
+ 2. **Scaffold.** `agent-sdk init`, then shape the files.
9519
+ 3. **Verify.** `validate` / `info` / `call`, then a model turn.
9520
+ 4. **Channels.** Slack, GitHub, webhook, or schedule as chosen.
9521
+ 5. **Hillclimb.** `skills/hillclimb/SKILL.md`.
9522
+
9523
+ Read `skills/framework-map/SKILL.md` if you have not. CLI is
9524
+ `agent-sdk`. Public docs:
9525
+ `node_modules/@cursor/july/dist/docs/llms.txt` or `/docs/llms.txt`
9526
+ on a running host.
9527
+
9528
+ ## Interview
9529
+
9530
+ `AskQuestion`: batched, recommended option first and labeled
9531
+ `(recommended)`.
9532
+
9533
+ - Mine the invoking message. Target: two rounds, then files.
9534
+ - Round 1 is identity. Round 2 is the surface, tailored by round 1.
9535
+ - Multi-select for channels, MCP, capabilities. Other where a
9536
+ custom answer is plausible.
9537
+ - Ask decisions, not how to build it. File layout, tool names,
9538
+ env prefix, and eval shape are yours.
9539
+ - If `AskQuestion` is missing or the user skips, state defaults
9540
+ in one list and proceed.
9541
+
9542
+ ### Round 1: identity
9543
+
9544
+ | Question | Options |
9545
+ | --- | --- |
9546
+ | **Purpose** | chat assistant · PR / repo automation · domain assistant over APIs/tools · scheduled reporter · Other |
9547
+ | **Name** | 2–3 slugs from the purpose + Other. Directory: `[A-Za-z0-9][A-Za-z0-9_-]*`, not `v1`/`playground`/`docs` |
9548
+ | **Location** | `./<slug>` (default) · another directory |
9549
+
9550
+ ### Round 2: surface
9551
+
9552
+ | Question | Options | Guidance |
9553
+ | --- | --- | --- |
9554
+ | **Runtime** | `local` (default) · `cloud` | Cloud needs `cloud.repos`. Approvals and sandbox seeds are local only. Recommend local unless the job needs a cloud checkout. |
9555
+ | **Model** | `grok-4.5` + `effort=high`, `fast=true` · Other id | Params, not id suffixes: `{ id: "grok-4.5", params: [{ id: "effort", value: "high" }, { id: "fast", value: "true" }] }` |
9556
+ | **Channels** (multi) | playground + HTTP (always on) · Slack · GitHub · custom webhook · schedule | Slack: `skills/setup-slack/SKILL.md`. GitHub: `skills/github/SKILL.md`. No Discord/Teams pack; offer custom webhook. |
9557
+ | **MCP** (multi) | none · remote URL · local stdio · Cursor account connectors · Other | One file under `agent/mcp-connections/`. Account file is `account.ts`, never `cursor.ts`. Named local tools need `advertiseTools: true`. Privileged servers go in `agent/host-connections/`. |
9558
+ | **Capabilities** (multi) | server tools · agent tools · skills · subagents · hooks · sandbox seeds · approvals · evals (recommend) | Always recommend one smoke eval. |
9559
+
9560
+ Close with the plan and file tree, then "Scaffold it" / "Adjust
9561
+ something". Write files only after approval.
9562
+
9563
+ ## Fill the blanks
9564
+
9565
+ | Purpose | Shape |
9566
+ | --- | --- |
9567
+ | Slack chat | `slackChannel({ envPrefix })` + suggested prompts. `agent-sdk slack create` mints the bot. Prefix: `skills/setup-slack/SKILL.md` |
9568
+ | PR review with host prep | Channel `callTool` into a trimmed workspace `pr/` tree |
9569
+ | GitHub automation | `githubChannel()` hooks, replay fixtures |
9570
+ | Domain assistant | Server tools + one MCP connection + a skill |
9571
+ | Wrap an existing pipeline | GitHub `{ task }` over a thin `agent/lib/` loop |
9572
+
9573
+ Defaults:
9574
+
9575
+ - Instructions + 1–2 tools + chosen channels + one smoke eval.
9576
+ - `local.cwd` stays outside a monorepo checkout (default: a cache
9577
+ directory under `~/.cache`) unless the agent must inherit that
9578
+ tree.
9579
+ - `agent/instructions.md`: identity, when to use which tool,
9580
+ output shape.
9581
+ - One tool per file. Prefer `execution: "server"` + zod
9582
+ `inputSchema`. Gate side effects with `needsApproval: true`.
9583
+ - Long prompts: `prompt\`…\`` / `prompt.lines\`…\`` from
9584
+ `@cursor/july`.
9585
+ - Host prep is for evidence (`callTool`, `workspaceFiles`), not
9586
+ judgment, formatting, or replies.
9587
+
9588
+ ### Deterministic-path budget
9589
+
9590
+ Default to instructions and skills. Promote to TypeScript only
9591
+ when it earns the left column:
9592
+
9593
+ | Code owns | Model owns |
9594
+ | --- | --- |
9595
+ | Side-effect gates (approve, deploy, post) | Human-facing output to a stated shape |
9596
+ | Dedupe of external writes | Summarizing, classifying, wording |
9597
+ | Auth and signature checks | What to do next from ground truth |
9598
+ | Evidence seeding | Soft-failure retries |
9599
+ | Hard API caps | Formatting under those caps |
9600
+
9601
+ Smells: prose builders in `.ts` (write `.md`); `agent/lib/` +
9602
+ `tools/` dwarfing instructions + skills on a first cut.
9603
+
9604
+ ## Scaffold
9605
+
9606
+ ```bash
9607
+ agent-sdk init ./<slug>
9608
+ ```
9609
+
9610
+ `init` writes the project, runs `npm install`, and may wait on
9611
+ `login`. Then shape it:
9612
+
9613
+ 1. `defineAgent` with the chosen model/runtime. Add
9614
+ `cloud: { repos: [...] }` for cloud.
9615
+ 2. Real `instructions.md`. Replace `echo.ts`.
9616
+ 3. Chosen `channels/`, `mcp-connections/`, `skills/`,
9617
+ `subagents/<id>/` (needs `description`), `schedules/`, `hooks/`.
9618
+ Slack: `agent-sdk slack create --dir ./<slug> --name "<Name>"`,
9619
+ not a hand-written channel. Manual Slack app:
9620
+ `agent-sdk slack init --manual` (Slack CLI, or paste at
9621
+ api.slack.com).
9622
+ 4. `evals/evals.config.ts` with `maxConcurrency: 20` (required;
9623
+ cap 200) plus `evals/**/*.eval.ts`. Assert `t.succeeded()` +
9624
+ `t.calledTool(...)`. API: `skills/evals/SKILL.md`.
9625
+
9626
+ Stay on deps the Agent SDK already ships (`zod`,
9627
+ `@modelcontextprotocol/sdk`, `tsx`).
9628
+
9629
+ ## Verify
9630
+
9631
+ ```bash
9632
+ agent-sdk validate --dir ./<slug>
9633
+ agent-sdk info --dir ./<slug> --json
9634
+ agent-sdk call <tool> --dir ./<slug> --input '{…}'
9635
+ agent-sdk eval --dir ./<slug> --list
9636
+ npx tsc --noEmit -p ./<slug>
9637
+ ```
9638
+
9639
+ tsx does not typecheck. Tool `execute` must return JSON-shaped
9640
+ values: object literals or `type` aliases, not `interface` types.
9641
+
9642
+ Model turns need `CURSOR_API_KEY`. Without one, finish the
9643
+ key-free checks, confirm `run` fails with only the API-key error,
9644
+ and hand these over:
9645
+
9646
+ ```bash
9647
+ agent-sdk run --dir ./<slug> --message "<fixture prompt>"
9648
+ agent-sdk serve --dir ./<slug> --mode single --dev
9649
+ # http://127.0.0.1:3000/playground
9650
+ agent-sdk eval --dir ./<slug>
9651
+ ```
9652
+
9653
+ Serve only this agent's directory. Session files land in the
9654
+ project state directory (`--state-root`). That is not harness cwd.
9655
+
9656
+ ## Channel setup
9657
+
9658
+ - **Slack.** `skills/setup-slack/SKILL.md`
9659
+ - **GitHub.** `skills/github/SKILL.md`
9660
+ - **Custom webhook.** `POST /v1/channels/<id>/<route>`.
9661
+ Loopback-only until you add `bearerAuth(...)`.
9662
+ - **Schedules.** Never auto-fire under `--dev`.
9663
+ `POST /v1/dev/schedules/<id>`.
9664
+
9665
+ ## Hillclimb handoff
9666
+
9667
+ Once a smoke turn passes, agree fixtures, success criteria, and a
9668
+ freeze line, then follow `skills/hillclimb/SKILL.md`. Seed from
9669
+ the smoke session under the project state directory. GitHub:
9670
+ snapshot `agent-sdk github replay ... --dry-run --out fixtures/github`.
9671
+
9672
+ Do not deploy or post to real Slack/GitHub beyond the smoke the
9673
+ user asked for. If the plan grows past ~10 files, cut scope.
9674
+ Re-check the budget at hand-off.
9675
+
9676
+ ---
9677
+
9678
+ Source: /docs/skills/debug.md
9679
+
9680
+ # Debugging the Agent SDK locally
9681
+
9682
+ Local `agent-sdk serve`. Hosted start or health failures:
9683
+ `docs/troubleshooting.md` and `docs/deployment.md`.
9684
+
9685
+ 1. **Validate.** `agent-sdk validate --dir <project>`
9686
+ 2. **Info.** `curl -s http://127.0.0.1:3000/<slug>/v1/info | jq .`
9687
+ 3. **Trace.** Session events under the project state directory.
9688
+ Then match the table.
9689
+
9690
+ | Symptom | Fix |
9691
+ | --- | --- |
9692
+ | Playground blank / "no agents" | Start `serve`. A built SPA with no backend serves nothing. |
9693
+ | Playground UI edits do not show | Open the URL `serve --dev` prints as `playground`, not `:3000`. |
9694
+ | Sessions on disk, empty playground list | List is the calling principal. `--dev` or `--allow-anonymous` shows all. Or `?sessionId=ses_...`. |
9695
+ | Built-in read/grep fail; retry loops | Bun. Rerun under Node. The tell is `NGHTTP2_FRAME_SIZE_ERROR`. |
9696
+ | `github forward` 401s; hook created | Blank `GITHUB_TOKEN`/`GH_TOKEN`. Relay uses `gh` login. |
9697
+ | `Hook already exists` | One forwarder per repo. `forward --dir <parent>`. |
9698
+ | Answers cite ancestor `AGENTS.md` | Nested checkout. Default `local.cwd` is a cache directory under `~/.cache`. |
9699
+ | Model lists IDE `cursor` tools, never MCP | `advertiseTools: true`. Check `GET /v1/info`. |
9700
+ | Port 3000 in use | `lsof -iTCP:3000 -sTCP:LISTEN` and kill that pid. |
9701
+ | Approval vanished after restart | Parked calls do not survive restart. Re-run. |
9702
+ | Schedule / reminder silent under `--dev` | Dev never auto-fires. `POST /<slug>/v1/dev/schedules/<id>`. |
9703
+ | `409` on follow-up | Stale `continuationToken`, busy session, or a task session. |
9704
+ | `409 session_busy` on `call --session` | Wait, or drop `--session`. |
9705
+ | `403` on stream | Wrong principal. Same auth as create; beyond loopback send `--bearer-token`. |
9706
+ | Works on loopback, blocked via tunnel | `localDevStrict()` rejects forwarded headers. Use `--bearer-token`. Never `--allow-anonymous` with account MCP. |
9707
+ | Slack `channel idle … missing credentials` | Expected. `slack doctor --prefix <PREFIX>`. |
9708
+ | Immediate API-key error | Model turns need `CURSOR_API_KEY`. |
9709
+ | Approvals or sandbox seeds missing | `runtime: "cloud"`. Those are local only. `validate` warns. |
9710
+ | `validate` clean, CI typecheck fails | tsx skipped types. JSON-shaped returns; `type` not `interface`. |
9711
+
9712
+ Count `action.result` by `toolName` before blaming latency.
9713
+ `turn.failed` + `"turn interrupted"` is a follow-up or stop, not a
9714
+ crash. `agent-sdk trajectory --events <file>` renders a saved
9715
+ trace.
9716
+
9717
+ ---
9718
+
9719
+ Source: /docs/skills/evals.md
9720
+
9721
+ # Agent SDK evals
9722
+
9723
+ Fixed input, model turn, gates on the trajectory. Files live at
9724
+ project-root `evals/**/*.eval.ts`. `agent/evals/` is ignored.
9725
+
9726
+ Live traffic variants: `skills/ab/SKILL.md`. That is not a test
9727
+ runner.
9728
+
9729
+ ```bash
9730
+ agent-sdk eval --dir . --list
9731
+ agent-sdk eval --dir . --json
9732
+ agent-sdk eval --dir . weather/nyc
9733
+ agent-sdk eval --dir . --tag smoke
9734
+ ```
9735
+
9736
+ | Form | Case id |
9737
+ | --- | --- |
9738
+ | `evals/weather.eval.ts` + `test` | `weather` |
9739
+ | `evals/weather/nyc.eval.ts` + `test` | `weather/nyc` |
9740
+ | `evals/weather.eval.ts` + `{ id: "nyc" }` | `weather/nyc` |
9741
+
9742
+ `eval` boots an ephemeral server and a temp state root. `--url`
9743
+ points at a running agent. Model turns need `CURSOR_API_KEY`.
9744
+
9745
+ ## Seeding
9746
+
9747
+ Creating or expanding cases: `AskQuestion` first.
9748
+
9749
+ | Question | Options |
9750
+ | --- | --- |
9751
+ | **How should we get eval samples?** | Generate test eval samples for me `(recommended)` · I will add / upload the data manually |
9752
+
9753
+ 1. **Manual.** They provide files or paste. Show the shape below.
9754
+ Do not invent cases. Then `eval --list` and wire gates.
9755
+ API-backed pointers (PR URLs, SHAs, gold labels): materialize
9756
+ under `fixtures/` first.
9757
+ 2. **Generated.** Ask count (`3` recommended). Append to an
9758
+ existing `cases` array when it fits. Never overwrite or weaken
9759
+ a datapoint. Create `evals/evals.config.ts` if missing
9760
+ (`maxConcurrency: 20`; cap 200).
9761
+
9762
+ ## API
9763
+
9764
+ ```ts
9765
+ import { defineEval, includes, satisfies } from "@cursor/july/evals";
9766
+
9767
+ export default defineEval({
9768
+ tags: ["smoke", "weather"],
9769
+ cases: [
9770
+ {
9771
+ id: "nyc",
9772
+ description: "NYC temperature.",
9773
+ async test(t) {
9774
+ await t.send("What's the temperature in NYC?");
9775
+ t.succeeded();
9776
+ t.calledTool("get_weather");
9777
+ t.notCalledTool("save_weather_note");
9778
+ t.check(t.reply, includes(/°|[FC]/));
9779
+ },
9780
+ },
9781
+ ],
9782
+ });
9783
+ ```
9784
+
9785
+ ```ts
9786
+ import { defineEvalConfig } from "@cursor/july/evals";
9787
+
9788
+ export default defineEvalConfig({
9789
+ maxConcurrency: 20,
9790
+ });
9791
+ ```
9792
+
9793
+ Either `test(t)` or `cases`, not both. `t.send` waits for park/fail.
9794
+ `workspaceFiles` seeds the first turn. Assert with `t.succeeded()`,
9795
+ `calledTool` / `notCalledTool`, `t.check(t.reply, …)`, `t.metric`.
9796
+
9797
+ ## What to gate
9798
+
9799
+ Decisions and shape, not prose.
9800
+
9801
+ 1. `t.succeeded()` first
9802
+ 2. Intended tool + the tempting wrong one
9803
+ 3. A shape regex or `satisfies` on parsed fields
9804
+ 4. If formatting keeps failing, tighten instructions. Do not move
9805
+ rendering into a host tool.
9806
+
9807
+ Anti-patterns: exact phrasing; more than ~5 gates (split); live
9808
+ drifting inputs (pin them).
9809
+
9810
+ | Surface | Fixture |
9811
+ | --- | --- |
9812
+ | Chat | One frozen prompt |
9813
+ | Tool-heavy | `agent-sdk call` first, then the prompt |
9814
+ | GitHub | `github replay … --dry-run --out fixtures/github` |
9815
+ | Host-prep PR review | A team-owned PR; gate findings shape, not counts |
9816
+ | Workspace | `workspaceFiles` in `t.send` |
9817
+
9818
+ Every kept hillclimb change lands an eval that would have failed
9819
+ before it. Never weaken a gate to pass a round.
9820
+
9821
+ ---
9822
+
9823
+ Source: /docs/skills/framework-map.md
9824
+
9825
+ # Agent SDK framework map
9826
+
9827
+ `@cursor/july` discovers files under `agent/` and serves the agent
9828
+ over HTTP, Slack, and GitHub. Markdown is prose. TypeScript is typed
9829
+ behavior. Ground truth: package `README.md` and `AGENTS.md`.
9830
+
9831
+ CLI is `agent-sdk` (Node, never Bun).
9832
+
9833
+ Public docs: `node_modules/@cursor/july/dist/docs/llms.txt` or
9834
+ `/docs/llms.txt` on a running host.
9835
+
9836
+ ## Invariants
9837
+
9838
+ 1. **Node 22.13+, never Bun.** Bun corrupts harness tool-result
9839
+ streams (`NGHTTP2_FRAME_SIZE_ERROR`).
9840
+ 2. **Evals live at project-root `evals/`.** `agent/evals/` is ignored.
9841
+ 3. **tsx does not typecheck.** Tool `execute` must return JSON-shaped
9842
+ values: object literals or `type` aliases, not `interface` types.
9843
+ 4. **Nested git checkouts.** Discovery sets `local.cwd` to a
9844
+ per-project cache directory under `~/.cache`. Point cwd at a
9845
+ checkout only when the agent must inherit that tree.
9846
+ 5. **Attached MCP is nameless** until `advertiseTools: true`.
9847
+ 6. **Model turns need `CURSOR_API_KEY`.** `validate`, `info`, `call`,
9848
+ and `serve` bring-up do not.
9849
+
9850
+ ## Folder structure
9851
+
9852
+ Path is identity. Full list: README "Folder structure".
9853
+
9854
+ | Path | Role |
9855
+ | --- | --- |
9856
+ | `agent/agent.ts` | `defineAgent({ model?, runtime?, cloud?, local? })` |
9857
+ | `agent/instructions.md` | Always-on system prompt (required) |
9858
+ | `agent/tools/<name>.ts` | One tool. `execution: "server"` or `"agent"` |
9859
+ | `agent/skills/*` | On-demand procedures |
9860
+ | `agent/mcp-connections/<name>.ts` | MCP. Never name an account file `cursor.ts`. `advertiseTools: true` for named local tools |
9861
+ | `agent/host-connections/<name>.ts` | Privileged MCP for `ctx.host.mcp` / `mcp oauth` |
9862
+ | `agent/subagents/<id>/` | Child agent (`description` required) |
9863
+ | `agent/channels/*.ts` | Slack / GitHub / custom HTTP |
9864
+ | `agent/hooks/*.ts` | Observe-only |
9865
+ | `agent/ab.ts` or `agent/ab/*.ts` | Live A/B (`defineAB`) |
9866
+ | `agent/otel.ts` | OpenTelemetry (`defineOtel`) |
9867
+ | `agent/schedules/*` | Cron. Never auto-fire under `--dev` |
9868
+ | `agent/sandbox/workspace/` | Session seed files (local only) |
9869
+ | `agent/lib/` | Import-only. Never discovered |
9870
+ | `evals/**/*.eval.ts` | Case id is the path under `evals/` |
9871
+
9872
+ ## Local vs cloud
9873
+
9874
+ `runtime: "local"` (default) runs on the serve host.
9875
+ `runtime: "cloud"` needs `cloud: { repos: [...] }`.
9876
+
9877
+ | Capability | local | cloud |
9878
+ | --- | --- | --- |
9879
+ | Server tools | yes | yes on managed hosting; self-hosted needs `--public-url` |
9880
+ | Tool approvals | yes | no |
9881
+ | Agent tools / skills | yes | yes |
9882
+ | sandbox seeds | yes | no |
9883
+ | Checkout | you arrange it | the VM carries it |
9884
+
9885
+ Use cloud when the job needs a checkout at scale. `validate` warns
9886
+ when cloud is combined with local-only capabilities.
9887
+
9888
+ ## Sessions
9889
+
9890
+ - **continuationToken** continues a conversation. HTTP follow-ups
9891
+ rotate it. Stale tokens return `409`.
9892
+ - **sessionId** is the inspect handle
9893
+ (`GET /v1/session/:id/stream?startIndex=N`).
9894
+
9895
+ A follow-up to a busy HTTP/MCP session interrupts the in-flight
9896
+ turn. Slack coalesces. Routes: `docs/reference/http-api.md`.
9897
+ Session files live under the project state directory
9898
+ (`--state-root`).
9899
+
9900
+ ## Where logic belongs
9901
+
9902
+ Code: side-effect gates, write dedupe, auth, evidence seeding, hard
9903
+ API caps. Model: formatting, summarizing, classification, replies.
9904
+ Budget: `skills/create-agent/SKILL.md`.
9905
+
9906
+ Loop: `validate` / `info` / `call` / `run` / `eval` / `serve`. Serve
9907
+ only this agent's directory.
9908
+
9909
+ | Task | Skill |
9910
+ | --- | --- |
9911
+ | Scaffold | `skills/create-agent/SKILL.md` |
9912
+ | Evals | `skills/evals/SKILL.md` |
9913
+ | Live A/B | `skills/ab/SKILL.md` |
9914
+ | OpenTelemetry | `skills/otel/SKILL.md` |
9915
+ | GitHub | `skills/github/SKILL.md` |
9916
+ | Slack | `skills/setup-slack/SKILL.md` |
9917
+ | Host MCP OAuth | `skills/mcp-auth/SKILL.md` |
9918
+ | Local triage | `skills/debug/SKILL.md` |
9919
+ | Measured improvement | `skills/hillclimb/SKILL.md` |
9920
+
9921
+ ---
9922
+
9923
+ Source: /docs/skills/github.md
9924
+
9925
+ # GitHub channels in the Agent SDK
9926
+
9927
+ Author `agent/channels/github.ts`. Production wakes:
9928
+ `cursorAccount` + `serve --cursor-events`. No public webhook URL.
9929
+ Use the HTTP route for fixtures, replay, and hosts that already
9930
+ terminate GitHub webhooks.
9931
+
9932
+ Guide: `docs/guides/github.md`.
9933
+
9934
+ ```ts
9935
+ import { defaultGitHubAuth, githubChannel } from "@cursor/july/channels/github";
9936
+
9937
+ export default githubChannel({
9938
+ botName: "my-agent",
9939
+ cursorAccount: { repos: ["owner/repo"] }, // permissions?: "read" | "pr-write" | "contents-write"
9940
+
9941
+ onPullRequest: (ctx, pr) =>
9942
+ pr.action === "opened" ? { auth: defaultGitHubAuth(ctx) } : null,
9943
+ onCheckSuite: (ctx, suite) =>
9944
+ suite.conclusion === "failure" ? { task: () => triage(ctx) } : null,
9945
+ });
9946
+ ```
9947
+
9948
+ Hooks: `onPullRequest`, `onComment`, `onIssue`, `onCheckSuite`,
9949
+ `onCheckRun`, `onWorkflowRun`, `onStatus`, plus `onEvent` and
9950
+ `onStart`/`onStop`.
9951
+
9952
+ | Return | Meaning |
9953
+ | --- | --- |
9954
+ | `{ auth }` | Model turn. Session shows in the playground. `workspaceFiles` can be a function. |
9955
+ | `{ task }` | Host work. No chat session. Use when the work can outlive a webhook timeout. |
9956
+ | `null` | Skip |
9957
+
9958
+ To keep repo scope in deploy config, use `cursorAccount: true`
9959
+ and pass `--repo owner/name` at serve / `--cursor-events-repo`
9960
+ at deploy. Repos must share one GitHub owner.
9961
+
9962
+ ## Event sources
9963
+
9964
+ | Source | When |
9965
+ | --- | --- |
9966
+ | `cursorAccount` + `serve --cursor-events` | Preferred. Signed-in host (`agent-sdk login` / `CURSOR_API_KEY`). Repos from the channel and from repeatable `--repo owner/name`. Cap 20, one GitHub owner |
9967
+ | HTTP `POST /<slug>/v1/channels/github` with a webhook secret | `allowAll()` + `X-Hub-Signature-256` |
9968
+ | HTTP, no secret | Loopback only. `serve --dev` also admits unsigned loopback (fixtures / forward) |
9969
+
9970
+ `permissions`: `"read"` inspect; `"pr-write"` (default) comments /
9971
+ PR writes; `"contents-write"` push or merge-box checks.
9972
+ `progress.commitStatus` needs check-write; see the GitHub guide.
9973
+ Set `checks: true` when channel code posts its own Checks API
9974
+ runs through `ctx.github.createCheck`. The flag grants access.
9975
+ It does not post a check.
9976
+
9977
+ Without `cursorAccount`, outbound calls prefer App installation
9978
+ tokens (`GITHUB_APP_ID` + `GITHUB_APP_PRIVATE_KEY`). Local tests
9979
+ can use `GITHUB_TOKEN` / `gh auth login`.
9980
+
9981
+ The Cursor stream is metadata, not full webhook bodies. Re-read
9982
+ the PR from GitHub.
9983
+
9984
+ ## Test locally
9985
+
9986
+ 1. **Fixtures** (offline, `--dev`, no signature):
9987
+
9988
+ ```bash
9989
+ curl -s -X POST http://127.0.0.1:3000/<slug>/v1/channels/github \
9990
+ -H 'content-type: application/json' \
9991
+ -H 'x-github-event: pull_request' \
9992
+ -d @fixtures/github/pull_request.synchronize.json
9993
+ ```
9994
+
9995
+ 2. **Replay** (hillclimb tier; pull access is enough):
9996
+
9997
+ ```bash
9998
+ agent-sdk github replay owner/repo#123 --dir <project>
9999
+ agent-sdk github replay owner/repo#123 --dir <project> --events '*' --dry-run --out fixtures/github
10000
+ ```
10001
+
10002
+ 3. **Forward** (live; repo admin). One forwarder per repo. Blank
10003
+ `GITHUB_TOKEN`/`GH_TOKEN` or every delivery 401s:
10004
+
10005
+ ```bash
10006
+ agent-sdk github doctor --install
10007
+ GITHUB_TOKEN= GH_TOKEN= agent-sdk github forward --dir <project>
10008
+ ```
10009
+
10010
+ 4. **Cursor pull** (same as production):
10011
+
10012
+ ```bash
10013
+ agent-sdk serve --dir <project> --cursor-events --repo owner/repo
10014
+ ```
10015
+
10016
+ Volume, debounce, and `progress.commitStatus` / `progress.banner`
10017
+ live in `docs/guides/github.md`. Do not preinstall them.
10018
+
10019
+ ---
10020
+
10021
+ Source: /docs/skills/hillclimb.md
10022
+
10023
+ # Agent SDK hillclimb
10024
+
10025
+ Measure → change one lever → remeasure. One failure mode per round.
10026
+
10027
+ Siblings: `skills/evals/SKILL.md`, `skills/github/SKILL.md`,
10028
+ `skills/debug/SKILL.md`.
10029
+
10030
+ ## Preconditions
10031
+
10032
+ From the user or the invoking message:
10033
+
10034
+ 1. **Target.** Path or slug
10035
+ 2. **Fixtures.** Fixed inputs
10036
+ 3. **Success.** What better means this round
10037
+ 4. **Freeze line.** What must not change
10038
+
10039
+ Ask before editing if any are missing. A moving fixture is noise.
10040
+
10041
+ | Surface | Pin it |
10042
+ | --- | --- |
10043
+ | GitHub | `agent-sdk github replay <pr> --dir <project>` (`--dry-run --out fixtures/github`) |
10044
+ | One tool | `agent-sdk call <tool> --dir <project> --input '{...}'` |
10045
+ | Chat | `agent-sdk run --dir <project> --message "<fixture>"` |
10046
+
10047
+ ## Loop
10048
+
10049
+ 1. **Serve.** `agent-sdk serve --dir <project> --mode single --dev`
10050
+ Playground: `http://127.0.0.1:3000/playground`.
10051
+ 2. **Hit it.** Same path a user would. Record status, wall time,
10052
+ `sessionId`, output, and `action.result` counts by `toolName`.
10053
+ 3. **Name the failure.** Score correctness, efficiency, harness
10054
+ fit. One dominant failure this round.
10055
+ 4. **Change one lever.** Smallest first. Delete the code or prompt
10056
+ that caused it. Then instructions / skills, evidence shape,
10057
+ host prep (`workspaceFiles`, channel `callTool`) for wandering
10058
+ and latency (not formatting or judgment), remove or gate
10059
+ wandering tools, framework only if the agent cannot express
10060
+ the fix. Hypothesis: *If we X, metric Y should move because Z.*
10061
+ 5. **Remeasure.** Same fixtures. Keep only if the target metric
10062
+ improves and the freeze line holds.
10063
+ 6. **Lock.** A kept change gets an eval that would have failed
10064
+ before it (`skills/evals/SKILL.md`). Never weaken a gate to
10065
+ pass a round.
10066
+
10067
+ ```markdown
10068
+ ### Hillclimb round N. `<slug>`
10069
+ - Fixture(s): …
10070
+ - Hypothesis: …
10071
+ - Change: …
10072
+ - Before → after: tools …; wall …; quality …
10073
+ - Verdict: keep | revert | narrow
10074
+ - Next failure mode:
10075
+ ```
10076
+
10077
+ Do not deploy or post real GitHub reviews unless asked.
10078
+
10079
+ ---
10080
+
10081
+ Source: /docs/skills/index.md
10082
+
10083
+ # Coding-agent skills
10084
+
10085
+ Each skill is a procedure a coding agent can follow. Installing
10086
+ `@cursor/july` copies them into `~/.cursor/skills/agentsdk/`. This site
10087
+ publishes the same files.
10088
+
10089
+ See [Building agents with agents](/docs/building-with-agents.md) for when
10090
+ to use each one.
10091
+
10092
+ | Skill | Use it to |
10093
+ | --- | --- |
10094
+ | [framework-map](/docs/skills/framework-map.md) | Learn the project layout and runtimes |
10095
+ | [create-agent](/docs/skills/create-agent.md) | Scaffold and verify a new agent |
10096
+ | [evals](/docs/skills/evals.md) | Write fixtures and regression checks |
10097
+ | [ab](/docs/skills/ab.md) | Compare variants on live traffic |
10098
+ | [otel](/docs/skills/otel.md) | Export OpenTelemetry traces |
10099
+ | [hillclimb](/docs/skills/hillclimb.md) | Improve an agent against fixed inputs |
10100
+ | [github](/docs/skills/github.md) | Add GitHub webhooks and replay events |
10101
+ | [setup-slack](/docs/skills/setup-slack.md) | Connect an agent to Slack |
10102
+ | [mcp-auth](/docs/skills/mcp-auth.md) | Authorize host MCP OAuth |
10103
+ | [debug](/docs/skills/debug.md) | Diagnose a local run |
10104
+
10105
+ ---
10106
+
10107
+ Source: /docs/skills/mcp-auth.md
10108
+
10109
+ # Host MCP OAuth
10110
+
10111
+ Guide: `docs/guides/mcp-oauth.md`.
10112
+
10113
+ | Need | Use |
10114
+ | --- | --- |
10115
+ | Connector already in the Cursor dashboard | `defineConnection({ cursorAccount: true })` or `servers: "*"` / `servers: […]` |
10116
+ | Remote URL that speaks OAuth; host holds tokens | `defineConnection({ url, oauth: true })` + this skill |
10117
+ | Static bearer / API key | `headers` / env on `{ url }` |
10118
+
10119
+ `advertiseTools: true` puts named tools on local turns. Host tools
10120
+ can still call `ctx.host.mcp`.
10121
+
10122
+ ## Checklist
10123
+
10124
+ 1. **Declare the connection.** `agent/mcp-connections/<name>.ts`
10125
+ with `url` + `oauth: true`. Use `agent/host-connections/` when
10126
+ the model must not see it.
10127
+ 2. **Name the secrets** if you will `--store`:
10128
+ `hosting.secretNames` lists
10129
+ `MCP_OAUTH_<NAME>_{ACCESS_TOKEN,REFRESH_TOKEN,CLIENT_ID}`.
10130
+ 3. **Allow egress** on hosted non-bootstrap hosts:
10131
+ `hosting.egressDomains`.
10132
+ 4. **Authorize.** Local: `agent-sdk mcp oauth <name>`. Hosted:
10133
+ finish Connect so the current process can retry, then
10134
+ `agent-sdk mcp oauth <name> --store` and `agent-sdk deploy` so
10135
+ the next pod gets `MCP_OAUTH_*`. `secrets list` shows the names.
10136
+ Secrets are deployment-wide, not per caller. `cursorAccount: true`
10137
+ stays on the Cursor backend.
10138
+
10139
+ ```ts
10140
+ // agent/mcp-connections/inventory.ts
10141
+ export default defineConnection({
10142
+ url: "https://mcp.example.com/inventory",
10143
+ oauth: true,
10144
+ });
10145
+ ```
10146
+
10147
+ ```ts
10148
+ // agent/agent.ts
10149
+ export default defineAgent({
10150
+ hosting: {
10151
+ secretNames: [
10152
+ "MCP_OAUTH_INVENTORY_ACCESS_TOKEN",
10153
+ "MCP_OAUTH_INVENTORY_REFRESH_TOKEN",
10154
+ "MCP_OAUTH_INVENTORY_CLIENT_ID",
10155
+ ],
10156
+ egressDomains: ["mcp.example.com"],
10157
+ },
10158
+ });
10159
+ ```
10160
+
10161
+ File `inventory.ts` → prefix `MCP_OAUTH_INVENTORY`. Do not put
10162
+ `CURSOR_*` names in `secretNames`.
10163
+
10164
+ ```bash
10165
+ agent-sdk mcp oauth <connection>
10166
+ agent-sdk mcp oauth <connection> --store [--slug <slug>]
10167
+ agent-sdk secrets list <slug>
10168
+ agent-sdk deploy
10169
+ ```
10170
+
10171
+ Browser callback: `http://127.0.0.1:8787/callback`. Tokens live in
10172
+ the CLI config directory (`mcp-auth.json` or
10173
+ `$AGENT_SERVE_CONFIG_DIR`). URL change drops the old entry; re-run.
10174
+ `--store` does not restart a running engine.
10175
+
10176
+ | Symptom | Fix |
10177
+ | --- | --- |
10178
+ | `must be defineConnection({ url, oauth: true })` | Wrong name or missing `oauth: true` |
10179
+ | `Unknown MCP connection` | Filename must match the CLI arg |
10180
+ | Callback hang | Free port 8787; finish the browser flow here |
10181
+ | Hosted 401 | `secrets list`; Connect or `--store`; redeploy |
10182
+ | Model invents `mcp_auth` / IDE MCP | `advertiseTools: true` on local turns |
10183
+
10184
+ No raw tokens in git.
10185
+
10186
+ ---
10187
+
10188
+ Source: /docs/skills/otel.md
10189
+
10190
+ # Agent SDK OpenTelemetry (`defineOtel`)
10191
+
10192
+ Push traces and metrics from the serve process to an OTLP
10193
+ collector. Logs are off until you opt in.
10194
+
10195
+ Guide: `docs/guides/opentelemetry.md`.
10196
+
10197
+ ## Enable
10198
+
10199
+ Any one of:
10200
+
10201
+ 1. `OTEL_EXPORTER_OTLP_ENDPOINT` (optional
10202
+ `OTEL_SERVICE_NAME`, `OTEL_EXPORTER_OTLP_HEADERS`,
10203
+ `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf|http/json`)
10204
+ 2. `agent/otel.ts`:
10205
+
10206
+ ```ts
10207
+ import { defineOtel } from "@cursor/july/otel";
10208
+
10209
+ export default defineOtel({
10210
+ serviceName: "cursor",
10211
+ exporters: [{ url: "https://otel.example.com", protocol: "http/protobuf" }],
10212
+ });
10213
+ ```
10214
+
10215
+ 3. `serve(dir, { otel: { … } })`. `otel: false` disables env and
10216
+ authored config.
10217
+
10218
+ | Signal | Default |
10219
+ | --- | --- |
10220
+ | Traces | on (`agent_sdk.http` → session → turn → tool / subagent) |
10221
+ | Metrics | on (`cursor.token.usage`, `cursor.tool.calls`, `cursor.cost.usage`) |
10222
+ | Logs | off (`logs: true` or `OTEL_LOGS_EXPORTER=otlp`) |
10223
+
10224
+ Prompt text and tool payloads stay off the wire unless you set
10225
+ `OTEL_LOG_USER_PROMPTS` / `OTEL_LOG_TOOL_CONTENT`.
10226
+
10227
+ ## Custom metrics
10228
+
10229
+ ```ts
10230
+ ctx.host.otel.setAttributes({ "abc.ticket_id": "INC-123" });
10231
+ ctx.host.otel.increment("abc.ticket.resolved");
10232
+ ctx.host.otel.record("abc.approval.duration_ms", 1420);
10233
+ ```
10234
+
10235
+ Prefix names with team or agent. No custom spans. Join on
10236
+ `cursor.conversation.id` and `agent_sdk.agent`. Run `serve` as its
10237
+ own process when Agent SDK should own the destination.
10238
+
10239
+ ---
10240
+
10241
+ Source: /docs/skills/setup-slack.md
10242
+
10243
+ # Setup Slack for the Agent SDK
10244
+
10245
+ Dedicated Socket Mode bot. `agent-sdk slack create` opens the
10246
+ signed-in dashboard wizard. Tokens land in `.env.local` and as
10247
+ deployment secrets. Never print token values.
10248
+
10249
+ Do not generate manifests or ask anyone to paste tokens unless
10250
+ they asked for `slack init --manual`.
10251
+
10252
+ Existing `<PREFIX>_SLACK_BOT_TOKEN` + `_SLACK_APP_TOKEN` in
10253
+ `.env.local` keep working. Do not force those onto the wizard.
10254
+
10255
+ Guide: `docs/guides/slack.md`.
10256
+
10257
+ ## 1. Wizard (default)
10258
+
10259
+ ```bash
10260
+ agent-sdk login
10261
+ agent-sdk slack create --dir .
10262
+ ```
10263
+
10264
+ Same Cursor account in the browser. **Add Slack to this agent**,
10265
+ approve Slack, pick the bot name. CLI writes
10266
+ `<PREFIX>_SLACK_BOT_TOKEN` / `<PREFIX>_SLACK_APP_TOKEN` and runs
10267
+ `doctor`.
10268
+
10269
+ Prefix is the directory basename in upper snake (`jenny` →
10270
+ `JENNY`, `pr-approver` → `PR_APPROVER` → `PR_APPROVER_SLACK_*`).
10271
+ `--prefix` / `--no-prefix` override. Dev and prod are separate
10272
+ apps; `--prod` is the prod app. `--name` / `--icon` /
10273
+ `--channel-posts` / `--slack-team` prefill the wizard.
10274
+
10275
+ If Slack needs workspace-admin approval, keep the CLI running.
10276
+ Open the **Request approval** link it prints. Managed install
10277
+ does not file the request. After approval, **Retry** in the
10278
+ wizard.
10279
+
10280
+ `create` scaffolds `agent/channels/slack.ts` when missing:
10281
+
10282
+ ```ts
10283
+ export default slackChannel({
10284
+ envPrefix: "JENNY",
10285
+ suggestedPrompts: [{ title: "Help", message: "How can you help me?" }],
10286
+ });
10287
+ ```
10288
+
10289
+ Return from dispatch handlers. Do not await long work in the
10290
+ handler.
10291
+
10292
+ A second `slack create` for the same agent and env overwrites the
10293
+ app (same Slack app id, new manifest and tokens).
10294
+ `agent-sdk slack destroy` deletes it.
10295
+
10296
+ Local serve does not need a hosted engine. Next
10297
+ `agent-sdk deploy` injects the stored secrets.
10298
+
10299
+ ## 2. Manual (`slack init --manual`)
10300
+
10301
+ Only when they own the app. Preferred: Slack CLI
10302
+ (`~/.slack/bin/slack`). Paste at api.slack.com if the CLI is
10303
+ missing. Never `slack deploy`; Agent SDK serve owns Socket Mode.
10304
+
10305
+ ```bash
10306
+ agent-sdk slack init --manual --dir . --name "My Agent"
10307
+ # --slack-team T0123ABCD when several workspaces are logged in
10308
+ # --no-install to scaffold only
10309
+ ```
10310
+
10311
+ Writes `agent/channels/slack.ts` (`envPrefix` from the directory),
10312
+ manifests, a `.slack/` project (`get-manifest` → those JSON files),
10313
+ and `env.example`. `--channel-posts` subscribes `message.channels`
10314
+ / `message.groups`. If Slack CLI is logged in, this installs the
10315
+ app. `--install` fails when that cannot run.
10316
+
10317
+ Human gates, one at a time. Stop after each.
10318
+
10319
+ 1. **Install the app.** If `next` starts with "Ask the user to
10320
+ install this Slack app", stop and prompt them. Slack CLI: they
10321
+ run `slack login --no-prompt`, send `/slackauthticket <ticket>`,
10322
+ then `slack login --ticket <ticket> --challenge <code>`, then
10323
+ `slack app install --environment local --team <T> --force`.
10324
+ Prod: `SLACK_ENV=deployed slack app install --environment
10325
+ deployed --team <T> --force`. Fallback: Create New App → From a
10326
+ manifest, start with `.slack/manifest.dev.json`.
10327
+ 2. **Tokens.** Slack CLI leaves xoxb / xapp in that process only.
10328
+ Copy the bot token from the app's OAuth page. Mint an app-level
10329
+ token with `connections:write`. Put both in `.env.local` using
10330
+ `env.example` names. If Slack CLI wrote `SLACK_*` to `.env`,
10331
+ copy those values to the prefixed keys.
10332
+ 3. **Doctor.**
10333
+
10334
+ ## 3. Doctor and smoke
10335
+
10336
+ ```bash
10337
+ agent-sdk slack doctor --prefix JENNY
10338
+ agent-sdk serve --dir . --dev
10339
+ ```
10340
+
10341
+ Green: `app_token`, `connections_open`, `bot_token`, `auth_test`.
10342
+ Log: `[agent-sdk/slack] Socket Mode connected`. Missing tokens
10343
+ idle the channel; `serve` continues.
10344
+
10345
+ 1. Invite the bot
10346
+ 2. `@mention` or DM
10347
+ 3. Thinking / Working, then a threaded reply
10348
+ 4. Logs: `inbound kind=app_mention`, `session start`,
10349
+ `reply delivered via postMessage|stream`
10350
+
10351
+ ## Watch channels (opt-in)
10352
+
10353
+ Default is mentions + DMs only. To wake on new posts:
10354
+
10355
+ ```ts
10356
+ export default slackChannel({
10357
+ envPrefix: "JENNY",
10358
+ engagement: {
10359
+ channelPosts: {
10360
+ allow: ["#alerts"], // ["*"] for every joined channel
10361
+ posts: "top-level",
10362
+ },
10363
+ },
10364
+ onChannelPost: async (ctx, message) => ({}), // null = skip
10365
+ });
10366
+ ```
10367
+
10368
+ Pass `--channel-posts` on `create` / `init --manual`. Invite the
10369
+ bot to each watched channel. Mentions stay on `app_mention`; a
10370
+ later mention continues the watch thread.
10371
+
10372
+ ## Approvals (opt-in)
10373
+
10374
+ ```ts
10375
+ export default slackChannel({
10376
+ envPrefix: "JENNY",
10377
+ toolApprovals: true,
10378
+ });
10379
+ ```
10380
+
10381
+ Wizard enables interactivity when this is set. Server tools on
10382
+ `local` only. Parked calls die on host restart. Cards truncate
10383
+ args; execution uses the full input.
10384
+
10385
+ ---
10386
+
7594
10387
  Source: /docs/storage.md
7595
10388
 
7596
10389
  # Storage
@@ -8617,7 +11410,7 @@ not on `PATH`, use `npx @cursor/july`.
8617
11410
  | 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`. |
8618
11411
 
8619
11412
  See [Host MCP OAuth](/docs/guides/mcp-oauth.md) and
8620
- [`skills/mcp-auth/SKILL.md`](https://github.com/cursor/cursor/blob/main/packages/agent-serve/skills/mcp-auth/SKILL.md).
11413
+ [`skills/mcp-auth/SKILL.md`](/docs/skills/mcp-auth.md).
8621
11414
 
8622
11415
  ## What if a secret showed up in a terminal transcript?
8623
11416