prompt_objects 0.6.0 → 0.7.0

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 (291) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +126 -0
  3. data/README.md +93 -22
  4. data/exe/poop +5 -0
  5. data/exe/prompt_objects +1 -1108
  6. data/exe/prompt_objects_mcp +5 -3
  7. data/lib/prompt_objects/capability.rb +33 -0
  8. data/lib/prompt_objects/cli/diagnostics_command.rb +69 -0
  9. data/lib/prompt_objects/cli/env_command.rb +435 -0
  10. data/lib/prompt_objects/cli/events_command.rb +145 -0
  11. data/lib/prompt_objects/cli/explore_command.rb +155 -0
  12. data/lib/prompt_objects/cli/helpers.rb +91 -0
  13. data/lib/prompt_objects/cli/message_command.rb +216 -0
  14. data/lib/prompt_objects/cli/repl_command.rb +366 -0
  15. data/lib/prompt_objects/cli/serve_command.rb +162 -0
  16. data/lib/prompt_objects/cli.rb +64 -422
  17. data/lib/prompt_objects/connectors/base.rb +16 -29
  18. data/lib/prompt_objects/connectors/mcp.rb +82 -98
  19. data/lib/prompt_objects/domain/records.rb +45 -0
  20. data/lib/prompt_objects/environment/exporter.rb +5 -4
  21. data/lib/prompt_objects/environment/git.rb +21 -1
  22. data/lib/prompt_objects/environment/importer.rb +5 -4
  23. data/lib/prompt_objects/environment/manager.rb +44 -6
  24. data/lib/prompt_objects/environment/manifest.rb +26 -4
  25. data/lib/prompt_objects/environment.rb +1337 -20
  26. data/lib/prompt_objects/execution/blocking_executor.rb +88 -0
  27. data/lib/prompt_objects/execution/context.rb +137 -0
  28. data/lib/prompt_objects/execution/contracts.rb +59 -0
  29. data/lib/prompt_objects/execution/permit_pool.rb +49 -0
  30. data/lib/prompt_objects/execution/policy.rb +47 -0
  31. data/lib/prompt_objects/execution/resource_lock_manager.rb +63 -0
  32. data/lib/prompt_objects/execution/runner.rb +498 -0
  33. data/lib/prompt_objects/execution/scheduler.rb +325 -0
  34. data/lib/prompt_objects/llm/factory.rb +4 -4
  35. data/lib/prompt_objects/llm/gemini_adapter.rb +25 -8
  36. data/lib/prompt_objects/llm/openai_adapter.rb +9 -1
  37. data/lib/prompt_objects/llm/pricing.rb +9 -2
  38. data/lib/prompt_objects/llm/response.rb +9 -4
  39. data/lib/prompt_objects/loader.rb +28 -1
  40. data/lib/prompt_objects/mcp/server.rb +6 -3
  41. data/lib/prompt_objects/mcp/tools/get_conversation.rb +15 -11
  42. data/lib/prompt_objects/mcp/tools/get_pending_requests.rb +2 -2
  43. data/lib/prompt_objects/mcp/tools/respond_to_request.rb +3 -3
  44. data/lib/prompt_objects/mcp/tools/send_message.rb +42 -32
  45. data/lib/prompt_objects/operations/diagnostics.rb +115 -0
  46. data/lib/prompt_objects/operations/retention.rb +75 -0
  47. data/lib/prompt_objects/operations/run_trace_builder.rb +152 -0
  48. data/lib/prompt_objects/primitives/http_get.rb +2 -0
  49. data/lib/prompt_objects/primitives/list_files.rb +2 -0
  50. data/lib/prompt_objects/primitives/read_file.rb +2 -0
  51. data/lib/prompt_objects/primitives/think.rb +36 -0
  52. data/lib/prompt_objects/primitives/write_file.rb +9 -0
  53. data/lib/prompt_objects/prompt_object.rb +171 -519
  54. data/lib/prompt_objects/repositories/artifact_repository.rb +109 -0
  55. data/lib/prompt_objects/repositories/base_repository.rb +37 -0
  56. data/lib/prompt_objects/repositories/definition_repository.rb +36 -0
  57. data/lib/prompt_objects/repositories/env_data_repository.rb +103 -0
  58. data/lib/prompt_objects/repositories/event_repository.rb +105 -0
  59. data/lib/prompt_objects/repositories/message_repository.rb +73 -0
  60. data/lib/prompt_objects/repositories/run_repository.rb +254 -0
  61. data/lib/prompt_objects/repositories/thread_repository.rb +87 -0
  62. data/lib/prompt_objects/repositories/tool_execution_repository.rb +88 -0
  63. data/lib/prompt_objects/repositories/workspace_session_repository.rb +61 -0
  64. data/lib/prompt_objects/server/api/routes.rb +549 -53
  65. data/lib/prompt_objects/server/app.rb +133 -14
  66. data/lib/prompt_objects/server/file_watcher.rb +9 -0
  67. data/lib/prompt_objects/server/public/assets/index-BjKlODDD.js +90 -0
  68. data/lib/prompt_objects/server/public/assets/index-w3fpIb5c.css +1 -0
  69. data/lib/prompt_objects/server/public/index.html +2 -2
  70. data/lib/prompt_objects/server/websocket_handler.rb +202 -227
  71. data/lib/prompt_objects/server.rb +170 -1
  72. data/lib/prompt_objects/service.rb +129 -0
  73. data/lib/prompt_objects/session/store.rb +600 -13
  74. data/lib/prompt_objects/universal/add_capability.rb +1 -1
  75. data/lib/prompt_objects/universal/create_capability.rb +4 -2
  76. data/lib/prompt_objects/universal/delete_env_data.rb +23 -15
  77. data/lib/prompt_objects/universal/delete_primitive.rb +1 -1
  78. data/lib/prompt_objects/universal/dispatch_po.rb +54 -0
  79. data/lib/prompt_objects/universal/get_env_data.rb +8 -14
  80. data/lib/prompt_objects/universal/get_run_status.rb +30 -0
  81. data/lib/prompt_objects/universal/join_runs.rb +35 -0
  82. data/lib/prompt_objects/universal/list_env_data.rb +10 -14
  83. data/lib/prompt_objects/universal/modify_primitive.rb +1 -1
  84. data/lib/prompt_objects/universal/render_artifact.rb +91 -0
  85. data/lib/prompt_objects/universal/request_primitive.rb +1 -1
  86. data/lib/prompt_objects/universal/{store_env_data.rb → set_env_data.rb} +34 -20
  87. data/lib/prompt_objects/version.rb +5 -0
  88. data/lib/prompt_objects.rb +33 -8
  89. data/objects/coordinator.md +8 -4
  90. data/templates/arc-agi-1/objects/data_manager.md +2 -2
  91. data/templates/arc-agi-1/objects/observer.md +2 -2
  92. data/templates/arc-agi-1/objects/solver.md +6 -5
  93. data/templates/arc-agi-1/objects/verifier.md +2 -2
  94. data/templates/arc-agi-3/manifest.yml +19 -0
  95. data/templates/arc-agi-3/objects/analyst.md +79 -0
  96. data/templates/arc-agi-3/objects/player.md +76 -0
  97. data/templates/arc-agi-3/objects/strategist.md +97 -0
  98. data/templates/arc-agi-3/primitives/action_log.rb +133 -0
  99. data/templates/arc-agi-3/primitives/arc3_api.rb +223 -0
  100. data/templates/arc-agi-3/primitives/arc3_scorecard.rb +131 -0
  101. data/templates/arc-agi-3/primitives/frame_diff.rb +143 -0
  102. data/templates/arc-agi-3/primitives/frame_info.rb +96 -0
  103. data/templates/arc-agi-3/primitives/render_frame.rb +140 -0
  104. data/templates/basic/objects/basic.md +11 -3
  105. data/templates/chiptune/extras/sonic_pi_boot.rb +220 -0
  106. data/templates/chiptune/manifest.yml +20 -0
  107. data/templates/chiptune/objects/conductor.md +184 -0
  108. data/templates/chiptune/objects/sonic_pi.md +65 -0
  109. data/templates/chiptune/objects/voice_bass.md +44 -0
  110. data/templates/chiptune/objects/voice_lead.md +44 -0
  111. data/templates/chiptune/objects/voice_pad.md +43 -0
  112. data/templates/chiptune/objects/voice_perc.md +45 -0
  113. data/templates/chiptune/primitives/play_note.rb +59 -0
  114. data/templates/chiptune/primitives/play_sequence.rb +55 -0
  115. data/templates/chiptune/primitives/query_sonic_pi.rb +141 -0
  116. data/templates/chiptune/primitives/set_synth.rb +57 -0
  117. data/templates/chiptune/primitives/set_tempo.rb +45 -0
  118. data/templates/chiptune/primitives/stop_voice.rb +46 -0
  119. data/templates/chiptune/primitives/support/osc_bridge.rb +33 -0
  120. data/templates/chiptune/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  121. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  122. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  123. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  124. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  125. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  126. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  127. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  128. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  129. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  130. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  131. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  132. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  133. data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  134. data/templates/developer/objects/code_reviewer.md +1 -1
  135. data/templates/developer/objects/coordinator.md +16 -3
  136. data/templates/developer/objects/debugger.md +3 -3
  137. data/templates/empty/objects/assistant.md +10 -5
  138. data/templates/minimal/objects/assistant.md +10 -5
  139. data/templates/music/extras/sonic_pi_boot.rb +220 -0
  140. data/templates/music/manifest.yml +16 -0
  141. data/templates/music/objects/conductor.md +154 -0
  142. data/templates/music/objects/sonic_pi.md +65 -0
  143. data/templates/music/primitives/play_note.rb +55 -0
  144. data/templates/music/primitives/play_sequence.rb +51 -0
  145. data/templates/music/primitives/query_sonic_pi.rb +139 -0
  146. data/templates/music/primitives/set_synth.rb +53 -0
  147. data/templates/music/primitives/set_tempo.rb +43 -0
  148. data/templates/music/primitives/stop_voice.rb +44 -0
  149. data/templates/music/primitives/support/osc_bridge.rb +33 -0
  150. data/templates/music/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  151. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  152. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  153. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  154. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  155. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  156. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  157. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  158. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  159. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  160. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  161. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  162. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  163. data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  164. data/templates/music-beginner/README.md +100 -0
  165. data/templates/music-beginner/extras/README.md +27 -0
  166. data/templates/music-beginner/extras/sonic_pi_boot.rb +297 -0
  167. data/templates/music-beginner/manifest.yml +25 -0
  168. data/templates/music-beginner/objects/conductor.md +222 -0
  169. data/templates/music-beginner/objects/engine_setup.md +54 -0
  170. data/templates/music-beginner/objects/voice_bass.md +43 -0
  171. data/templates/music-beginner/objects/voice_keys.md +48 -0
  172. data/templates/music-beginner/objects/voice_pad.md +46 -0
  173. data/templates/music-beginner/objects/voice_percussion.md +45 -0
  174. data/templates/music-beginner/objects/voice_texture.md +37 -0
  175. data/templates/music-beginner/primitives/play_pattern.rb +118 -0
  176. data/templates/music-beginner/primitives/query_music_engine.rb +94 -0
  177. data/templates/music-beginner/primitives/set_instrument.rb +47 -0
  178. data/templates/music-beginner/primitives/set_tempo.rb +43 -0
  179. data/templates/music-beginner/primitives/set_voice_level.rb +46 -0
  180. data/templates/music-beginner/primitives/stop_voice.rb +37 -0
  181. data/templates/music-beginner/primitives/support/osc_bridge.rb +25 -0
  182. data/templates/music-beginner/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  183. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  184. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  185. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  186. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  187. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  188. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  189. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  190. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  191. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  192. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  193. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  194. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  195. data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  196. data/templates/music-pro/extras/AUDITION.md +100 -0
  197. data/templates/music-pro/extras/sonic_pi_boot.rb +546 -0
  198. data/templates/music-pro/manifest.yml +19 -0
  199. data/templates/music-pro/objects/conductor.md +126 -0
  200. data/templates/music-pro/objects/sonic_pi.md +35 -0
  201. data/templates/music-pro/objects/voice_harmony.md +48 -0
  202. data/templates/music-pro/objects/voice_rhythm.md +43 -0
  203. data/templates/music-pro/objects/voice_texture.md +42 -0
  204. data/templates/music-pro/primitives/define_patch.rb +46 -0
  205. data/templates/music-pro/primitives/query_music_engine.rb +69 -0
  206. data/templates/music-pro/primitives/set_live_pattern.rb +70 -0
  207. data/templates/music-pro/primitives/set_tempo.rb +31 -0
  208. data/templates/music-pro/primitives/stop_voice.rb +31 -0
  209. data/templates/music-pro/primitives/support/music_protocol.rb +136 -0
  210. data/templates/music-pro/primitives/support/osc_bridge.rb +33 -0
  211. data/templates/music-pro/primitives/trigger_event.rb +44 -0
  212. data/templates/music-pro/vendor/osc-ruby-1.1.5/LICENSE +19 -0
  213. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
  214. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
  215. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
  216. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
  217. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
  218. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
  219. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
  220. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
  221. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
  222. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
  223. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
  224. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
  225. data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
  226. data/templates/writer/objects/coordinator.md +14 -3
  227. data/templates/writer/objects/editor.md +1 -1
  228. data/templates/writer/objects/researcher.md +2 -2
  229. data/tools/thread-explorer.html +1 -1
  230. metadata +172 -65
  231. data/CLAUDE.md +0 -177
  232. data/Gemfile +0 -10
  233. data/Rakefile +0 -27
  234. data/frontend/.gitignore +0 -3
  235. data/frontend/index.html +0 -17
  236. data/frontend/package-lock.json +0 -4540
  237. data/frontend/package.json +0 -36
  238. data/frontend/postcss.config.js +0 -6
  239. data/frontend/src/App.tsx +0 -104
  240. data/frontend/src/canvas/CanvasView.tsx +0 -113
  241. data/frontend/src/canvas/ForceLayout.ts +0 -115
  242. data/frontend/src/canvas/SceneManager.ts +0 -587
  243. data/frontend/src/canvas/canvasStore.ts +0 -47
  244. data/frontend/src/canvas/constants.ts +0 -95
  245. data/frontend/src/canvas/controls/CameraControls.ts +0 -98
  246. data/frontend/src/canvas/edges/MessageArc.ts +0 -149
  247. data/frontend/src/canvas/inspector/InspectorPanel.tsx +0 -31
  248. data/frontend/src/canvas/inspector/POInspector.tsx +0 -262
  249. data/frontend/src/canvas/inspector/ToolCallInspector.tsx +0 -67
  250. data/frontend/src/canvas/nodes/PONode.ts +0 -249
  251. data/frontend/src/canvas/nodes/ToolCallNode.ts +0 -116
  252. data/frontend/src/canvas/types.ts +0 -24
  253. data/frontend/src/components/ContextMenu.tsx +0 -68
  254. data/frontend/src/components/EnvDataPane.tsx +0 -69
  255. data/frontend/src/components/Inspector.tsx +0 -263
  256. data/frontend/src/components/MarkdownMessage.tsx +0 -155
  257. data/frontend/src/components/MethodList.tsx +0 -90
  258. data/frontend/src/components/ModelSelector.tsx +0 -115
  259. data/frontend/src/components/NotificationPanel.tsx +0 -130
  260. data/frontend/src/components/ObjectList.tsx +0 -78
  261. data/frontend/src/components/PaneSlot.tsx +0 -30
  262. data/frontend/src/components/SourcePane.tsx +0 -202
  263. data/frontend/src/components/SystemBar.tsx +0 -74
  264. data/frontend/src/components/Transcript.tsx +0 -76
  265. data/frontend/src/components/UsagePanel.tsx +0 -105
  266. data/frontend/src/components/Workspace.tsx +0 -260
  267. data/frontend/src/components/index.ts +0 -12
  268. data/frontend/src/hooks/useResize.ts +0 -55
  269. data/frontend/src/hooks/useWebSocket.ts +0 -559
  270. data/frontend/src/index.css +0 -102
  271. data/frontend/src/main.tsx +0 -10
  272. data/frontend/src/store/index.ts +0 -311
  273. data/frontend/src/types/index.ts +0 -176
  274. data/frontend/tailwind.config.js +0 -44
  275. data/frontend/tsconfig.json +0 -30
  276. data/frontend/vite.config.ts +0 -29
  277. data/lib/prompt_objects/server/public/assets/index-DEPawnfZ.js +0 -4345
  278. data/lib/prompt_objects/server/public/assets/index-oMrRce1m.css +0 -1
  279. data/lib/prompt_objects/universal/add_primitive.rb +0 -118
  280. data/lib/prompt_objects/universal/ask_human.rb +0 -109
  281. data/lib/prompt_objects/universal/create_primitive.rb +0 -174
  282. data/lib/prompt_objects/universal/list_primitives.rb +0 -145
  283. data/lib/prompt_objects/universal/think.rb +0 -41
  284. data/lib/prompt_objects/universal/update_env_data.rb +0 -88
  285. data/prompt_objects.gemspec +0 -50
  286. data/templates/basic/.gitignore +0 -2
  287. data/templates/developer/.gitignore +0 -5
  288. data/templates/empty/.gitignore +0 -5
  289. data/templates/empty/objects/.gitkeep +0 -0
  290. data/templates/minimal/.gitignore +0 -5
  291. data/templates/writer/.gitignore +0 -5
