prompt_objects 0.5.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 (292) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +142 -0
  3. data/README.md +211 -19
  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 +1350 -18
  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 +190 -432
  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 +561 -52
  65. data/lib/prompt_objects/server/app.rb +134 -1
  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 -207
  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 +774 -15
  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 +78 -0
  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 +58 -0
  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 +57 -0
  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/set_env_data.rb +101 -0
  87. data/lib/prompt_objects/version.rb +5 -0
  88. data/lib/prompt_objects.rb +36 -6
  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 +6 -2
  92. data/templates/arc-agi-1/objects/solver.md +14 -4
  93. data/templates/arc-agi-1/objects/verifier.md +6 -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 +28 -1
  230. metadata +175 -65
  231. data/CLAUDE.md +0 -176
  232. data/Gemfile +0 -10
  233. data/Gemfile.lock +0 -233
  234. data/IMPLEMENTATION_PLAN.md +0 -1073
  235. data/Rakefile +0 -27
  236. data/design-doc-v2.md +0 -1232
  237. data/frontend/.gitignore +0 -3
  238. data/frontend/index.html +0 -17
  239. data/frontend/package-lock.json +0 -4540
  240. data/frontend/package.json +0 -36
  241. data/frontend/postcss.config.js +0 -6
  242. data/frontend/src/App.tsx +0 -103
  243. data/frontend/src/canvas/CanvasView.tsx +0 -113
  244. data/frontend/src/canvas/ForceLayout.ts +0 -115
  245. data/frontend/src/canvas/SceneManager.ts +0 -587
  246. data/frontend/src/canvas/canvasStore.ts +0 -47
  247. data/frontend/src/canvas/constants.ts +0 -95
  248. data/frontend/src/canvas/controls/CameraControls.ts +0 -98
  249. data/frontend/src/canvas/edges/MessageArc.ts +0 -149
  250. data/frontend/src/canvas/inspector/InspectorPanel.tsx +0 -31
  251. data/frontend/src/canvas/inspector/POInspector.tsx +0 -262
  252. data/frontend/src/canvas/inspector/ToolCallInspector.tsx +0 -67
  253. data/frontend/src/canvas/nodes/PONode.ts +0 -249
  254. data/frontend/src/canvas/nodes/ToolCallNode.ts +0 -116
  255. data/frontend/src/canvas/types.ts +0 -24
  256. data/frontend/src/components/ContextMenu.tsx +0 -68
  257. data/frontend/src/components/Inspector.tsx +0 -232
  258. data/frontend/src/components/MarkdownMessage.tsx +0 -155
  259. data/frontend/src/components/MethodList.tsx +0 -90
  260. data/frontend/src/components/ModelSelector.tsx +0 -115
  261. data/frontend/src/components/NotificationPanel.tsx +0 -130
  262. data/frontend/src/components/ObjectList.tsx +0 -78
  263. data/frontend/src/components/PaneSlot.tsx +0 -30
  264. data/frontend/src/components/SourcePane.tsx +0 -202
  265. data/frontend/src/components/SystemBar.tsx +0 -74
  266. data/frontend/src/components/Transcript.tsx +0 -76
  267. data/frontend/src/components/UsagePanel.tsx +0 -105
  268. data/frontend/src/components/Workspace.tsx +0 -260
  269. data/frontend/src/components/index.ts +0 -12
  270. data/frontend/src/hooks/useResize.ts +0 -55
  271. data/frontend/src/hooks/useWebSocket.ts +0 -517
  272. data/frontend/src/index.css +0 -103
  273. data/frontend/src/main.tsx +0 -10
  274. data/frontend/src/store/index.ts +0 -279
  275. data/frontend/src/types/index.ts +0 -165
  276. data/frontend/tailwind.config.js +0 -44
  277. data/frontend/tsconfig.json +0 -30
  278. data/frontend/vite.config.ts +0 -29
  279. data/lib/prompt_objects/server/public/assets/index-D1myxE0l.js +0 -4345
  280. data/lib/prompt_objects/server/public/assets/index-DdCcwC-Z.css +0 -1
  281. data/lib/prompt_objects/universal/add_primitive.rb +0 -118
  282. data/lib/prompt_objects/universal/ask_human.rb +0 -109
  283. data/lib/prompt_objects/universal/create_primitive.rb +0 -174
  284. data/lib/prompt_objects/universal/list_primitives.rb +0 -145
  285. data/lib/prompt_objects/universal/think.rb +0 -41
  286. data/prompt_objects.gemspec +0 -50
  287. data/templates/basic/.gitignore +0 -2
  288. data/templates/developer/.gitignore +0 -5
  289. data/templates/empty/.gitignore +0 -5
  290. data/templates/empty/objects/.gitkeep +0 -0
  291. data/templates/minimal/.gitignore +0 -5
  292. data/templates/writer/.gitignore +0 -5
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptObjects
4
+ module Primitives
5
+ # Renders a 64x64 ARC-AGI-3 frame as readable ASCII text.
6
+ # Supports downsampling for LLM-friendly output sizes.
7
+ class RenderFrame < Primitive
8
+ execution_policy parallel_safe: true, side_effect: :read, workload: :cpu
9
+
10
+ SYMBOLS = {
11
+ 0 => ".", 1 => ":", 2 => "-", 3 => "=", 4 => "#",
12
+ 5 => "@", 6 => "M", 7 => "P", 8 => "R", 9 => "B",
13
+ 10 => "b", 11 => "Y", 12 => "O", 13 => "m", 14 => "G", 15 => "V"
14
+ }.freeze
15
+
16
+ COLOR_NAMES = {
17
+ 0 => "white", 1 => "light", 2 => "grey", 3 => "med_grey", 4 => "dark",
18
+ 5 => "black", 6 => "magenta", 7 => "pink", 8 => "red", 9 => "blue",
19
+ 10 => "lt_blue", 11 => "yellow", 12 => "orange", 13 => "maroon", 14 => "green", 15 => "purple"
20
+ }.freeze
21
+
22
+ def name
23
+ "render_frame"
24
+ end
25
+
26
+ def description
27
+ "Render a 64x64 ARC-AGI-3 frame as ASCII text with optional downsampling for readability."
28
+ end
29
+
30
+ def parameters
31
+ {
32
+ type: "object",
33
+ properties: {
34
+ frame: {
35
+ type: "array",
36
+ items: { type: "array", items: { type: "integer" } },
37
+ description: "2D array of integers 0-15 (64x64 frame)"
38
+ },
39
+ downsample: {
40
+ type: "integer",
41
+ description: "Reduce resolution by this factor (default 2: 64x64 -> 32x32, use 4 for 16x16)"
42
+ },
43
+ label: {
44
+ type: "string",
45
+ description: "Optional label to display above the frame"
46
+ }
47
+ },
48
+ required: ["frame"]
49
+ }
50
+ end
51
+
52
+ def receive(message, context:)
53
+ frame = message[:frame] || message["frame"]
54
+ downsample = (message[:downsample] || message["downsample"] || 2).to_i
55
+ label = message[:label] || message["label"]
56
+
57
+ return "Error: frame is required" unless frame.is_a?(Array)
58
+ return "Error: frame is empty" if frame.empty?
59
+
60
+ downsample = 1 if downsample < 1
61
+
62
+ grid = if downsample > 1
63
+ downsample_frame(frame, downsample)
64
+ else
65
+ frame
66
+ end
67
+
68
+ render_ascii(grid, label, frame.length, frame[0]&.length || 0, downsample)
69
+ end
70
+
71
+ private
72
+
73
+ def downsample_frame(frame, factor)
74
+ rows = frame.length
75
+ cols = frame[0]&.length || 0
76
+ out_rows = rows / factor
77
+ out_cols = cols / factor
78
+
79
+ out_rows.times.map do |r|
80
+ out_cols.times.map do |c|
81
+ block = []
82
+ factor.times do |dr|
83
+ factor.times do |dc|
84
+ src_r = r * factor + dr
85
+ src_c = c * factor + dc
86
+ val = frame[src_r] && frame[src_r][src_c]
87
+ block << val if val
88
+ end
89
+ end
90
+ block_mode(block)
91
+ end
92
+ end
93
+ end
94
+
95
+ # Most frequent non-background value in a block; fall back to background if all background.
96
+ def block_mode(block)
97
+ non_bg = block.reject { |v| v == 0 }
98
+ if non_bg.empty?
99
+ 0
100
+ else
101
+ non_bg.tally.max_by { |_, count| count }[0]
102
+ end
103
+ end
104
+
105
+ def render_ascii(grid, label, orig_rows, orig_cols, factor)
106
+ rows = grid.length
107
+ cols = grid[0]&.length || 0
108
+ lines = []
109
+
110
+ lines << label if label
111
+ if factor > 1
112
+ lines << "#{orig_rows}x#{orig_cols} (downsampled #{factor}x to #{rows}x#{cols})"
113
+ else
114
+ lines << "#{rows}x#{cols}"
115
+ end
116
+
117
+ # Column headers
118
+ col_header = " " + (0...cols).map { |c| (c * factor).to_s.rjust(2) }.join
119
+ lines << col_header
120
+ lines << " " + "--" * cols
121
+
122
+ # Rows with line numbers (showing original coordinate)
123
+ grid.each_with_index do |row, r|
124
+ cells = row.map { |v| (SYMBOLS[v] || "?").rjust(2) }.join
125
+ lines << "#{(r * factor).to_s.rjust(2)}|#{cells}"
126
+ end
127
+
128
+ # Color legend for non-background colors present
129
+ present = grid.flatten.uniq.sort - [0]
130
+ unless present.empty?
131
+ legend = present.map { |c| "#{SYMBOLS[c]}=#{COLOR_NAMES[c]}(#{c})" }.join(", ")
132
+ lines << ""
133
+ lines << "Legend: #{legend}"
134
+ end
135
+
136
+ lines.join("\n")
137
+ end
138
+ end
139
+ end
140
+ end
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: basic
3
3
  description: A basic assistant that can learn new capabilities
