prompt_objects 0.1.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 (117) hide show
  1. checksums.yaml +7 -0
  2. data/CLAUDE.md +108 -0
  3. data/Gemfile +10 -0
  4. data/Gemfile.lock +231 -0
  5. data/IMPLEMENTATION_PLAN.md +1073 -0
  6. data/LICENSE +21 -0
  7. data/README.md +73 -0
  8. data/Rakefile +27 -0
  9. data/design-doc-v2.md +1232 -0
  10. data/exe/prompt_objects +572 -0
  11. data/exe/prompt_objects_mcp +34 -0
  12. data/frontend/.gitignore +3 -0
  13. data/frontend/index.html +13 -0
  14. data/frontend/package-lock.json +4417 -0
  15. data/frontend/package.json +32 -0
  16. data/frontend/postcss.config.js +6 -0
  17. data/frontend/src/App.tsx +95 -0
  18. data/frontend/src/components/CapabilitiesPanel.tsx +44 -0
  19. data/frontend/src/components/ChatPanel.tsx +251 -0
  20. data/frontend/src/components/Dashboard.tsx +83 -0
  21. data/frontend/src/components/Header.tsx +141 -0
  22. data/frontend/src/components/MarkdownMessage.tsx +153 -0
  23. data/frontend/src/components/MessageBus.tsx +55 -0
  24. data/frontend/src/components/ModelSelector.tsx +112 -0
  25. data/frontend/src/components/NotificationPanel.tsx +134 -0
  26. data/frontend/src/components/POCard.tsx +56 -0
  27. data/frontend/src/components/PODetail.tsx +117 -0
  28. data/frontend/src/components/PromptPanel.tsx +51 -0
  29. data/frontend/src/components/SessionsPanel.tsx +174 -0
  30. data/frontend/src/components/ThreadsSidebar.tsx +119 -0
  31. data/frontend/src/components/index.ts +11 -0
  32. data/frontend/src/hooks/useWebSocket.ts +363 -0
  33. data/frontend/src/index.css +37 -0
  34. data/frontend/src/main.tsx +10 -0
  35. data/frontend/src/store/index.ts +246 -0
  36. data/frontend/src/types/index.ts +146 -0
  37. data/frontend/tailwind.config.js +25 -0
  38. data/frontend/tsconfig.json +30 -0
  39. data/frontend/vite.config.ts +29 -0
  40. data/lib/prompt_objects/capability.rb +46 -0
  41. data/lib/prompt_objects/cli.rb +431 -0
  42. data/lib/prompt_objects/connectors/base.rb +73 -0
  43. data/lib/prompt_objects/connectors/mcp.rb +524 -0
  44. data/lib/prompt_objects/environment/exporter.rb +83 -0
  45. data/lib/prompt_objects/environment/git.rb +118 -0
  46. data/lib/prompt_objects/environment/importer.rb +159 -0
  47. data/lib/prompt_objects/environment/manager.rb +401 -0
  48. data/lib/prompt_objects/environment/manifest.rb +218 -0
  49. data/lib/prompt_objects/environment.rb +283 -0
  50. data/lib/prompt_objects/human_queue.rb +144 -0
  51. data/lib/prompt_objects/llm/anthropic_adapter.rb +137 -0
  52. data/lib/prompt_objects/llm/factory.rb +84 -0
  53. data/lib/prompt_objects/llm/gemini_adapter.rb +209 -0
  54. data/lib/prompt_objects/llm/openai_adapter.rb +104 -0
  55. data/lib/prompt_objects/llm/response.rb +61 -0
  56. data/lib/prompt_objects/loader.rb +32 -0
  57. data/lib/prompt_objects/mcp/server.rb +167 -0
  58. data/lib/prompt_objects/mcp/tools/get_conversation.rb +60 -0
  59. data/lib/prompt_objects/mcp/tools/get_pending_requests.rb +54 -0
  60. data/lib/prompt_objects/mcp/tools/inspect_po.rb +73 -0
  61. data/lib/prompt_objects/mcp/tools/list_prompt_objects.rb +37 -0
  62. data/lib/prompt_objects/mcp/tools/respond_to_request.rb +68 -0
  63. data/lib/prompt_objects/mcp/tools/send_message.rb +71 -0
  64. data/lib/prompt_objects/message_bus.rb +97 -0
  65. data/lib/prompt_objects/primitive.rb +13 -0
  66. data/lib/prompt_objects/primitives/http_get.rb +72 -0
  67. data/lib/prompt_objects/primitives/list_files.rb +95 -0
  68. data/lib/prompt_objects/primitives/read_file.rb +81 -0
  69. data/lib/prompt_objects/primitives/write_file.rb +73 -0
  70. data/lib/prompt_objects/prompt_object.rb +415 -0
  71. data/lib/prompt_objects/registry.rb +88 -0
  72. data/lib/prompt_objects/server/api/routes.rb +297 -0
  73. data/lib/prompt_objects/server/app.rb +174 -0
  74. data/lib/prompt_objects/server/file_watcher.rb +113 -0
  75. data/lib/prompt_objects/server/public/assets/index-2acS2FYZ.js +77 -0
  76. data/lib/prompt_objects/server/public/assets/index-DXU5uRXQ.css +1 -0
  77. data/lib/prompt_objects/server/public/index.html +14 -0
  78. data/lib/prompt_objects/server/websocket_handler.rb +619 -0
  79. data/lib/prompt_objects/server.rb +166 -0
  80. data/lib/prompt_objects/session/store.rb +826 -0
  81. data/lib/prompt_objects/universal/add_capability.rb +74 -0
  82. data/lib/prompt_objects/universal/add_primitive.rb +113 -0
  83. data/lib/prompt_objects/universal/ask_human.rb +109 -0
  84. data/lib/prompt_objects/universal/create_capability.rb +219 -0
  85. data/lib/prompt_objects/universal/create_primitive.rb +170 -0
  86. data/lib/prompt_objects/universal/list_capabilities.rb +55 -0
  87. data/lib/prompt_objects/universal/list_primitives.rb +145 -0
  88. data/lib/prompt_objects/universal/modify_primitive.rb +180 -0
  89. data/lib/prompt_objects/universal/request_primitive.rb +287 -0
  90. data/lib/prompt_objects/universal/think.rb +41 -0
  91. data/lib/prompt_objects/universal/verify_primitive.rb +173 -0
  92. data/lib/prompt_objects.rb +62 -0
  93. data/objects/coordinator.md +48 -0
  94. data/objects/greeter.md +30 -0
  95. data/objects/reader.md +33 -0
  96. data/prompt_objects.gemspec +50 -0
  97. data/templates/basic/.gitignore +2 -0
  98. data/templates/basic/manifest.yml +7 -0
  99. data/templates/basic/objects/basic.md +32 -0
  100. data/templates/developer/.gitignore +5 -0
  101. data/templates/developer/manifest.yml +17 -0
  102. data/templates/developer/objects/code_reviewer.md +33 -0
  103. data/templates/developer/objects/coordinator.md +39 -0
  104. data/templates/developer/objects/debugger.md +35 -0
  105. data/templates/empty/.gitignore +5 -0
  106. data/templates/empty/manifest.yml +14 -0
  107. data/templates/empty/objects/.gitkeep +0 -0
  108. data/templates/empty/objects/assistant.md +41 -0
  109. data/templates/minimal/.gitignore +5 -0
  110. data/templates/minimal/manifest.yml +7 -0
  111. data/templates/minimal/objects/assistant.md +41 -0
  112. data/templates/writer/.gitignore +5 -0
  113. data/templates/writer/manifest.yml +17 -0
  114. data/templates/writer/objects/coordinator.md +33 -0
  115. data/templates/writer/objects/editor.md +33 -0
  116. data/templates/writer/objects/researcher.md +34 -0
  117. metadata +343 -0