@@ -1,9 +1,10 @@
1
1
  ---
2
2
  name: assistant
3
3
  description: A helpful assistant that can read files and create specialists
4
- capabilities:
4
+ tools:
5
5
  - list_files
6
6
  - read_file
7
+ - think
7
8
  ---
8
9
 
9
10
  # Assistant
@@ -17,7 +18,7 @@ You are a helpful assistant. You're friendly, concise, and focused on being genu
17
18
  When someone asks for help:
18
19
  - Understand what they need
19
20
  - Provide clear, actionable answers
20
- - Ask clarifying questions when needed using ask_human
21
+ - Resolve ambiguity by inspecting available files, using tools, or delegating to a capable PO; if the task remains underdetermined, make a reasonable assumption and state it
21
22
 
22
23
  When someone needs to work with files:
23
24
  - Use list_files to see what's available
@@ -28,13 +29,17 @@ When you don't know something:
28
29
  - Be honest about your limitations
29
30
 
30
31
  When a task needs a specialist you don't have:
31
- - Use ask_human to confirm creating a new specialist
32
- - Use create_capability to build a new prompt object for the task
32
+ - Use list_capabilities and delegate to an existing specialist first
33
+ - Use create_capability to build a focused prompt object only when no existing specialist fits
33
34
  - The new specialist will then be available for future use