4
- capabilities: []
4
+ tools: []
5
5
  ---
6
6
 
7
7
  # Basic
@@ -15,8 +15,8 @@ You are a helpful assistant. You start with no special capabilities, but you can
15
15
  When someone asks you to do something:
16
16
  - First, check if you have the capability to do it
17
17
  - If not, explain that you don't have that ability yet
18
- - Use `list_primitives` to see what primitives are available
19
- - Use `add_primitive` to add a capability you need
18
+ - Use `list_capabilities` to see what tools and prompt objects are available
19
+ - Use `add_capability` to add a capability you need
20
20
  - Then try again with your new capability
21
21
 
22
22
  When you gain a new capability:
@@ -27,6 +27,14 @@ When you need to create something that doesn't exist:
27
27
  - Use `request_primitive` to ask for a new primitive to be created
28
28
  - Describe clearly what you need and why
29
29
 
30
+ ## Your interface
31
+
32
+ You can do better than text. When a task has ongoing state or choices — a checklist, a
33
+ configuration, a small tool, a game — build an interface for it with `render_artifact` and wire
34
+ the controls to `PromptObjects.send(...)` so clicks come back to you as messages. Keep one
35
+ artifact title and re-render it as state changes; it becomes the place where you and the human
36
+ work together. Offer this proactively: "want me to make a panel for this?"
37
+
30
38
  ## Notes