@@ -0,0 +1,1073 @@
1
+ # PromptObjects Implementation Plan
2
+
3
+ Detailed phased implementation plan for the PromptObjects MVP.
4
+
5
+ ---
6
+
7
+ ## Phase 1: Core Loop
8
+
9
+ **Goal**: Get a single Prompt-Object (greeter) responding to human input via LLM.
10
+
11
+ ### 1.1 Project Setup
12
+
13
+ - [ ] Initialize Ruby project structure
14
+ ```
15
+ prompt_objects/
16
+ ├── Gemfile
17
+ ├── lib/
18
+ │ ├── prompt_objects.rb
19
+ │ └── prompt_objects/
20
+ └── exe/
21
+ └── prompt_objects
22
+ ```
23
+ - [ ] Gemfile dependencies:
24
+ - `ruby-openai` (or `anthropic` SDK)
25
+ - `front_matter_parser` (YAML frontmatter parsing)
26
+ - Standard library: `optparse`, `readline`
27
+
28
+ ### 1.2 Capability Base Class
29
+
30
+ **File**: `lib/prompt_objects/capability.rb`
31
+
32
+ ```ruby
33
+ module PromptObjects
34
+ class Capability
35
+ attr_reader :name, :description
36
+
37
+ def receive(message, context:)
38
+ raise NotImplementedError
39
+ end
40
+
41
+ def descriptor
42
+ {
43
+ name: name,
44
+ description: description,
45
+ parameters: parameters
46
+ }
47
+ end
48
+
49
+ def parameters
50
+ { type: "object", properties: {}, required: [] }
51
+ end
52
+ end
53
+ end
54
+ ```
55
+
56
+ ### 1.3 Loader
57
+
58
+ **File**: `lib/prompt_objects/loader.rb`
59
+
60
+ - [ ] Parse markdown file with YAML frontmatter
61
+ - [ ] Extract config (name, description, capabilities list)
62
+ - [ ] Extract body (everything after frontmatter)
63
+ - [ ] Return structured data for PromptObject initialization
64
+
65
+ ```ruby
66
+ module PromptObjects
67
+ class Loader
68
+ def self.load(path)
69
+ content = File.read(path)
70
+ parsed = FrontMatterParser::Parser.new(:md).call(content)
71
+
72
+ {
73
+ config: parsed.front_matter,
74
+ body: parsed.content,
75
+ path: path
76
+ }
77
+ end
78
+ end
79
+ end
80
+ ```
81
+
82
+ ### 1.4 LLM Adapter (OpenAI)
83
+
84
+ **File**: `lib/prompt_objects/llm/openai_adapter.rb`
85
+
86
+ - [ ] Initialize with API key (from ENV)
87
+ - [ ] `chat(system:, messages:, tools:)` method
88
+ - [ ] Handle tool_calls in response
89
+ - [ ] Return structured response object with `content` and `tool_calls`
90
+
91
+ ```ruby
92
+ module PromptObjects
93
+ module LLM
94
+ class OpenAIAdapter
95
+ def initialize(api_key: ENV["OPENAI_API_KEY"], model: "gpt-4")
96
+ @client = OpenAI::Client.new(access_token: api_key)
97
+ @model = model
98
+ end
99
+
100
+ def chat(system:, messages:, tools: [])
101
+ response = @client.chat(
102
+ parameters: {
103
+ model: @model,
104
+ messages: format_messages(system, messages),
105
+ tools: format_tools(tools)
106
+ }
107
+ )
108
+
109
+ Response.new(response)
110
+ end
111
+ end
112
+
113
+ class Response
114
+ attr_reader :content, :tool_calls
115
+ # Parse OpenAI response format
116
+ end
117
+ end
118
+ end
119
+ ```
120
+
121
+ ### 1.5 PromptObject Class
122
+
123
+ **File**: `lib/prompt_objects/prompt_object.rb`
124
+
125
+ - [ ] Initialize with config, body, environment reference, LLM adapter
126
+ - [ ] Implement `receive(message, context:)`:
127
+ 1. Add message to history
128
+ 2. Call LLM with system prompt (body) + history
129
+ 3. If tool_calls: execute and loop
130
+ 4. If no tool_calls: return content
131
+ - [ ] Track conversation history
132
+
133
+ ### 1.6 Simple REPL
134
+
135
+ **File**: `exe/prompt_objects`
136
+
137
+ - [ ] Parse command line args (prompt object name)
138
+ - [ ] Load the specified .md file from `objects/` directory
139
+ - [ ] Create PromptObject instance
140
+ - [ ] Loop: read input → call receive → print response
141
+
142
+ ```ruby
143
+ #!/usr/bin/env ruby
144
+ require "prompt_objects"
145
+
146
+ name = ARGV[0] || "greeter"
147
+ path = File.join("objects", "#{name}.md")
148
+
149
+ env = PromptObjects::Environment.new
150
+ po = env.load_prompt_object(path)
151
+
152
+ loop do
153
+ print "You: "
154
+ input = gets&.chomp
155
+ break if input.nil? || input == "exit"
156
+
157
+ response = po.receive(input, context: env.context)
158
+ puts "\n#{po.name}: #{response}\n\n"
159
+ end
160
+ ```
161
+
162
+ ### 1.7 Test Prompt Object
163
+
164
+ **File**: `objects/greeter.md`
165
+
166
+ Create the greeter from the design doc to test with.
167
+
168
+ ### Phase 1 Deliverable
169
+
170
+ - Run `./exe/prompt_objects greeter`
171
+ - Type "hello"
172
+ - Get a response from the greeter personality
173
+ - **Demo 1 achievable**
174
+
175
+ ---
176
+
177
+ ## Conceptual Model: The PromptObject Hierarchy
178
+
179
+ PromptObjects follow an object-oriented capability model inspired by Smalltalk/Ruby:
180
+
181
+ ### Universal Capabilities = "BasicObject/Kernel"
182
+
183
+ The absolute minimum every PO inherits to function in the environment:
184
+
185
+ | Capability | Purpose |
186
+ |------------|---------|
187
+ | `ask_human` | Communicate with the human operator |
188
+ | `think` | Internal reasoning (chain-of-thought) |
189
+ | `create_capability` | Self-modification - create new POs or primitives |
190
+ | `add_capability` | Extend self with new capabilities at runtime |
191
+ | `list_capabilities` | Introspection - discover available tools |
192
+
193
+ These are **always available** - POs don't need to declare them.
194
+
195
+ ### Stdlib Primitives = "Standard Library"
196
+
197
+ Opt-in deterministic Ruby capabilities, organized by category:
198
+
199
+ | Category | Primitives |
200
+ |----------|------------|
201
+ | **File System** | `read_file`, `write_file`, `list_files`, `delete_file`, `move_file`, `file_exists` |
202
+ | **Network** | `http_get`, `http_post`, `http_request`, `download_file` |
203
+ | **Process** | `run_command`, `spawn_process` |
204
+ | **Data** | `json_parse`, `json_stringify`, `yaml_parse`, `yaml_stringify`, `csv_parse` |
205
+ | **Environment** | `get_env`, `current_time`, `sleep` |
206
+ | **Text** | `regex_match`, `regex_replace`, `string_split` |
207
+ | **Crypto** | `hash_string`, `random_uuid`, `random_string` |
208
+
209
+ POs declare which stdlib primitives they need in their frontmatter.
210
+
211
+ ### Custom Primitives = "User-Defined Functions"
212
+
213
+ Runtime-generated primitives created via `create_capability`. These are project-specific tools.
214
+
215
+ ### Delegate POs = "Collaborators"
216
+
217
+ Other prompt objects this PO can call for help. Listed in `capabilities` frontmatter.
218
+
219
+ ---
220
+
221
+ ## Phase 2: Primitives & Binding
222
+
223
+ **Goal**: Add primitive capabilities that PromptObjects can call. Reader uses `read_file` and `list_files`.
224
+
225
+ ### 2.1 Primitive Base Class
226
+
227
+ **File**: `lib/prompt_objects/primitive.rb`
228
+
229
+ ```ruby
230
+ module PromptObjects
231
+ class Primitive < Capability
232
+ # Primitives are Capabilities with deterministic Ruby implementations
233
+ # They define parameters more strictly than POs
234
+ end
235
+ end
236
+ ```
237
+
238
+ ### 2.2 Built-in Primitives
239
+
240
+ **Directory**: `lib/prompt_objects/primitives/`
241
+
242
+ #### read_file.rb
243
+ - [ ] Parameters: `{ path: string }`
244
+ - [ ] Validate path (prevent directory traversal)
245
+ - [ ] Read and return file contents
246
+ - [ ] Handle errors gracefully
247
+
248
+ #### list_files.rb
249
+ - [ ] Parameters: `{ path: string }` (defaults to ".")
250
+ - [ ] Return array of filenames/directories
251
+ - [ ] Optional: include file types/sizes
252
+
253
+ #### write_file.rb
254
+ - [ ] Parameters: `{ path: string, content: string }`
255
+ - [ ] Validate path
256
+ - [ ] Write content to file
257
+ - [ ] Return confirmation
258
+
259
+ ### 2.3 Registry
260
+
261
+ **File**: `lib/prompt_objects/registry.rb`
262
+
263
+ - [ ] Store capabilities by name
264
+ - [ ] `register(capability)` - add to registry
265
+ - [ ] `get(name)` - retrieve by name
266
+ - [ ] `all` - list all capabilities
267
+ - [ ] `prompt_objects` - filter to just POs
268
+ - [ ] `primitives` - filter to just primitives
269
+ - [ ] Generate tool descriptors for LLM
270
+
271
+ ```ruby
272
+ module PromptObjects
273
+ class Registry
274
+ def initialize
275
+ @capabilities = {}
276
+ end
277
+
278
+ def register(capability)
279
+ @capabilities[capability.name] = capability
280
+ end
281
+
282
+ def get(name)
283
+ @capabilities[name] or raise "Unknown capability: #{name}"
284
+ end
285
+
286
+ def descriptors_for(names)
287
+ names.map { |n| get(n).descriptor }
288
+ end
289
+ end
290
+ end
291
+ ```
292
+
293
+ ### 2.4 Environment Updates
294
+
295
+ **File**: `lib/prompt_objects/environment.rb`
296
+
297
+ - [ ] Hold registry instance
298
+ - [ ] Auto-register built-in primitives on init
299
+ - [ ] Load prompt objects from `objects/` directory
300
+ - [ ] Provide context object for capability execution
301
+
302
+ ```ruby
303
+ module PromptObjects
304
+ class Environment
305
+ attr_reader :registry, :llm
306
+
307
+ def initialize
308
+ @registry = Registry.new
309
+ @llm = LLM::OpenAIAdapter.new
310
+ register_primitives
311
+ end
312
+
313
+ def register_primitives
314
+ registry.register(Primitives::ReadFile.new)
315
+ registry.register(Primitives::ListFiles.new)
316
+ registry.register(Primitives::WriteFile.new)
317
+ end
318
+
319
+ def load_prompt_object(path)
320
+ data = Loader.load(path)
321
+ po = PromptObject.new(
322
+ config: data[:config],
323
+ body: data[:body],
324
+ env: self,
325
+ llm: @llm
326
+ )
327
+ registry.register(po)
328
+ po
329
+ end
330
+ end
331
+ end
332
+ ```
333
+
334
+ ### 2.5 PromptObject Updates
335
+
336
+ - [ ] Look up declared capabilities from registry
337
+ - [ ] Pass capability descriptors to LLM as tools
338
+ - [ ] Execute capability calls through registry
339
+ - [ ] Handle capability results in conversation loop
340
+
341
+ ### 2.6 Test Prompt Object
342
+
343
+ **File**: `objects/reader.md`
344
+
345
+ Create the reader from the design doc with `read_file` and `list_files` capabilities.
346
+
347
+ ### Phase 2 Deliverable
348
+
349
+ - Run `./exe/prompt_objects reader`
350
+ - Ask "what's in here?"
351
+ - Reader calls `list_files`, interprets results, responds
352
+ - Ask "tell me about the README"
353
+ - Reader calls `read_file`, summarizes content
354
+ - **Demo 2 achievable** - semantic binding visible in output
355
+
356
+ ---
357
+
358
+ ## Phase 3: Multi-Capability
359
+
360
+ **Goal**: Prompt-Objects can call other Prompt-Objects. Coordinator delegates to reader.
361
+
362
+ ### 3.1 Message Bus
363
+
364
+ **File**: `lib/prompt_objects/message_bus.rb`
365
+
366
+ - [ ] Log all messages: `{ timestamp, from, to, message }`
367
+ - [ ] Subscribe mechanism for UI updates
368
+ - [ ] `recent(n)` to get last n messages
369
+ - [ ] Truncate long messages for display
370
+
371
+ ```ruby
372
+ module PromptObjects
373
+ class MessageBus
374
+ attr_reader :log
375
+
376
+ def initialize
377
+ @log = []
378
+ @subscribers = []
379
+ end
380
+
381
+ def publish(from:, to:, message:)
382
+ entry = {
383
+ timestamp: Time.now,
384
+ from: from,
385
+ to: to,
386
+ message: message
387
+ }
388
+ @log << entry
389
+ @subscribers.each { |s| s.call(entry) }
390
+ entry
391
+ end
392
+
393
+ def subscribe(&block)
394
+ @subscribers << block
395
+ end
396
+
397
+ def recent(n = 20)
398
+ @log.last(n)
399
+ end
400
+ end
401
+ end
402
+ ```
403
+
404
+ ### 3.2 Context Object
405
+
406
+ **File**: `lib/prompt_objects/context.rb`
407
+
408
+ - [ ] Hold reference to environment
409
+ - [ ] Hold reference to message bus
410
+ - [ ] Track current execution chain (for loop detection)
411
+ - [ ] Provide callbacks for streaming/deltas
412
+
413
+ ```ruby
414
+ module PromptObjects
415
+ class Context
416
+ attr_reader :env, :bus
417
+ attr_accessor :on_delta
418
+
419
+ def initialize(env:, bus:)
420
+ @env = env
421
+ @bus = bus
422
+ @call_stack = []
423
+ end
424
+
425
+ def push(capability_name)
426
+ if @call_stack.include?(capability_name)
427
+ raise "Capability loop detected: #{@call_stack.join(' → ')} → #{capability_name}"
428
+ end
429
+ @call_stack.push(capability_name)
430
+ end
431
+
432
+ def pop
433
+ @call_stack.pop
434
+ end
435
+ end
436
+ end
437
+ ```
438
+
439
+ ### 3.3 PO → PO Communication
440
+
441
+ Update PromptObject to:
442
+ - [ ] When calling a capability, check if it's a PO or primitive
443
+ - [ ] Log message to bus before calling
444
+ - [ ] Log response to bus after receiving
445
+ - [ ] POs receive natural language messages from other POs
446
+
447
+ ### 3.4 REPL Updates
448
+
449
+ - [ ] Print message bus entries as they happen
450
+ - [ ] Show the cascade of messages
451
+ - [ ] Format: `HH:MM:SS from → to: "message..."`
452
+
453
+ ### 3.5 Test Prompt Object
454
+
455
+ **File**: `objects/coordinator.md`
456
+
457
+ Create coordinator with capabilities: `greeter`, `reader`, `list_files`
458
+
459
+ ### Phase 3 Deliverable
460
+
461
+ - Run `./exe/prompt_objects coordinator`
462
+ - Ask "help me understand this codebase"
463
+ - See coordinator delegate to reader
464
+ - See reader use primitives
465
+ - See message cascade in output
466
+ - **Demo 3 achievable**
467
+
468
+ ---
469
+
470
+ ## Phase 4: Self-Modification & Human Interaction Queue
471
+
472
+ **Goal**: System can create new Prompt-Objects at runtime. Human interactions are non-blocking with a notification queue.
473
+
474
+ ### 4.1 Universal Capabilities
475
+
476
+ **Directory**: `lib/prompt_objects/universal/`
477
+
478
+ These are automatically available to ALL prompt objects.
479
+
480
+ #### ask_human.rb
481
+ - [ ] Parameters: `{ question: string, options: array (optional) }`
482
+ - [ ] **Non-blocking**: Suspends PO execution, queues notification
483
+ - [ ] Returns when human eventually responds
484
+ - [ ] Use **Huh** gem for rendering the prompt when human engages
485
+
486
+ ```ruby
487
+ module PromptObjects
488
+ module Universal
489
+ class AskHuman < Capability
490
+ def name = "ask_human"
491
+ def description = "Pause and ask the human a question"
492
+
493
+ def parameters
494
+ {
495
+ type: "object",
496
+ properties: {
497
+ question: { type: "string", description: "Question to ask" },
498
+ options: {
499
+ type: "array",
500
+ items: { type: "string" },
501
+ description: "Optional choices to present"
502
+ }
503
+ },
504
+ required: ["question"]
505
+ }
506
+ end
507
+
508
+ def receive(message, context:)
509
+ question = message[:question] || message["question"]
510
+ options = message[:options] || message["options"]
511
+
512
+ # Create a pending request and suspend until human responds
513
+ request = HumanRequest.new(
514
+ capability: context.current_capability,
515
+ type: :ask_human,
516
+ question: question,
517
+ options: options
518
+ )
519
+
520
+ # Queue the request - this suspends the PO's fiber
521
+ context.env.human_queue.enqueue(request)
522
+
523
+ # When we resume, the response is in the request
524
+ request.response
525
+ end
526
+ end
527
+ end
528
+ end
529
+ ```
530
+
531
+ #### think.rb
532
+ - [ ] Parameters: `{ thought: string }`
533
+ - [ ] Log to bus but don't show to human (or show dimmed)
534
+ - [ ] Return acknowledgment
535
+ - [ ] Useful for chain-of-thought reasoning
536
+
537
+ #### create_capability.rb
538
+ - [ ] Parameters: `{ type, name, description, capabilities, body }`
539
+ - [ ] For type "prompt_object":
540
+ - Build frontmatter YAML
541
+ - Combine with body markdown
542
+ - Write to `objects/` directory
543
+ - Load into registry
544
+ - [ ] For type "primitive": return error (not supported in MVP)
545
+
546
+ ### 4.2 Human Interaction Queue
547
+
548
+ **File**: `lib/prompt_objects/human_queue.rb`
549
+
550
+ Non-blocking system for POs to request human attention. Multiple POs can be waiting simultaneously.
551
+
552
+ ```ruby
553
+ module PromptObjects
554
+ class HumanRequest
555
+ attr_reader :id, :capability, :type, :question, :options, :created_at
556
+ attr_accessor :response
557
+
558
+ def initialize(capability:, type:, question:, options: nil)
559
+ @id = SecureRandom.uuid
560
+ @capability = capability
561
+ @type = type
562
+ @question = question
563
+ @options = options
564
+ @created_at = Time.now
565
+ @response = nil
566
+ @fiber = Fiber.current
567
+ end
568
+
569
+ def respond!(value)
570
+ @response = value
571
+ @fiber.resume(value)
572
+ end
573
+
574
+ def pending?
575
+ @response.nil?
576
+ end
577
+ end
578
+
579
+ class HumanQueue
580
+ attr_reader :pending
581
+
582
+ def initialize
583
+ @pending = []
584
+ @subscribers = []
585
+ end
586
+
587
+ def enqueue(request)
588
+ @pending << request
589
+ notify_subscribers(:added, request)
590
+ # Suspend the calling fiber until human responds
591
+ Fiber.yield
592
+ end
593
+
594
+ def respond(request_id, value)
595
+ request = @pending.find { |r| r.id == request_id }
596
+ return unless request
597
+
598
+ @pending.delete(request)
599
+ notify_subscribers(:resolved, request)
600
+ request.respond!(value)
601
+ end
602
+
603
+ def pending_for(capability_name)
604
+ @pending.select { |r| r.capability == capability_name }
605
+ end
606
+
607
+ def subscribe(&block)
608
+ @subscribers << block
609
+ end
610
+
611
+ private
612
+
613
+ def notify_subscribers(event, request)
614
+ @subscribers.each { |s| s.call(event, request) }
615
+ end
616
+ end
617
+ end
618
+ ```
619
+
620
+ ### 4.3 PO States & Concurrent Execution
621
+
622
+ **File**: `lib/prompt_objects/executor.rb`
623
+
624
+ POs run in Fibers for cooperative concurrency. States:
625
+ - `idle` - Not doing anything
626
+ - `working` - Processing (LLM call in progress)
627
+ - `waiting_for_human` - Suspended, has pending HumanRequest
628
+ - `active` - Currently being interacted with by human
629
+
630
+ ```ruby
631
+ module PromptObjects
632
+ class Executor
633
+ def initialize(env:)
634
+ @env = env
635
+ @fibers = {} # capability_name => Fiber
636
+ end
637
+
638
+ def run(capability, message, context:)
639
+ fiber = Fiber.new do
640
+ capability.receive(message, context: context)
641
+ end
642
+
643
+ @fibers[capability.name] = fiber
644
+ capability.state = :working
645
+
646
+ result = fiber.resume
647
+
648
+ if fiber.alive?
649
+ # Fiber yielded (waiting for human)
650
+ capability.state = :waiting_for_human
651
+ nil # Result pending
652
+ else
653
+ # Fiber completed
654
+ capability.state = :idle
655
+ @fibers.delete(capability.name)
656
+ result
657
+ end
658
+ end
659
+
660
+ def resume(capability_name)
661
+ fiber = @fibers[capability_name]
662
+ return unless fiber&.alive?
663
+
664
+ capability = @env.registry.get(capability_name)
665
+ capability.state = :working
666
+
667
+ result = fiber.resume
668
+
669
+ unless fiber.alive?
670
+ capability.state = :idle
671
+ @fibers.delete(capability_name)
672
+ end
673
+
674
+ result
675
+ end
676
+ end
677
+ end
678
+ ```
679
+
680
+ ### 4.4 Extensible Request Types
681
+
682
+ The `HumanRequest` system is designed to support future interaction types beyond `ask_human`:
683
+
684
+ ```ruby
685
+ module PromptObjects
686
+ class HumanRequest
687
+ TYPES = {
688
+ ask_human: {
689
+ icon: "❓",
690
+ renderer: :render_question
691
+ },
692
+ confirm_action: {
693
+ icon: "⚠️",
694
+ renderer: :render_confirmation
695
+ },
696
+ review_output: {
697
+ icon: "👁",
698
+ renderer: :render_review
699
+ },
700
+ provide_input: {
701
+ icon: "✏️",
702
+ renderer: :render_input_form
703
+ }
704
+ }
705
+ end
706
+ end
707
+ ```
708
+
709
+ Future universal capabilities can use the same queue:
710
+ - `confirm_action` - "About to delete 5 files. Proceed?"
711
+ - `review_output` - "Here's the code I generated. Approve?"
712
+ - `provide_input` - "I need the API key for service X"
713
+
714
+ ### 4.5 Environment Updates
715
+
716
+ - [ ] Auto-register universal capabilities
717
+ - [ ] Add `human_queue` instance
718
+ - [ ] Add `executor` for fiber management
719
+ - [ ] Method to add universal caps to any PO's available tools
720
+ - [ ] `prompt_objects_dir` configuration
721
+
722
+ ### 4.6 PromptObject Updates
723
+
724
+ - [ ] Merge universal capabilities with declared capabilities
725
+ - [ ] Universal caps don't need to be in frontmatter
726
+ - [ ] Add `state` attribute for tracking execution state
727
+
728
+ ### 4.7 Update Coordinator
729
+
730
+ Add `create_capability` to coordinator's capabilities list.
731
+
732
+ ### 4.8 Known Issues to Address in Phase 5
733
+
734
+ During Phase 4 testing, several issues were identified with capability creation:
735
+ - [ ] LLM sometimes creates POs when primitives would be more appropriate
736
+ - [ ] Dynamically created capabilities may not be immediately available to existing POs
737
+ - [ ] Need better visibility into what capabilities each PO has access to
738
+ - [ ] Modifying existing PO capabilities at runtime needs cleaner UX
739
+
740
+ These will be addressed in Phase 5 with:
741
+ - **PO Inspector** showing exactly what tools each PO has
742
+ - Better prompting/guidance for the LLM on when to create POs vs primitives
743
+ - Visual feedback when capabilities are added/modified
744
+
745
+ ### Phase 4 Deliverable
746
+
747
+ - Run `./exe/prompt_objects coordinator`
748
+ - Ask for help with something requiring a new specialist
749
+ - Coordinator uses `ask_human` to get permission
750
+ - **Request appears as notification badge on coordinator** (not blocking UI)
751
+ - Human can navigate to notification, respond via Huh prompt
752
+ - Coordinator resumes and creates new PO via `create_capability`
753
+ - New PO appears and can be interacted with
754
+ - **Demo 4 achievable** with non-blocking human interaction
755
+
756
+ ---
757
+
758
+ ## Phase 5: Polish & UI
759
+
760
+ **Goal**: Beautiful Charm-based terminal UI with capability bar, message log, conversation.
761
+
762
+ ### 5.1 Charm Ruby Gems (Marco Roth's Ports)
763
+
764
+ Use Marco Roth's Ruby ports of the Charm libraries:
765
+ - **bubbletea** - Elm-inspired TUI framework (Model-View-Update)
766
+ - **lipgloss** - CSS-like terminal styling
767
+ - **glamour** - Markdown rendering in terminal
768
+ - **bubbles** - Pre-built components (spinners, text inputs, progress bars)
769
+ - **huh** - Interactive forms/prompts (perfect for `ask_human`!)
770
+
771
+ GitHub: https://github.com/marcoroth/bubbletea-ruby (and related repos)
772
+ Reference: https://marcoroth.dev/posts/glamorous-christmas
773
+
774
+ ### 5.2 UI Components (Bubble Tea approach)
775
+
776
+ **Directory**: `lib/prompt_objects/ui/`
777
+
778
+ #### app.rb
779
+ - [ ] Main application loop
780
+ - [ ] Handle keyboard input
781
+ - [ ] Manage screen layout
782
+ - [ ] Coordinate component updates
783
+
784
+ #### capability_bar.rb
785
+ - [ ] Display all registered capabilities
786
+ - [ ] Show state: ○ idle, ◐ working, ● active, ⚠ waiting_for_human
787
+ - [ ] Distinguish POs from primitives (▪)
788
+ - [ ] **Notification badges**: Show count of pending requests per PO
789
+ - [ ] Keyboard navigation: Tab/arrow keys to select PO
790
+ - [ ] Click or Enter to focus a PO (especially ones with pending requests)
791
+ - [ ] Update in real-time as state changes
792
+
793
+ ```
794
+ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
795
+ │ greeter │ │ reader │ │ coord │ │ debugger │
796
+ │ ○ │ │ ◐ │ │ ⚠ 2 │ │ ⚠ 1 │
797
+ └──────────┘ └──────────┘ └──────────┘ └──────────┘
798
+ ^ 2 pending ^ 1 pending
799
+ ```
800
+
801
+ #### message_log.rb
802
+ - [ ] Subscribe to message bus
803
+ - [ ] Display scrolling log of messages
804
+ - [ ] Color-code by capability type
805
+ - [ ] Truncate long messages
806
+ - [ ] Timestamp formatting
807
+
808
+ #### conversation.rb
809
+ - [ ] Show current conversation with active PO
810
+ - [ ] Render markdown responses (via `tty-markdown` or similar)
811
+ - [ ] Handle streaming output
812
+
813
+ #### input.rb
814
+ - [ ] Text input field
815
+ - [ ] History (up/down arrows)
816
+ - [ ] Handle special commands
817
+
818
+ #### notification_panel.rb
819
+ - [ ] List all pending HumanRequests across all POs
820
+ - [ ] Show: PO name, request type, question preview, age
821
+ - [ ] Keyboard navigation to select a request
822
+ - [ ] Enter to engage with selected request (opens Huh prompt)
823
+ - [ ] Toggle visibility with hotkey (e.g., `n` for notifications)
824
+ - [ ] Sort by age or PO name
825
+
826
+ ```
827
+ ┌─ PENDING REQUESTS (3) ────────────────────────────────────┐
828
+ │ │
829
+ │ ▸ coordinator "Create Ruby debugging specialist?" 2m │
830
+ │ debugger "Which bug should I focus on?" 45s │
831
+ │ reader "Delete all files in spec/?" 10s │
832
+ │ │
833
+ │ [Enter] Respond [Esc] Close [↑↓] Navigate │
834
+ └───────────────────────────────────────────────────────────┘
835
+ ```
836
+
837
+ #### request_responder.rb
838
+ - [ ] Modal that appears when engaging with a HumanRequest
839
+ - [ ] Renders the full question with context
840
+ - [ ] Uses **Huh** components for input (select, text input, confirm)
841
+ - [ ] On submit: calls `human_queue.respond(request_id, value)`
842
+ - [ ] PO resumes execution automatically
843
+
844
+ #### po_inspector.rb
845
+ - [ ] Modal/panel that appears when inspecting a selected PO
846
+ - [ ] Shows the PO's full prompt (markdown body rendered via glamour)
847
+ - [ ] Shows capabilities with the "object model" framing:
848
+ - **Universal (inherited)**: The "BasicObject" - always available
849
+ - **Stdlib Primitives**: Opt-in standard library capabilities
850
+ - **Custom Primitives**: Runtime-generated primitives
851
+ - **Delegates (POs)**: Other prompt objects this one can call
852
+ - [ ] Each tool shows: name, description, parameters schema
853
+ - [ ] Keyboard shortcut to toggle (e.g., `i` for inspect)
854
+ - [ ] **Edit mode** to modify the PO's capabilities:
855
+ - Browse & add stdlib primitives
856
+ - Generate new primitive (PO describes need, we create it)
857
+ - Add/remove PO delegates
858
+ - [ ] Maybe show conversation history / message log filtered to this PO
859
+
860
+ ```
861
+ ┌─ INSPECT: coordinator ─────────────────────────────────────────────┐
862
+ │ │
863
+ │ PROMPT │
864
+ │ ──────────────────────────────────────────────────────────────── │
865
+ │ # Coordinator │
866
+ │ You orchestrate work by delegating to specialists... │
867
+ │ │
868
+ │ CAPABILITIES │
869
+ │ ──────────────────────────────────────────────────────────────── │
870
+ │ Universal (inherited): ask_human, think, create_capability, ... │
871
+ │ Stdlib: list_files, read_file, write_file, http_get │
872
+ │ Custom: (none) │
873
+ │ Delegates: greeter, reader │
874
+ │ │
875
+ │ [e] Edit Capabilities [Esc] Close [p] Prompt [h] History │
876
+ └─────────────────────────────────────────────────────────────────────┘
877
+ ```
878
+
879
+ #### capability_editor.rb
880
+ - [ ] Sub-modal for editing a PO's capabilities
881
+ - [ ] Three tabs/sections:
882
+ 1. **Add from Stdlib**: Browse categorized primitives, toggle on/off
883
+ 2. **Generate New**: Describe what you need, system creates primitive
884
+ 3. **Manage Delegates**: Add/remove other POs as delegates
885
+ - [ ] Changes update PO config in real-time
886
+ - [ ] Option to persist changes back to .md file
887
+
888
+ ```
889
+ ┌─ EDIT CAPABILITIES: coordinator ───────────────────────────────────┐
890
+ │ │
891
+ │ [Stdlib] [Generate] [Delegates] │
892
+ │ ───────────────────────────────────────────────────────────────── │
893
+ │ │
894
+ │ FILE SYSTEM NETWORK │
895
+ │ ───────────── ─────── │
896
+ │ [✓] read_file [✓] http_get │
897
+ │ [✓] write_file [ ] http_post │
898
+ │ [✓] list_files [ ] http_request │
899
+ │ [ ] delete_file │
900
+ │ [ ] move_file PROCESS │
901
+ │ ─────── │
902
+ │ DATA [ ] run_command │
903
+ │ ──── [ ] spawn_process │
904
+ │ [ ] json_parse │
905
+ │ [ ] yaml_parse ENVIRONMENT │
906
+ │ [ ] csv_parse ─────────── │
907
+ │ [ ] get_env │
908
+ │ [ ] current_time │
909
+ │ │
910
+ │ [Enter] Toggle [Tab] Next Section [Esc] Done │
911
+ └─────────────────────────────────────────────────────────────────────┘
912
+ ```
913
+
914
+ ### 5.3 Streaming Support
915
+
916
+ - [ ] Update LLM adapter to support streaming
917
+ - [ ] Pass deltas through context callbacks
918
+ - [ ] UI updates character-by-character
919
+
920
+ ### 5.4 State Management
921
+
922
+ - [ ] Track which PO is "active" (talking to human)
923
+ - [ ] Track which POs are "working" (processing)
924
+ - [ ] Broadcast state changes to UI
925
+
926
+ ### 5.5 Spinners & Polish
927
+
928
+ - [ ] Show spinner during LLM calls
929
+ - [ ] Graceful handling of slow responses
930
+ - [ ] Keyboard shortcuts (e.g., `m` to toggle message log)
931
+
932
+ ### Phase 5 Deliverable
933
+
934
+ - Full TUI experience
935
+ - See capability bar at top with **notification badges**
936
+ - See message log showing all traffic
937
+ - See conversation area with current PO
938
+ - **Notification panel** accessible via hotkey
939
+ - Navigate between POs, respond to pending requests
940
+ - Multiple POs can be waiting simultaneously without blocking
941
+ - Input at bottom
942
+ - **Demo 5 achievable** - human-in-the-loop feels natural and scalable
943
+
944
+ ---
945
+
946
+ ## Phase 6: Demo Ready
947
+
948
+ **Goal**: Everything works smoothly for an 8-minute demo.
949
+
950
+ ### 6.1 Error Handling
951
+
952
+ - [ ] Graceful LLM API errors (rate limits, timeouts)
953
+ - [ ] Invalid capability references
954
+ - [ ] File system errors in primitives
955
+ - [ ] Malformed prompt object files
956
+ - [ ] Capability loop detection
957
+
958
+ ### 6.2 Demo Script
959
+
960
+ - [ ] Write exact script for 8-minute demo
961
+ - [ ] Prepare prompt objects for each demo
962
+ - [ ] Time each section
963
+ - [ ] Identify where things could go wrong
964
+
965
+ ### 6.3 Demo Prompt Objects
966
+
967
+ Finalize and test:
968
+ - [ ] `objects/greeter.md` - warm, welcoming, no capabilities
969
+ - [ ] `objects/reader.md` - file exploration specialist
970
+ - [ ] `objects/coordinator.md` - delegates to specialists
971
+
972
+ ### 6.4 Backup Plan
973
+
974
+ - [ ] Record backup video of successful run
975
+ - [ ] Prepare fallback if live demo fails
976
+ - [ ] Have reset script to restore clean state
977
+
978
+ ### 6.5 Practice
979
+
980
+ - [ ] Run through demo 5+ times
981
+ - [ ] Time it precisely
982
+ - [ ] Identify common failure points
983
+ - [ ] Prepare recovery strategies
984
+
985
+ ### Phase 6 Deliverable
986
+
987
+ - Polished, tested demo
988
+ - Backup video ready
989
+ - Confident presenter
990
+ - **All 5 demos work smoothly**
991
+
992
+ ---
993
+
994
+ ## File Checklist
995
+
996
+ ```
997
+ prompt_objects/
998
+ ├── Gemfile
999
+ ├── CLAUDE.md
1000
+ ├── IMPLEMENTATION_PLAN.md
1001
+ ├── design-doc-v2.md
1002
+
1003
+ ├── exe/
1004
+ │ └── prompt_objects
1005
+
1006
+ ├── lib/
1007
+ │ ├── prompt_objects.rb
1008
+ │ └── prompt_objects/
1009
+ │ ├── capability.rb
1010
+ │ ├── context.rb
1011
+ │ ├── environment.rb
1012
+ │ ├── executor.rb
1013
+ │ ├── human_queue.rb
1014
+ │ ├── loader.rb
1015
+ │ ├── message_bus.rb
1016
+ │ ├── primitive.rb
1017
+ │ ├── prompt_object.rb
1018
+ │ ├── registry.rb
1019
+ │ │
1020
+ │ ├── llm/
1021
+ │ │ ├── adapter.rb
1022
+ │ │ ├── openai_adapter.rb
1023
+ │ │ └── response.rb
1024
+ │ │
1025
+ │ ├── primitives/
1026
+ │ │ ├── list_files.rb
1027
+ │ │ ├── read_file.rb
1028
+ │ │ └── write_file.rb
1029
+ │ │
1030
+ │ ├── universal/
1031
+ │ │ ├── ask_human.rb
1032
+ │ │ ├── create_capability.rb
1033
+ │ │ └── think.rb
1034
+ │ │
1035
+ │ └── ui/
1036
+ │ ├── app.rb
1037
+ │ ├── capability_bar.rb
1038
+ │ ├── capability_editor.rb
1039
+ │ ├── conversation.rb
1040
+ │ ├── input.rb
1041
+ │ ├── message_log.rb
1042
+ │ ├── notification_panel.rb
1043
+ │ ├── po_inspector.rb
1044
+ │ └── request_responder.rb
1045
+
1046
+ ├── objects/
1047
+ │ ├── coordinator.md
1048
+ │ ├── greeter.md
1049
+ │ └── reader.md
1050
+
1051
+ └── spec/
1052
+ └── (tests as needed)
1053
+ ```
1054
+
1055
+ ---
1056
+
1057
+ ## Dependencies Summary
1058
+
1059
+ ```ruby
1060
+ # Gemfile
1061
+ source "https://rubygems.org"
1062
+
1063
+ # Core
1064
+ gem "ruby-openai" # LLM API (or anthropic SDK)
1065
+ gem "front_matter_parser" # YAML frontmatter parsing
1066
+
1067
+ # Charm TUI (Marco Roth's Ruby ports)
1068
+ gem "bubbletea" # Elm-inspired TUI framework
1069
+ gem "lipgloss" # CSS-like terminal styling
1070
+ gem "glamour" # Markdown rendering
1071
+ gem "bubbles" # Pre-built components (spinners, inputs, etc.)
1072
+ gem "huh" # Interactive forms/prompts
1073
+ ```