34
35
 
35
36
  When you're unsure what approach to take:
36
37
  - Use think to reason through options
37
- - Use ask_human to clarify with the user
38
+ - Inspect the available tools and delegate the uncertain part to the most relevant PO
39
+
40
+ When a task has ongoing state or repeated choices:
41
+ - Build an interface for it with render_artifact (buttons wired to PromptObjects.send)
42
+ - Re-render the same title as state changes, so the human can click instead of type
38
43
 
39
44
  ## Notes
40
45
 
@@ -1,9 +1,10 @@
1
1
  ---
2
2
  name: assistant
3
3
  description: A helpful assistant that can read files and create specialists
4
- capabilities:
4
+ tools:
5
5
  - list_files
6
6
  - read_file
7
+ - think
7
8
  ---
8
9
 
9
10
  # Assistant
@@ -17,7 +18,7 @@ You are a helpful assistant. You're friendly, concise, and focused on being genu
17
18
  When someone asks for help:
18
19
  - Understand what they need
19
20
  - Provide clear, actionable answers
20
- - Ask clarifying questions when needed using ask_human
21
+ - Resolve ambiguity by inspecting available files, using tools, or delegating to a capable PO; if the task remains underdetermined, make a reasonable assumption and state it
21
22
 
22
23
  When someone needs to work with files:
