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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +142 -0
- data/README.md +211 -19
- data/exe/poop +5 -0
- data/exe/prompt_objects +1 -1108
- data/exe/prompt_objects_mcp +5 -3
- data/lib/prompt_objects/capability.rb +33 -0
- data/lib/prompt_objects/cli/diagnostics_command.rb +69 -0
- data/lib/prompt_objects/cli/env_command.rb +435 -0
- data/lib/prompt_objects/cli/events_command.rb +145 -0
- data/lib/prompt_objects/cli/explore_command.rb +155 -0
- data/lib/prompt_objects/cli/helpers.rb +91 -0
- data/lib/prompt_objects/cli/message_command.rb +216 -0
- data/lib/prompt_objects/cli/repl_command.rb +366 -0
- data/lib/prompt_objects/cli/serve_command.rb +162 -0
- data/lib/prompt_objects/cli.rb +64 -422
- data/lib/prompt_objects/connectors/base.rb +16 -29
- data/lib/prompt_objects/connectors/mcp.rb +82 -98
- data/lib/prompt_objects/domain/records.rb +45 -0
- data/lib/prompt_objects/environment/exporter.rb +5 -4
- data/lib/prompt_objects/environment/git.rb +21 -1
- data/lib/prompt_objects/environment/importer.rb +5 -4
- data/lib/prompt_objects/environment/manager.rb +44 -6
- data/lib/prompt_objects/environment/manifest.rb +26 -4
- data/lib/prompt_objects/environment.rb +1350 -18
- data/lib/prompt_objects/execution/blocking_executor.rb +88 -0
- data/lib/prompt_objects/execution/context.rb +137 -0
- data/lib/prompt_objects/execution/contracts.rb +59 -0
- data/lib/prompt_objects/execution/permit_pool.rb +49 -0
- data/lib/prompt_objects/execution/policy.rb +47 -0
- data/lib/prompt_objects/execution/resource_lock_manager.rb +63 -0
- data/lib/prompt_objects/execution/runner.rb +498 -0
- data/lib/prompt_objects/execution/scheduler.rb +325 -0
- data/lib/prompt_objects/llm/factory.rb +4 -4
- data/lib/prompt_objects/llm/gemini_adapter.rb +25 -8
- data/lib/prompt_objects/llm/openai_adapter.rb +9 -1
- data/lib/prompt_objects/llm/pricing.rb +9 -2
- data/lib/prompt_objects/llm/response.rb +9 -4
- data/lib/prompt_objects/loader.rb +28 -1
- data/lib/prompt_objects/mcp/server.rb +6 -3
- data/lib/prompt_objects/mcp/tools/get_conversation.rb +15 -11
- data/lib/prompt_objects/mcp/tools/get_pending_requests.rb +2 -2
- data/lib/prompt_objects/mcp/tools/respond_to_request.rb +3 -3
- data/lib/prompt_objects/mcp/tools/send_message.rb +42 -32
- data/lib/prompt_objects/operations/diagnostics.rb +115 -0
- data/lib/prompt_objects/operations/retention.rb +75 -0
- data/lib/prompt_objects/operations/run_trace_builder.rb +152 -0
- data/lib/prompt_objects/primitives/http_get.rb +2 -0
- data/lib/prompt_objects/primitives/list_files.rb +2 -0
- data/lib/prompt_objects/primitives/read_file.rb +2 -0
- data/lib/prompt_objects/primitives/think.rb +36 -0
- data/lib/prompt_objects/primitives/write_file.rb +9 -0
- data/lib/prompt_objects/prompt_object.rb +190 -432
- data/lib/prompt_objects/repositories/artifact_repository.rb +109 -0
- data/lib/prompt_objects/repositories/base_repository.rb +37 -0
- data/lib/prompt_objects/repositories/definition_repository.rb +36 -0
- data/lib/prompt_objects/repositories/env_data_repository.rb +103 -0
- data/lib/prompt_objects/repositories/event_repository.rb +105 -0
- data/lib/prompt_objects/repositories/message_repository.rb +73 -0
- data/lib/prompt_objects/repositories/run_repository.rb +254 -0
- data/lib/prompt_objects/repositories/thread_repository.rb +87 -0
- data/lib/prompt_objects/repositories/tool_execution_repository.rb +88 -0
- data/lib/prompt_objects/repositories/workspace_session_repository.rb +61 -0
- data/lib/prompt_objects/server/api/routes.rb +561 -52
- data/lib/prompt_objects/server/app.rb +134 -1
- data/lib/prompt_objects/server/file_watcher.rb +9 -0
- data/lib/prompt_objects/server/public/assets/index-BjKlODDD.js +90 -0
- data/lib/prompt_objects/server/public/assets/index-w3fpIb5c.css +1 -0
- data/lib/prompt_objects/server/public/index.html +2 -2
- data/lib/prompt_objects/server/websocket_handler.rb +202 -207
- data/lib/prompt_objects/server.rb +170 -1
- data/lib/prompt_objects/service.rb +129 -0
- data/lib/prompt_objects/session/store.rb +774 -15
- data/lib/prompt_objects/universal/add_capability.rb +1 -1
- data/lib/prompt_objects/universal/create_capability.rb +4 -2
- data/lib/prompt_objects/universal/delete_env_data.rb +78 -0
- data/lib/prompt_objects/universal/delete_primitive.rb +1 -1
- data/lib/prompt_objects/universal/dispatch_po.rb +54 -0
- data/lib/prompt_objects/universal/get_env_data.rb +58 -0
- data/lib/prompt_objects/universal/get_run_status.rb +30 -0
- data/lib/prompt_objects/universal/join_runs.rb +35 -0
- data/lib/prompt_objects/universal/list_env_data.rb +57 -0
- data/lib/prompt_objects/universal/modify_primitive.rb +1 -1
- data/lib/prompt_objects/universal/render_artifact.rb +91 -0
- data/lib/prompt_objects/universal/request_primitive.rb +1 -1
- data/lib/prompt_objects/universal/set_env_data.rb +101 -0
- data/lib/prompt_objects/version.rb +5 -0
- data/lib/prompt_objects.rb +36 -6
- data/objects/coordinator.md +8 -4
- data/templates/arc-agi-1/objects/data_manager.md +2 -2
- data/templates/arc-agi-1/objects/observer.md +6 -2
- data/templates/arc-agi-1/objects/solver.md +14 -4
- data/templates/arc-agi-1/objects/verifier.md +6 -2
- data/templates/arc-agi-3/manifest.yml +19 -0
- data/templates/arc-agi-3/objects/analyst.md +79 -0
- data/templates/arc-agi-3/objects/player.md +76 -0
- data/templates/arc-agi-3/objects/strategist.md +97 -0
- data/templates/arc-agi-3/primitives/action_log.rb +133 -0
- data/templates/arc-agi-3/primitives/arc3_api.rb +223 -0
- data/templates/arc-agi-3/primitives/arc3_scorecard.rb +131 -0
- data/templates/arc-agi-3/primitives/frame_diff.rb +143 -0
- data/templates/arc-agi-3/primitives/frame_info.rb +96 -0
- data/templates/arc-agi-3/primitives/render_frame.rb +140 -0
- data/templates/basic/objects/basic.md +11 -3
- data/templates/chiptune/extras/sonic_pi_boot.rb +220 -0
- data/templates/chiptune/manifest.yml +20 -0
- data/templates/chiptune/objects/conductor.md +184 -0
- data/templates/chiptune/objects/sonic_pi.md +65 -0
- data/templates/chiptune/objects/voice_bass.md +44 -0
- data/templates/chiptune/objects/voice_lead.md +44 -0
- data/templates/chiptune/objects/voice_pad.md +43 -0
- data/templates/chiptune/objects/voice_perc.md +45 -0
- data/templates/chiptune/primitives/play_note.rb +59 -0
- data/templates/chiptune/primitives/play_sequence.rb +55 -0
- data/templates/chiptune/primitives/query_sonic_pi.rb +141 -0
- data/templates/chiptune/primitives/set_synth.rb +57 -0
- data/templates/chiptune/primitives/set_tempo.rb +45 -0
- data/templates/chiptune/primitives/stop_voice.rb +46 -0
- data/templates/chiptune/primitives/support/osc_bridge.rb +33 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/LICENSE +19 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
- data/templates/chiptune/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
- data/templates/developer/objects/code_reviewer.md +1 -1
- data/templates/developer/objects/coordinator.md +16 -3
- data/templates/developer/objects/debugger.md +3 -3
- data/templates/empty/objects/assistant.md +10 -5
- data/templates/minimal/objects/assistant.md +10 -5
- data/templates/music/extras/sonic_pi_boot.rb +220 -0
- data/templates/music/manifest.yml +16 -0
- data/templates/music/objects/conductor.md +154 -0
- data/templates/music/objects/sonic_pi.md +65 -0
- data/templates/music/primitives/play_note.rb +55 -0
- data/templates/music/primitives/play_sequence.rb +51 -0
- data/templates/music/primitives/query_sonic_pi.rb +139 -0
- data/templates/music/primitives/set_synth.rb +53 -0
- data/templates/music/primitives/set_tempo.rb +43 -0
- data/templates/music/primitives/stop_voice.rb +44 -0
- data/templates/music/primitives/support/osc_bridge.rb +33 -0
- data/templates/music/vendor/osc-ruby-1.1.5/LICENSE +19 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
- data/templates/music/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
- data/templates/music-beginner/README.md +100 -0
- data/templates/music-beginner/extras/README.md +27 -0
- data/templates/music-beginner/extras/sonic_pi_boot.rb +297 -0
- data/templates/music-beginner/manifest.yml +25 -0
- data/templates/music-beginner/objects/conductor.md +222 -0
- data/templates/music-beginner/objects/engine_setup.md +54 -0
- data/templates/music-beginner/objects/voice_bass.md +43 -0
- data/templates/music-beginner/objects/voice_keys.md +48 -0
- data/templates/music-beginner/objects/voice_pad.md +46 -0
- data/templates/music-beginner/objects/voice_percussion.md +45 -0
- data/templates/music-beginner/objects/voice_texture.md +37 -0
- data/templates/music-beginner/primitives/play_pattern.rb +118 -0
- data/templates/music-beginner/primitives/query_music_engine.rb +94 -0
- data/templates/music-beginner/primitives/set_instrument.rb +47 -0
- data/templates/music-beginner/primitives/set_tempo.rb +43 -0
- data/templates/music-beginner/primitives/set_voice_level.rb +46 -0
- data/templates/music-beginner/primitives/stop_voice.rb +37 -0
- data/templates/music-beginner/primitives/support/osc_bridge.rb +25 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/LICENSE +19 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
- data/templates/music-beginner/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
- data/templates/music-pro/extras/AUDITION.md +100 -0
- data/templates/music-pro/extras/sonic_pi_boot.rb +546 -0
- data/templates/music-pro/manifest.yml +19 -0
- data/templates/music-pro/objects/conductor.md +126 -0
- data/templates/music-pro/objects/sonic_pi.md +35 -0
- data/templates/music-pro/objects/voice_harmony.md +48 -0
- data/templates/music-pro/objects/voice_rhythm.md +43 -0
- data/templates/music-pro/objects/voice_texture.md +42 -0
- data/templates/music-pro/primitives/define_patch.rb +46 -0
- data/templates/music-pro/primitives/query_music_engine.rb +69 -0
- data/templates/music-pro/primitives/set_live_pattern.rb +70 -0
- data/templates/music-pro/primitives/set_tempo.rb +31 -0
- data/templates/music-pro/primitives/stop_voice.rb +31 -0
- data/templates/music-pro/primitives/support/music_protocol.rb +136 -0
- data/templates/music-pro/primitives/support/osc_bridge.rb +33 -0
- data/templates/music-pro/primitives/trigger_event.rb +44 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/LICENSE +19 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/address_pattern.rb +58 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/broadcast_client.rb +24 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/bundle.rb +46 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/client.rb +13 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/core_ext/time.rb +6 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/em_server.rb +63 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/message.rb +46 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/network_packet.rb +42 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_argument.rb +26 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_packet.rb +140 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/osc_types.rb +53 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby/server.rb +91 -0
- data/templates/music-pro/vendor/osc-ruby-1.1.5/lib/osc-ruby.rb +18 -0
- data/templates/writer/objects/coordinator.md +14 -3
- data/templates/writer/objects/editor.md +1 -1
- data/templates/writer/objects/researcher.md +2 -2
- data/tools/thread-explorer.html +28 -1
- metadata +175 -65
- data/CLAUDE.md +0 -176
- data/Gemfile +0 -10
- data/Gemfile.lock +0 -233
- data/IMPLEMENTATION_PLAN.md +0 -1073
- data/Rakefile +0 -27
- data/design-doc-v2.md +0 -1232
- data/frontend/.gitignore +0 -3
- data/frontend/index.html +0 -17
- data/frontend/package-lock.json +0 -4540
- data/frontend/package.json +0 -36
- data/frontend/postcss.config.js +0 -6
- data/frontend/src/App.tsx +0 -103
- data/frontend/src/canvas/CanvasView.tsx +0 -113
- data/frontend/src/canvas/ForceLayout.ts +0 -115
- data/frontend/src/canvas/SceneManager.ts +0 -587
- data/frontend/src/canvas/canvasStore.ts +0 -47
- data/frontend/src/canvas/constants.ts +0 -95
- data/frontend/src/canvas/controls/CameraControls.ts +0 -98
- data/frontend/src/canvas/edges/MessageArc.ts +0 -149
- data/frontend/src/canvas/inspector/InspectorPanel.tsx +0 -31
- data/frontend/src/canvas/inspector/POInspector.tsx +0 -262
- data/frontend/src/canvas/inspector/ToolCallInspector.tsx +0 -67
- data/frontend/src/canvas/nodes/PONode.ts +0 -249
- data/frontend/src/canvas/nodes/ToolCallNode.ts +0 -116
- data/frontend/src/canvas/types.ts +0 -24
- data/frontend/src/components/ContextMenu.tsx +0 -68
- data/frontend/src/components/Inspector.tsx +0 -232
- data/frontend/src/components/MarkdownMessage.tsx +0 -155
- data/frontend/src/components/MethodList.tsx +0 -90
- data/frontend/src/components/ModelSelector.tsx +0 -115
- data/frontend/src/components/NotificationPanel.tsx +0 -130
- data/frontend/src/components/ObjectList.tsx +0 -78
- data/frontend/src/components/PaneSlot.tsx +0 -30
- data/frontend/src/components/SourcePane.tsx +0 -202
- data/frontend/src/components/SystemBar.tsx +0 -74
- data/frontend/src/components/Transcript.tsx +0 -76
- data/frontend/src/components/UsagePanel.tsx +0 -105
- data/frontend/src/components/Workspace.tsx +0 -260
- data/frontend/src/components/index.ts +0 -12
- data/frontend/src/hooks/useResize.ts +0 -55
- data/frontend/src/hooks/useWebSocket.ts +0 -517
- data/frontend/src/index.css +0 -103
- data/frontend/src/main.tsx +0 -10
- data/frontend/src/store/index.ts +0 -279
- data/frontend/src/types/index.ts +0 -165
- data/frontend/tailwind.config.js +0 -44
- data/frontend/tsconfig.json +0 -30
- data/frontend/vite.config.ts +0 -29
- data/lib/prompt_objects/server/public/assets/index-D1myxE0l.js +0 -4345
- data/lib/prompt_objects/server/public/assets/index-DdCcwC-Z.css +0 -1
- data/lib/prompt_objects/universal/add_primitive.rb +0 -118
- data/lib/prompt_objects/universal/ask_human.rb +0 -109
- data/lib/prompt_objects/universal/create_primitive.rb +0 -174
- data/lib/prompt_objects/universal/list_primitives.rb +0 -145
- data/lib/prompt_objects/universal/think.rb +0 -41
- data/prompt_objects.gemspec +0 -50
- data/templates/basic/.gitignore +0 -2
- data/templates/developer/.gitignore +0 -5
- data/templates/empty/.gitignore +0 -5
- data/templates/empty/objects/.gitkeep +0 -0
- data/templates/minimal/.gitignore +0 -5
- 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
|
-
|
|
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
|
-
-
|
|
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
|
|
32
|
-
- Use create_capability to build a
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
|
32
|
-
- Use create_capability to build a
|
|
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
|
-
-
|
|
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
|