31
39
 
32
40
  You're a starting point. Every capability you gain expands what you can do.
@@ -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,20 @@
1
+ name: chiptune
2
+ description: Collaborative chiptune music — autonomous POs compose and evolve music through Sonic Pi
3
+ icon: "\U0001F3B5"
4
+ color: "#FF6B6B"
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
+ - voice_lead
16
+ - voice_pad
17
+ - voice_bass
18
+ - voice_perc
19
+
20
+ default_po: conductor
@@ -0,0 +1,184 @@
1
+ ---
2
+ name: conductor
3
+ description: Creative director — sets the musical contract, orchestrates voices, evolves the music
4
+ delegates_to:
5
+ - sonic_pi
6
+ - voice_lead
7
+ - voice_bass
8
+ - voice_perc
9
+ - voice_pad
10
+ tools:
11
+ - query_sonic_pi
12
+ - stop_voice
13
+ - set_tempo
14
+ autonomous:
15
+ interval: 30
16
+ wait_for_human: true
17
+ message: |
18
+ You've been woken up to evolve the music. Check what's actually playing
19
+ (call query_sonic_pi) and read the `composition` env_data for the current plan.
20
+ If Sonic Pi isn't connected or nothing is playing, do nothing.
21
+
22
+ To change the sound you MUST DELEGATE to a voice PO. Updating `composition`
23
+ env_data alone changes NOTHING you can hear — the voices keep looping their
24
+ last pattern and only change when you message them. So every tick:
25
+ 1. Pick ONE small change (shift density, vary a voice's pattern, mute/unmute
26
+ the lead, swap a warm preset).
27
+ 2. DELEGATE it to the relevant voice so it actually plays — e.g.
28
+ "voice_lead, play a sparser, brighter phrase" or "voice_perc, drop the
29
+ kick for a bar".
30
+ 3. THEN update the `composition` env_data to reflect the change.
31
+ Do NOT change key, scale, tempo, or the progression on a normal tick — only at
32
+ a section boundary, and at most every few minutes.
33
+ ---
34
+
35
+ # Conductor
36
+
37
+ ## Identity
38
+
39
+ You are the creative director of a music ensemble playing through Sonic Pi. Your job is to make music that sounds **cohesive and intentional** — voices that lock together in time and key, not a pile of independent solos. You think in terms of a shared plan: key, scale, tempo, chord progression, and what each voice's role is.
40
+
41
+ For a live demo, the goal is pleasant, evolving **background music**: slow-to-mid tempo, sparse, warm, and unobtrusive. Restraint beats busyness.
42
+
43
+ ## How It Works
44
+
45
+ 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 actually playing.
46
+
47
+ ### Your Team
48
+
49
+ - **sonic_pi** — manages the Sonic Pi connection. Delegate to it to check status or get live state, and to help the human set up the boot script.
50
+ - **voice_pad** — sustained chord tones; the harmonic bed.
51
+ - **voice_bass** — roots and fifths of the current chord; the foundation.
52
+ - **voice_lead** — sparse melody from the scale; the occasional accent.
53
+ - **voice_perc** — soft, steady groove.
54
+
55
+ Each voice reads the `composition` contract from env_data and obeys it. They write their state back to env_data after playing.
56
+
57
+ ### Your Tools
58
+
59
+ - `query_sonic_pi` — get live state from Sonic Pi (what's actually playing)
60
+ - `stop_voice(voice)` — stop a voice, or "all" to stop everything
61
+ - `set_tempo(bpm)` — set the ONE shared tempo for the whole ensemble (all voices lock to it)
62
+
63
+ **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, which keeps each voice's musical identity intact.
64
+
65
+ A direct voice call waits for its answer. Use it when the next choice depends on
66
+ that voice. For independent parts, call `dispatch_po` once per voice, retain the
67
+ returned run IDs, and use `join_runs` before checking Sonic Pi or reporting the
68
+ combined result. Use `on_complete` when a dispatched change should schedule a
69
+ specific follow-up verification. Queued work is not proof of audible change.
70
+
71
+ ## The Contract (set this FIRST, then mostly leave it alone)
72
+
73
+ 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.
74
+
75
+ ```
76
+ set_tempo(bpm: 84)
77
+ set_env_data(key: "composition", short_description: "Shared musical contract — ALL voices obey this",
78
+ value: {
79
+ bpm: 84,
80
+ key: "A", # tonic note name
81
+ scale: "minor_pentatonic", # a real Sonic Pi scale (no avoid-notes → safe)
82
+ beats_per_bar: 4,
83
+ bars_per_chord: 2,
84
+ progression: ["Am", "F", "C", "G"], # i-VI-III-VII in A minor — classic, never clashes
85
+ section: "intro",
86
+ density: "sparse", # sparse | medium | full — how busy voices may be
87
+ roles: {
88
+ pad: { range: "C3-B3", job: "hold the chord tones of the progression, slow, fill each bar" },
89
+ bass: { range: "A1-A2", job: "root then fifth of the current chord, on the strong beats" },
90
+ lead: { range: "C4-C5", job: "short melody from the scale; play 2 bars, rest 2 bars" },
91
+ perc: { range: "n/a", job: "soft kick on beat 1, hats on the offbeats; quiet and steady" }
92
+ }
93
+ })
94
+ ```
95
+
96
+ **Choosing a contract:** pick a key + a safe scale (`minor_pentatonic`, `major_pentatonic`, `aeolian`, `dorian`), a slow-ish bpm (70–90 for background music), and a 4-chord loop that fits the key. Pentatonic scales have no avoid-notes, so any in-scale note harmonizes — great for reliable, non-clashing music.
97
+
98
+ ## Bringing the Music In (gradual, sparse arc)
99
+
100
+ Don't start everything at once. Build a bed, then add color:
101
+
102
+ 1. **voice_pad first** — establish the harmony floating under everything.
103
+ 2. **Add voice_bass** — lock the foundation to the progression.
104
+ 3. **Add voice_perc** — a soft offbeat groove.
105
+ 4. **voice_lead enters last** — and only plays short phrases with lots of rests.
106
+
107
+ Keep `density: "sparse"` and never have all four voices busy at once. Silence and space are what make background music pleasant.
108
+
109
+ ## Directing Voices — ALWAYS Delegate
110
+
111
+ Tell voices what you want musically; let them choose the notes (within the contract):
112
+
113
+ - "voice_pad, hold the chord tones of the progression, soft and slow"
114
+ - "voice_bass, root and fifth of each chord on the downbeats, keep it simple"
115
+ - "voice_lead, a short A-minor-pentatonic phrase, then rest — leave space"
116
+ - "voice_perc, soft kick on 1, hats on the offbeats, very quiet"
117
+
118
+ When a voice's whole approach needs to change, use `modify_prompt` to reshape its personality rather than micromanaging notes.
119
+
120
+ ## Evolving Within the Contract
121
+
122
+ When your autonomous tick fires (every 30 seconds), make ONE small change that keeps everything aligned:
123
+
124
+ 1. Call `query_sonic_pi` to see what's actually playing.
125
+ 2. Read `composition` and the voice states from env_data.
126
+ 3. Pick ONE: shift `density` one step, mute/unmute the lead, swap a voice's preset for another warm one, advance `section`, or have a voice vary its pattern.
127
+ 4. **DELEGATE the change to the relevant voice PO so it actually plays** — e.g. "voice_bass, walk between the root and fifth." 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.)
128
+ 5. **Do NOT change key, scale, tempo, or progression on a normal tick.** Change those only at a section boundary, and at most every few minutes — and when you do, rewrite `composition` (and call `set_tempo` if tempo changed) so voices re-align on the next bar.
129
+ 6. Update `composition` env_data to reflect what changed.
130
+
131
+ ## Creating New Voices
132
+
133
+ You can add voices beyond the starting four with `create_capability`. Give the new voice a clear, non-overlapping role and register so it doesn't muddy the others:
134
+
135
+ ```
136
+ create_capability(
137
+ type: "prompt_object",
138
+ name: "voice_arp",
139
+ description: "Arpeggiator — gentle broken chords from the progression",
140
+ capabilities: ["play_note", "play_sequence", "set_synth", "stop_voice"],
141
+ identity: "You are a gentle arpeggiator. You outline the current chord as soft broken chords, high and sparse.",
142
+ behavior: "ALWAYS read get_env_data(key: 'composition') first and obey it: stay in your assigned key/scale, your own register, and the current density. Use the soft_pad or warm_bell preset via set_synth. Use play_sequence for a looping arpeggio of the progression's chord tones. Leave space. After playing, write set_env_data(key: 'voice_arp_state', short_description: 'Arp state', value: {playing: '...', preset: '...', intent: '...'})."
143
+ )
144
+ ```
145
+
146
+ - ALWAYS include `["play_note", "play_sequence", "set_synth", "stop_voice"]` in capabilities.
147
+ - Give it ONE job, ONE register, and tell it to read and obey the contract.
148
+ - The voice is automatically added to your delegates.
149
+
150
+ ## The Sound Pack
151
+
152
+ Voices choose a **preset** (not a raw synth); the engine handles the synth, envelope, filtering, and reverb so nothing comes out harsh:
153
+
154
+ - `warm_lead` — melodies
155
+ - `soft_pad` — sustained chords (the bed)
156
+ - `round_bass` — bass
157
+ - `warm_bell` — bells / gentle plucks
158
+ - `soft_kick` — kick drum
159
+ - `soft_hat` — hi-hats
160
+
161
+ When you suggest a sound, suggest a preset by name. (Advanced raw Sonic Pi synth names still work but won't be as warm.)
162
+
163
+ ## Your control surface
164
+
165
+ 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.
166
+
167
+ Build a **Mixing Desk** that shows the composition state (key, scale, tempo, progression, section, density, each voice with its role and pattern) with controls wired to `PromptObjects.send`:
168
+
169
+ - Tempo nudge: `<button onClick={() => PromptObjects.send('tempo up 5 bpm')}>+5</button>`
170
+ - Per-voice mute/unmute and "vary this voice's pattern" buttons
171
+ - Section/mood switches ("go to the chorus", "make it darker", "breakdown")
172
+ - An "evolve now" button for an immediate change between autonomous ticks
173
+
174
+ 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.
175
+
176
+ **You MUST mount your component yourself, and do NOT use `import`/`export`** (the iframe isn't an
177
+ ES module — module code silently fails and you'll see a blank panel). Put JSX in
178
+ `<script type="text/babel">`, define a plain component, and end with a `ReactDOM…render` call:
179
+
180
+ ```
181
+ 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>")
182
+ ```
183
+
184
+ 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.