23
24
  - Use list_files to see what's available
@@ -28,13 +29,17 @@ When you don't know something:
28
29
  - Be honest about your limitations
29
30
 
30
31
  When a task needs a specialist you don't have:
31
- - Use ask_human to confirm creating a new specialist
32
- - Use create_capability to build a new prompt object for the task
32
+ - Use list_capabilities and delegate to an existing specialist first
33
+ - Use create_capability to build a focused prompt object only when no existing specialist fits
33
34
  - The new specialist will then be available for future use
34
35
 
35
36
  When you're unsure what approach to take:
36
37
  - Use think to reason through options
37
- - Use ask_human to clarify with the user
38
+ - Inspect the available tools and delegate the uncertain part to the most relevant PO
39
+
40
+ When a task has ongoing state or repeated choices:
41
+ - Build an interface for it with render_artifact (buttons wired to PromptObjects.send)
42
+ - Re-render the same title as state changes, so the human can click instead of type
38
43
 
39
44
  ## Notes
40
45
 
@@ -0,0 +1,220 @@
1
+ # PromptObjects Music Engine — Sonic Pi boot script (chiptune + music templates).
2
+ # Too big to paste ("Buffer Full")? Run ONE line instead — no size limit:
3
+ # run_file "/ABSOLUTE/PATH/TO/this-environment/extras/sonic_pi_boot.rb"
4
+
5
+ use_bpm 84
6
+ set :bpm, 84 # shared tempo — every voice reads this
7
+ set :beats_per_bar, 4 # the bar grid voices align to
8
+
9
+ # Voice pool: 8 slots, names map to slots dynamically.
10
+ set :voice_map, {}
11
+ set :next_slot, 0
12
+ set :max_slots, 8
13
+
14
+ 8.times do |i|
15
+ set :"v#{i}_active", false
16
+ set :"v#{i}_synth", "warm_lead" # a sound-pack preset name, not a raw synth
17
+ set :"v#{i}_notes", []
18
+ set :"v#{i}_durs", []
19
+ set :"v#{i}_amp", 1.0 # per-voice amp scale (1.0 = preset's baked level)
20
+ set :"v#{i}_name", nil
21
+ end
22
+
23
+ # Helpers
24
+ define :resolve_note do |n|
25
+ begin
26
+ if n.to_s.match?(/^\d+$/)
27
+ n.to_i
28
+ else
29
+ note(n.to_s.to_sym)
30
+ end
31
+ rescue
32
+ 60
33
+ end
34
+ end
35
+
36
+ define :parse_notes do |notes_str|
37
+ notes_str.to_s.split(",").map { |n| resolve_note(n.strip) }
38
+ end
39
+
40
+ define :parse_durs do |durs_str|
41
+ durs = durs_str.to_s.split(",").map { |d| d.strip.to_f }
42
+ durs = [1.0] if durs.empty? || durs.all? { |d| d <= 0 }
43
+ durs
44
+ end
45
+
46
+ # Map a voice name to a slot index, auto-assigning new names.
47
+ define :voice_slot do |name|
48
+ vmap = get(:voice_map)
49
+ return vmap[name.to_s] if vmap[name.to_s]
50
+ idx = get(:next_slot)
51
+ if idx >= get(:max_slots)
52
+ puts "WARNING: All 8 voice slots full, cannot register '#{name}'"
53
+ return nil
54
+ end
55
+ new_map = {} # Sonic Pi's SPMap is immutable — build a new hash
56
+ vmap.each { |k, v| new_map[k] = v } if vmap
57
+ new_map[name.to_s] = idx
58
+ set :voice_map, new_map
59
+ set :next_slot, idx + 1
60
+ set :"v#{idx}_name", name.to_s
61
+ puts "Registered voice '#{name}' → slot v#{idx}"
62
+ idx
63
+ end
64
+
65
+ # Sound pack: warm, pre-tuned presets. play_pack(preset, midi_note, dur, amp_scale)
66
+ define :play_pack do |pack, n, dur, amp_scale|
67
+ amp_scale = 1.0 if amp_scale.nil? || amp_scale <= 0
68
+ case pack.to_s
69
+ when "warm_lead"
70
+ use_synth :prophet
71
+ play n, attack: 0.03, decay: 0.1, sustain: dur * 0.5, release: dur * 0.5, cutoff: 95, amp: 0.4 * amp_scale
72
+ when "soft_pad"
73
+ use_synth :hollow
74
+ play n, attack: 0.6, sustain: dur, release: dur * 0.9, cutoff: 75, amp: 0.3 * amp_scale
75
+ when "round_bass"
76
+ use_synth :prophet
77
+ play n, attack: 0.02, sustain: dur * 0.7, release: dur * 0.3, cutoff: 65, res: 0.2, amp: 0.5 * amp_scale
78
+ when "warm_bell"
79
+ use_synth :pretty_bell
80
+ play n, attack: 0.01, sustain: 0.1, release: dur * 0.9, cutoff: 110, amp: 0.35 * amp_scale
81
+ when "soft_kick"
82
+ use_synth :tri
83
+ play n, attack: 0.005, sustain: 0.05, release: 0.18, amp: 0.6 * amp_scale
84
+ when "soft_hat"
85
+ use_synth :pluck
86
+ play n, attack: 0.001, sustain: 0.0, release: 0.08, amp: 0.18 * amp_scale
87
+ else
88
+ # Unknown name: use it as a raw synth if known-safe, else warm default.
89
+ known = [:prophet, :blade, :hollow, :dark_ambience, :pretty_bell, :pluck,
90
+ :tri, :saw, :dsaw, :pulse, :square, :beep, :tb303, :fm, :sine,
91
+ :chiplead, :chipbass, :chipnoise, :dtri, :dpulse]
92
+ s = pack.to_s.to_sym
93
+ use_synth(known.include?(s) ? s : :prophet)
94
+ play n, attack: 0.02, sustain: dur * 0.7, release: dur * 0.3, cutoff: 100, amp: 0.4 * amp_scale
95
+ end
96
+ end
97
+
98
+ # Master clock: cues :metro once per bar; every voice syncs to it so they lock.
99
+ live_loop :metro do
100
+ use_bpm get(:bpm)
101
+ sleep get(:beats_per_bar)
102
+ end
103
+
104
+ # 8 voice loops locked to the clock, through one FX chain (lpf + reverb +
105
+ # compressor). use_bpm runs in each loop's own thread (it's thread-local).
106
+ with_fx :compressor do
107
+ with_fx :lpf, cutoff: 110 do
108
+ with_fx :reverb, room: 0.7, mix: 0.22 do
109
+ 8.times do |i|
110
+ live_loop :"v#{i}" do
111
+ sync :metro
112
+ use_bpm get(:bpm)
113
+ if get(:"v#{i}_active") && get(:"v#{i}_notes").length > 0
114
+ pack = get(:"v#{i}_synth")
115
+ notes = get(:"v#{i}_notes")
116
+ durs = get(:"v#{i}_durs")
117
+ amp_scale = get(:"v#{i}_amp") || 1.0
118
+ total = 0.0
119
+ notes.each_with_index do |n, j|
120
+ dur = durs[[j, durs.length - 1].min]
121
+ play_pack(pack, n, dur, amp_scale) unless n.nil?
122
+ sleep dur
123
+ total += dur
124
+ end
125
+ # Pad to a whole number of bars so odd-length patterns re-lock.
126
+ bar = get(:beats_per_bar)
127
+ rem = (bar - (total % bar)) % bar
128
+ sleep rem if rem > 0.001
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ # OSC: /chiptune/sequence voice notes durations (tempo is global; see /chiptune/tempo)
137
+ live_loop :osc_sequence do
138
+ v = sync "/osc*/chiptune/sequence"
139
+ voice, notes_str, durations_str = v
140
+ idx = voice_slot(voice)
141
+ next unless idx
142
+ set :"v#{idx}_notes", parse_notes(notes_str)
143
+ set :"v#{idx}_durs", parse_durs(durations_str)
144
+ set :"v#{idx}_active", true
145
+ puts "#{voice} (v#{idx}) pattern: #{get(:"v#{idx}_notes").length} notes"
146
+ end
147
+
148
+ # OSC: /chiptune/play voice note duration amp
149
+ live_loop :osc_play do
150
+ v = sync "/osc*/chiptune/play"
151
+ voice, note_str, dur, amp = v
152
+ idx = voice_slot(voice)
153
+ next unless idx
154
+ n = resolve_note(note_str)
155
+ dur = dur.to_f > 0 ? dur.to_f : 1
156
+ amp = amp.to_f > 0 ? amp.to_f : 1.0
157
+ in_thread do
158
+ play_pack(get(:"v#{idx}_synth"), n, dur, amp)
159
+ end
160
+ end
161
+
162
+ # OSC: /chiptune/tempo bpm — one shared tempo for the ensemble.
163
+ live_loop :osc_tempo do
164
+ v = sync "/osc*/chiptune/tempo"
165
+ bpm = v[0].to_i
166
+ set :bpm, bpm if bpm > 0
167
+ puts "tempo → #{bpm} bpm"
168
+ end
169
+
170
+ # OSC: /chiptune/synth voice preset_name
171
+ live_loop :osc_synth do
172
+ v = sync "/osc*/chiptune/synth"
173
+ voice, synth_name = v
174
+ idx = voice_slot(voice)
175
+ next unless idx
176
+ set :"v#{idx}_synth", synth_name.to_s
177
+ puts "#{voice} (v#{idx}) sound → #{synth_name}"
178
+ end
179
+
180
+ # OSC: /chiptune/stop voice ("all" stops everything)
181
+ live_loop :osc_stop do
182
+ v = sync "/osc*/chiptune/stop"
183
+ voice = v[0].to_s
184
+ if voice == "all"
185
+ 8.times { |i| set :"v#{i}_active", false }
186
+ puts "All voices stopped"
187
+ else
188
+ idx = voice_slot(voice)
189
+ if idx
190
+ set :"v#{idx}_active", false
191
+ puts "#{voice} (v#{idx}) stopped"
192
+ end
193
+ end
194
+ end
195
+
196
+ # OSC: /prompt_objects/query response_port → replies on /prompt_objects/state_response
197
+ live_loop :osc_query do
198
+ v = sync "/osc*/prompt_objects/query"
199
+ response_port = v[0].to_i
200
+ response_port = 4561 if response_port <= 0
201
+ vmap = get(:voice_map)
202
+ state_parts = []
203
+ vmap.each do |name, idx|
204
+ active = get(:"v#{idx}_active") ? "true" : "false"
205
+ synth = get(:"v#{idx}_synth").to_s
206
+ notes = get(:"v#{idx}_notes")
207
+ notes_str = notes.is_a?(Array) ? notes.map(&:to_s).join(",") : ""
208
+ durs = get(:"v#{idx}_durs")
209
+ durs_str = durs.is_a?(Array) ? durs.map(&:to_s).join(",") : ""
210
+ state_parts << "#{name}|#{active}|#{synth}|#{notes_str}|#{durs_str}"
211
+ end
212
+ bpm_val = get(:bpm).to_i
213
+ slots_used = vmap.size
214
+ slots_free = get(:max_slots) - slots_used
215
+ response = "#{bpm_val}|#{slots_used}|#{slots_free}||#{state_parts.join('||')}"
216
+ osc_send "localhost", response_port, "/prompt_objects/state_response", response
217
+ puts "State query: #{slots_used} voices registered"
218
+ end
219
+
220
+ puts "PromptObjects Music Engine loaded — 8 voices, master clock, warm FX (reverb+lpf+compressor). OSC on 4560."
@@ -0,0 +1,16 @@
1
+ name: music
2
+ description: Open-ended music composition — the conductor builds a band for any genre via Sonic Pi
3
+ icon: "\U0001F3B6"
4
+ color: "#9B59B6"
5
+
6
+ dependencies:
7
+ gems:
8
+ - name: osc-ruby
9
+ version: "~> 1.1"
10
+ purpose: "OSC communication with Sonic Pi"
11
+
12
+ objects:
13
+ - conductor
14
+ - sonic_pi
15
+
16
+ default_po: conductor
@@ -0,0 +1,154 @@
1
+ ---
2
+ name: conductor
3
+ description: Music director — builds a band for any genre, sets the musical contract, orchestrates the composition
4
+ delegates_to:
5
+ - sonic_pi
6
+ tools:
7
+ - query_sonic_pi
8
+ - stop_voice
9
+ - set_tempo
10
+ autonomous:
11
+ interval: 30
12
+ wait_for_human: true
13
+ message: |
14
+ You've been woken up to evolve the music. Check what's actually playing
15
+ (call query_sonic_pi) and read the `composition` env_data for the current plan.
16
+ If Sonic Pi isn't connected or nothing is playing, do nothing.
17
+
18
+ To change the sound you MUST DELEGATE to a voice PO. Updating `composition`
19
+ env_data alone changes NOTHING you can hear — the voices keep looping their
20
+ last pattern and only change when you message them. So every tick:
21
+ 1. Pick ONE small change (shift density, vary a voice's pattern, mute/unmute
22
+ a voice, swap a warm preset).
23
+ 2. DELEGATE it to the relevant voice so it actually plays — e.g.
24
+ "voice_piano, comp more sparsely" or "voice_drums, drop to just brushes".
25
+ 3. THEN update the `composition` env_data to reflect the change.
26
+ Do NOT change key, scale, tempo, or the progression on a normal tick — only at
27
+ a section boundary, and at most every few minutes.
28
+ ---
29
+
30
+ # Conductor
31
+
32
+ ## Identity
33
+
34
+ You are an open-ended music director. You can create music in ANY genre — jazz, ambient, rock, electronic, lo-fi, funk, whatever the human wants. You build your band by creating voice POs tailored to the genre, then orchestrate them into a **cohesive** composition: voices that lock together in time and key, not independent solos.
35
+
36
+ For a live demo, default to pleasant, evolving **background music**: slow-to-mid tempo, sparse, warm, unobtrusive. Restraint beats busyness.
37
+
38
+ ## How It Works
39
+
40
+ You communicate with Sonic Pi via OSC. Sonic Pi is an external application the human needs running. Use **sonic_pi** (your connection manager) to check status and see what's playing.
41
+
42
+ ### Your Tools
43
+
44
+ - `query_sonic_pi` — get live state from Sonic Pi (what's actually playing)
45
+ - `stop_voice(voice)` — stop a voice, or "all" to stop everything
46
+ - `set_tempo(bpm)` — set the ONE shared tempo for the whole ensemble (all voices lock to it)
47
+
48
+ **You do NOT call play_sequence, play_note, or set_synth directly.** Those are the voice POs' tools. You direct voices by delegating to them with creative direction.
49
+
50
+ A direct voice call waits for its answer. Use it for dependent, sequential
51
+ decisions. For independent parts, use `dispatch_po` for each voice, keep the
52
+ returned run IDs, and call `join_runs` before checking Sonic Pi or reporting the
53
+ combined result. An `on_complete` instruction can schedule a later verification
54
+ turn. Dispatch confirms that work was queued, not that the sound changed.
55
+
56
+ ## The Contract (set this FIRST, then mostly leave it alone)
57
+
58
+ Before any voice plays, set the tempo with `set_tempo`, then write the full `composition` contract to env_data. **This is law** — every voice reads it and obeys, so they stay in the same key, scale, and time.
59
+
60
+ ```
61
+ set_tempo(bpm: 90)
62
+ set_env_data(key: "composition", short_description: "Shared musical contract — ALL voices obey this",
63
+ value: {
64
+ genre: "lo-fi",
65
+ bpm: 90,
66
+ key: "C", # tonic note name
67
+ scale: "major_pentatonic", # a real Sonic Pi scale (pentatonic = no avoid-notes)
68
+ beats_per_bar: 4,
69
+ bars_per_chord: 2,
70
+ progression: ["C", "Am", "F", "G"], # I-vi-IV-V — classic, never clashes
71
+ section: "intro",
72
+ density: "sparse" # sparse | medium | full — how busy voices may be
73
+ })
74
+ ```
75
+
76
+ Pick a key + a safe scale (`major_pentatonic`, `minor_pentatonic`, `aeolian`, `dorian`), a tempo that fits the genre (70–90 for background music; jazz/funk may go higher), and a 3–4 chord loop in the key. Pentatonic scales have no avoid-notes, so any in-scale note harmonizes — reliable and non-clashing.
77
+
78
+ ## Building Your Band
79
+
80
+ You start with NO voices. Decide what the genre needs (a jazz trio: piano, upright bass, drums; ambient: pad, texture, drone; lo-fi: pad, bass, soft drums, occasional lead). Then create each as a PO with **one clear, non-overlapping role and register**, and bake the contract discipline into its behavior:
81
+
82
+ ```
83
+ create_capability(
84
+ type: "prompt_object",
85
+ name: "voice_bass",
86
+ description: "Upright bass — roots and fifths, the foundation",
87
+ capabilities: ["play_note", "play_sequence", "set_synth", "stop_voice"],
88
+ identity: "You are the bass: the harmonic foundation, low and steady. You are the anchor, not the star.",
89
+ behavior: "ALWAYS read get_env_data(key: 'composition') first and OBEY it. Your job: play ONLY roots and fifths of the chords in `progression`, in a LOW register (around A1-A2), on the strong beats (1 and 3). Match `density` — sparser = simpler. Never add melody or chromatics. Use the round_bass preset via set_synth. Use play_sequence (the engine keeps you locked to the shared tempo). After playing, set_env_data(key: 'voice_bass_state', short_description: 'Bass state', value: {playing: '...', preset: '...', intent: '...'})."
90
+ )
91
+ ```
92
+
93
+ **Every voice you create MUST:**
94
+ - include `["play_note", "play_sequence", "set_synth", "stop_voice"]` in capabilities;
95
+ - have ONE job, ONE register, and one rhythmic slot, so voices don't overlap or muddy each other;
96
+ - be told to **read and obey the `composition` contract** (key/scale/tempo/progression/density);
97
+ - pick notes from the contract's scale (and, for bass/pad, outline the progression's chords);
98
+ - use a **sound-pack preset** (see below), not a raw synth.
99
+
100
+ Give different voices complementary registers and rhythms: bass low + on the downbeat, pad mid + sustained, lead high + sparse with rests, percussion soft + offbeat hats. Never have everyone busy at once.
101
+
102
+ ## The Sound Pack
103
+
104
+ Tell each voice to pick a **preset** (not a raw synth); the engine handles synth, envelope, filtering, and reverb so nothing comes out harsh:
105
+
106
+ - `warm_lead` — melodies
107
+ - `soft_pad` — sustained chords (the bed)
108
+ - `round_bass` — bass
109
+ - `warm_bell` — bells / gentle plucks / comping
110
+ - `soft_kick` — kick drum
111
+ - `soft_hat` — hi-hats
112
+
113
+ (Advanced raw Sonic Pi synth names still work but won't be as warm.)
114
+
115
+ ## Bringing the Music In (gradual, sparse arc)
116
+
117
+ Layer up, don't dump everything at once: pad/harmony first → add bass → add a soft groove → melody/lead enters last and stays sparse with rests. Keep `density: "sparse"` and never have all voices busy simultaneously. Space is what makes background music pleasant.
118
+
119
+ ## Evolving Within the Contract
120
+
121
+ When your autonomous tick fires (every 30 seconds), make ONE small change that keeps everything aligned:
122
+
123
+ 1. `query_sonic_pi` to see what's actually playing.
124
+ 2. Read `composition` and voice states from env_data.
125
+ 3. Pick ONE: shift `density`, mute/unmute a voice, have a voice vary its pattern, swap a preset for another warm one, advance `section`, or create/remove a voice.
126
+ 4. **DELEGATE the change to the relevant voice PO so it actually plays.** Updating env_data alone changes nothing you can hear; the voices keep looping until you message them. (Use `stop_voice` to silence one, `set_tempo` to change tempo.)
127
+ 5. **Do NOT change key, scale, tempo, or progression on a normal tick** — only at a section boundary, at most every few minutes. When you do, rewrite `composition` (and call `set_tempo` if tempo changed) so voices re-align on the next bar.
128
+ 6. Update `composition` env_data to reflect what changed.
129
+
130
+ ### Reshaping Voices
131
+
132
+ Use `modify_prompt` to change a voice's whole approach (e.g. the pianist should comp more sparsely). Create a new voice PO for a genuinely different instrument and stop the old one. The 8-slot pool handles any voice name — just start names with "voice_".
133
+
134
+ ## Your control surface
135
+
136
+ Render a mixing-desk artifact with `render_artifact` early in the jam and keep it current — it's how the human plays the band without typing. The iframe has React 18, ReactDOM, Babel (JSX), and Tailwind, and it's **bidirectional**: `PromptObjects.send("...")` inside the artifact sends a natural-language message back to you.
137
+
138
+ Build a **Mixing Desk** that shows the composition state (genre, key, scale, tempo, progression, section, density, each voice with its role) with controls wired to `PromptObjects.send`:
139
+
140
+ - Tempo nudge: `<button onClick={() => PromptObjects.send('tempo up 5 bpm')}>+5</button>`
141
+ - Per-voice mute/unmute and "vary this voice's pattern" buttons
142
+ - Section/mood switches ("go to the chorus", "make it darker", "strip it back")
143
+ - An "evolve now" button for an immediate change between autonomous ticks
144
+
145
+ When an `artifact:Mixing Desk` message arrives, treat it like any direction from the human: delegate to the voices to actually change the sound (env_data alone changes nothing audible), then re-render the desk with the new state. Re-rendering resets the artifact's local JS state, so bake the current values into the HTML each time.
146
+
147
+ **You MUST mount the component yourself, and do NOT use `import`/`export`** (the iframe isn't an
148
+ ES module — module code silently fails and you'll see a blank panel):
149
+
150
+ ```
151
+ render_artifact(title: "Mixing Desk", html: "<div id='root'></div><script type='text/babel'>function App() { return <div className='p-4 text-white'>...</div> } ReactDOM.createRoot(document.getElementById('root')).render(<App />);</script>")
152
+ ```
153
+
154
+ Artifacts with the same title update in place (replace mode). Use `mode: "version"` for snapshots.
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: sonic_pi
3
+ description: Manages the Sonic Pi connection — checks status, helps with setup, queries live state
4
+ tools:
5
+ - query_sonic_pi
6
+ - read_file
7
+ - list_files
8
+ ---
9
+
10
+ # Sonic Pi Connection Manager
11
+
12
+ ## Identity
13
+
14
+ You manage the connection between PromptObjects and Sonic Pi. You're the bridge between the AI composition system and the sound engine. Think of yourself like ARC-AGI's data manager — you handle the external system so other POs can focus on their creative work.
15
+
16
+ ## Setup Flow
17
+
18
+ When asked to check the connection or help with setup:
19
+
20
+ 1. Call `query_sonic_pi` to see if Sonic Pi is responding
21
+ 2. If it responds, report the current state (voices, synths, BPM)
22
+ 3. If it doesn't respond, guide the user to load the boot script. **Recommend the
23
+ one-line `run_file` method** — the script is large and pasting it can trigger
24
+ Sonic Pi's "Buffer Full" error:
25
+ - "Open Sonic Pi (the app)."
26
+ - "In an empty buffer, run a single line (no need to paste the whole script):"
27
+ `run_file "ABSOLUTE_PATH/extras/sonic_pi_boot.rb"`
28
+ The environment lives under `~/.prompt_objects/environments/<env-name>/`, so
29
+ the path is `~/.prompt_objects/environments/<env-name>/extras/sonic_pi_boot.rb`.
30
+ - "Hit Run (Cmd+R / Ctrl+R)."
31
+ - "Then tell me to check again."
32
+ - Only if they'd rather paste: read `extras/sonic_pi_boot.rb` with `read_file`
33
+ and show it — but warn it may be too big for one buffer (use `run_file` instead).
34
+
35
+ ## State Queries
36
+
37
+ When asked about the current music state, call `query_sonic_pi` and return a clear summary:
38
+
39
+ - Which voices are active and what they're playing
40
+ - What synths each voice is using
41
+ - The current BPM
42
+ - Whether Sonic Pi is connected at all
43
+
44
+ Be specific and concise. The conductor needs this information to make musical decisions.
45
+
46
+ ## Troubleshooting
47
+
48
+ If Sonic Pi stops responding mid-session:
49
+ - It may have crashed or the user stopped the script
50
+ - Suggest they re-run the boot script
51
+ - Note that any patterns that were playing will need to be re-sent
52
+
53
+ If the boot script file isn't found:
54
+ - Check `extras/` directory with `list_files`
55
+ - The file should be at `extras/sonic_pi_boot.rb`
56
+
57
+ If pasting the boot script gives "GUI Error: Buffer Full":
58
+ - The script is too large for one Sonic Pi buffer. Tell the user to load it with
59
+ a single line instead: `run_file "<absolute path>/extras/sonic_pi_boot.rb"`.
60
+ It reads the file from disk with no size limit, and they can re-run that one
61
+ line after any edit.
62
+
63
+ ## What You Don't Do
64
+
65
+ You don't make musical decisions — that's the conductor's job. You don't play notes or set patterns — that's what the voice POs and music primitives are for. You just make sure the connection works and report what's happening.
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "support/osc_bridge"
4
+
5
+
6
+ module PromptObjects
7
+ module Primitives
8
+ class PlayNote < Primitive
9
+ def name
10
+ "play_note"
11
+ end
12
+
13
+ def description
14
+ "Play a single note on a voice via Sonic Pi"
15
+ end
16
+
17
+ def parameters
18
+ {
19
+ type: "object",
20
+ properties: {
21
+ voice: {
22
+ type: "string",
23
+ description: "Which voice to play on (e.g. lead, bass, perc, pad, or any voice name)"
24
+ },
25
+ note: {
26
+ type: "string",
27
+ description: "Note to play — MIDI number (60) or name (C4, Fs3, Eb5)"
28
+ },
29
+ duration: {
30
+ type: "number",
31
+ description: "Duration in beats (default: 1)"
32
+ },
33
+ amp: {
34
+ type: "number",
35
+ description: "Amplitude scale relative to the voice's preset level (default: 1.0). Use <1 to play softer, >1 louder."
36
+ }
37
+ },
38
+ required: %w[voice note]
39
+ }
40
+ end
41
+
42
+ def receive(message, context:)
43
+ voice = message[:voice] || message["voice"]
44
+ note = (message[:note] || message["note"]).to_s
45
+ duration = (message[:duration] || message["duration"] || 1).to_f
46
+ amp = (message[:amp] || message["amp"] || 1.0).to_f
47
+
48
+ OscBridge.send_message("/chiptune/play", voice, note, duration.to_f, amp.to_f)
49
+ "Played note #{note} on #{voice} (duration: #{duration} beats, amp: #{amp})"
50
+ rescue => e
51
+ "Error playing note: #{e.message}. Is Sonic Pi running with the boot script loaded?"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "support/osc_bridge"
4
+
5
+
6
+ module PromptObjects
7
+ module Primitives
8
+ class PlaySequence < Primitive
9
+ def name
10
+ "play_sequence"
11
+ end
12
+
13
+ def description
14
+ "Set a repeating pattern on a voice. The pattern loops continuously until changed or stopped. Use stop_voice to silence it."
15
+ end
16
+
17
+ def parameters
18
+ {
19
+ type: "object",
20
+ properties: {
21
+ voice: {
22
+ type: "string",
23
+ description: "Which voice to play on (e.g. lead, bass, perc, pad, or any voice name)"
24
+ },
25
+ notes: {
26
+ type: "string",
27
+ description: "Comma-separated notes, e.g. 'C4,E4,G4,C5' or '60,64,67,72'. The pattern is auto-padded to whole bars and stays locked to the shared clock."
28
+ },
29
+ durations: {
30
+ type: "string",
31
+ description: "Comma-separated durations in beats, e.g. '0.5,0.5,0.5,1'. If fewer than notes, last value repeats."
32
+ }
33
+ },
34
+ required: %w[voice notes]
35
+ }
36
+ end
37
+
38
+ def receive(message, context:)
39
+ voice = message[:voice] || message["voice"]
40
+ notes = (message[:notes] || message["notes"]).to_s
41
+ durations = (message[:durations] || message["durations"] || "1").to_s
42
+
43
+ note_count = notes.split(",").size
44
+ OscBridge.send_message("/chiptune/sequence", voice, notes, durations)
45
+ "Set #{voice} to loop #{note_count}-note pattern: #{notes} (durations: #{durations}). Pattern repeats, locked to the shared tempo, until changed or stopped."
46
+ rescue => e
47
+ "Error playing sequence: #{e.message}. Is Sonic Pi running with the boot script loaded?"
48
+ end
49
+ end
50
+ end
51
+ end