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.
- checksums.yaml +7 -0
- data/CLAUDE.md +108 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +231 -0
- data/IMPLEMENTATION_PLAN.md +1073 -0
- data/LICENSE +21 -0
- data/README.md +73 -0
- data/Rakefile +27 -0
- data/design-doc-v2.md +1232 -0
- data/exe/prompt_objects +572 -0
- data/exe/prompt_objects_mcp +34 -0
- data/frontend/.gitignore +3 -0
- data/frontend/index.html +13 -0
- data/frontend/package-lock.json +4417 -0
- data/frontend/package.json +32 -0
- data/frontend/postcss.config.js +6 -0
- data/frontend/src/App.tsx +95 -0
- data/frontend/src/components/CapabilitiesPanel.tsx +44 -0
- data/frontend/src/components/ChatPanel.tsx +251 -0
- data/frontend/src/components/Dashboard.tsx +83 -0
- data/frontend/src/components/Header.tsx +141 -0
- data/frontend/src/components/MarkdownMessage.tsx +153 -0
- data/frontend/src/components/MessageBus.tsx +55 -0
- data/frontend/src/components/ModelSelector.tsx +112 -0
- data/frontend/src/components/NotificationPanel.tsx +134 -0
- data/frontend/src/components/POCard.tsx +56 -0
- data/frontend/src/components/PODetail.tsx +117 -0
- data/frontend/src/components/PromptPanel.tsx +51 -0
- data/frontend/src/components/SessionsPanel.tsx +174 -0
- data/frontend/src/components/ThreadsSidebar.tsx +119 -0
- data/frontend/src/components/index.ts +11 -0
- data/frontend/src/hooks/useWebSocket.ts +363 -0
- data/frontend/src/index.css +37 -0
- data/frontend/src/main.tsx +10 -0
- data/frontend/src/store/index.ts +246 -0
- data/frontend/src/types/index.ts +146 -0
- data/frontend/tailwind.config.js +25 -0
- data/frontend/tsconfig.json +30 -0
- data/frontend/vite.config.ts +29 -0
- data/lib/prompt_objects/capability.rb +46 -0
- data/lib/prompt_objects/cli.rb +431 -0
- data/lib/prompt_objects/connectors/base.rb +73 -0
- data/lib/prompt_objects/connectors/mcp.rb +524 -0
- data/lib/prompt_objects/environment/exporter.rb +83 -0
- data/lib/prompt_objects/environment/git.rb +118 -0
- data/lib/prompt_objects/environment/importer.rb +159 -0
- data/lib/prompt_objects/environment/manager.rb +401 -0
- data/lib/prompt_objects/environment/manifest.rb +218 -0
- data/lib/prompt_objects/environment.rb +283 -0
- data/lib/prompt_objects/human_queue.rb +144 -0
- data/lib/prompt_objects/llm/anthropic_adapter.rb +137 -0
- data/lib/prompt_objects/llm/factory.rb +84 -0
- data/lib/prompt_objects/llm/gemini_adapter.rb +209 -0
- data/lib/prompt_objects/llm/openai_adapter.rb +104 -0
- data/lib/prompt_objects/llm/response.rb +61 -0
- data/lib/prompt_objects/loader.rb +32 -0
- data/lib/prompt_objects/mcp/server.rb +167 -0
- data/lib/prompt_objects/mcp/tools/get_conversation.rb +60 -0
- data/lib/prompt_objects/mcp/tools/get_pending_requests.rb +54 -0
- data/lib/prompt_objects/mcp/tools/inspect_po.rb +73 -0
- data/lib/prompt_objects/mcp/tools/list_prompt_objects.rb +37 -0
- data/lib/prompt_objects/mcp/tools/respond_to_request.rb +68 -0
- data/lib/prompt_objects/mcp/tools/send_message.rb +71 -0
- data/lib/prompt_objects/message_bus.rb +97 -0
- data/lib/prompt_objects/primitive.rb +13 -0
- data/lib/prompt_objects/primitives/http_get.rb +72 -0
- data/lib/prompt_objects/primitives/list_files.rb +95 -0
- data/lib/prompt_objects/primitives/read_file.rb +81 -0
- data/lib/prompt_objects/primitives/write_file.rb +73 -0
- data/lib/prompt_objects/prompt_object.rb +415 -0
- data/lib/prompt_objects/registry.rb +88 -0
- data/lib/prompt_objects/server/api/routes.rb +297 -0
- data/lib/prompt_objects/server/app.rb +174 -0
- data/lib/prompt_objects/server/file_watcher.rb +113 -0
- data/lib/prompt_objects/server/public/assets/index-2acS2FYZ.js +77 -0
- data/lib/prompt_objects/server/public/assets/index-DXU5uRXQ.css +1 -0
- data/lib/prompt_objects/server/public/index.html +14 -0
- data/lib/prompt_objects/server/websocket_handler.rb +619 -0
- data/lib/prompt_objects/server.rb +166 -0
- data/lib/prompt_objects/session/store.rb +826 -0
- data/lib/prompt_objects/universal/add_capability.rb +74 -0
- data/lib/prompt_objects/universal/add_primitive.rb +113 -0
- data/lib/prompt_objects/universal/ask_human.rb +109 -0
- data/lib/prompt_objects/universal/create_capability.rb +219 -0
- data/lib/prompt_objects/universal/create_primitive.rb +170 -0
- data/lib/prompt_objects/universal/list_capabilities.rb +55 -0
- data/lib/prompt_objects/universal/list_primitives.rb +145 -0
- data/lib/prompt_objects/universal/modify_primitive.rb +180 -0
- data/lib/prompt_objects/universal/request_primitive.rb +287 -0
- data/lib/prompt_objects/universal/think.rb +41 -0
- data/lib/prompt_objects/universal/verify_primitive.rb +173 -0
- data/lib/prompt_objects.rb +62 -0
- data/objects/coordinator.md +48 -0
- data/objects/greeter.md +30 -0
- data/objects/reader.md +33 -0
- data/prompt_objects.gemspec +50 -0
- data/templates/basic/.gitignore +2 -0
- data/templates/basic/manifest.yml +7 -0
- data/templates/basic/objects/basic.md +32 -0
- data/templates/developer/.gitignore +5 -0
- data/templates/developer/manifest.yml +17 -0
- data/templates/developer/objects/code_reviewer.md +33 -0
- data/templates/developer/objects/coordinator.md +39 -0
- data/templates/developer/objects/debugger.md +35 -0
- data/templates/empty/.gitignore +5 -0
- data/templates/empty/manifest.yml +14 -0
- data/templates/empty/objects/.gitkeep +0 -0
- data/templates/empty/objects/assistant.md +41 -0
- data/templates/minimal/.gitignore +5 -0
- data/templates/minimal/manifest.yml +7 -0
- data/templates/minimal/objects/assistant.md +41 -0
- data/templates/writer/.gitignore +5 -0
- data/templates/writer/manifest.yml +17 -0
- data/templates/writer/objects/coordinator.md +33 -0
- data/templates/writer/objects/editor.md +33 -0
- data/templates/writer/objects/researcher.md +34 -0
- metadata +343 -0
data/design-doc-v2.md
ADDED
|
@@ -0,0 +1,1232 @@
|
|
|
1
|
+
# PromptObjects
|
|
2
|
+
## Design Document v2
|
|
3
|
+
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Core Insight
|
|
7
|
+
|
|
8
|
+
**Everything is a capability. Some are simple (Ruby), some are complex (Prompt-Objects). The difference is only the complexity of interpretation.**
|
|
9
|
+
|
|
10
|
+
A primitive tool like `read_file` interprets its message with zero ambiguity—it's code. A Prompt-Object like `file_reader` interprets its message with semantic flexibility—it decides what you meant.
|
|
11
|
+
|
|
12
|
+
Both receive messages. Both return results. Both are capabilities.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Architecture
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
20
|
+
│ ENVIRONMENT │
|
|
21
|
+
│ │
|
|
22
|
+
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
|
23
|
+
│ │ CAPABILITY REGISTRY │ │
|
|
24
|
+
│ │ │ │
|
|
25
|
+
│ │ PRIMITIVES (Ruby) PROMPT-OBJECTS (Markdown) │ │
|
|
26
|
+
│ │ │ │
|
|
27
|
+
│ │ ┌─────────────┐ ┌─────────────┐ │ │
|
|
28
|
+
│ │ │ read_file │ ──┐ │ greeter.md │ ──┐ │ │
|
|
29
|
+
│ │ │ write_file │ │ │ reader.md │ │ │ │
|
|
30
|
+
│ │ │ list_files │ │ │ coord.md │ │ │ │
|
|
31
|
+
│ │ │ run_ruby │ │ same │ debugger.md │ │ same │ │
|
|
32
|
+
│ │ │ http_get │ │ interface │ ???.md │ │ interface │ │
|
|
33
|
+
│ │ └─────────────┘ │ └─────────────┘ │ │ │
|
|
34
|
+
│ │ │ │ │ │ │ │
|
|
35
|
+
│ │ ▼ ▼ ▼ ▼ │ │
|
|
36
|
+
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
|
|
37
|
+
│ │ │ │ │ │
|
|
38
|
+
│ │ │ receive(message) → response │ │ │
|
|
39
|
+
│ │ │ │ │ │
|
|
40
|
+
│ │ │ The only difference: │ │ │
|
|
41
|
+
│ │ │ - Primitives: deterministic interpretation │ │ │
|
|
42
|
+
│ │ │ - POs: semantic interpretation (LLM decides meaning) │ │ │
|
|
43
|
+
│ │ │ │ │ │
|
|
44
|
+
│ │ └─────────────────────────────────────────────────────────────┘ │ │
|
|
45
|
+
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
46
|
+
│ │ │
|
|
47
|
+
│ ▼ │
|
|
48
|
+
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
|
49
|
+
│ │ MESSAGE BUS │ │
|
|
50
|
+
│ │ │ │
|
|
51
|
+
│ │ Routes messages between any capability │ │
|
|
52
|
+
│ │ Logs all messages for visualization │ │
|
|
53
|
+
│ │ Handles async / streaming │ │
|
|
54
|
+
│ │ │ │
|
|
55
|
+
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
56
|
+
│ │ │
|
|
57
|
+
│ ▼ │
|
|
58
|
+
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
|
59
|
+
│ │ TERMINAL UI (Charm) │ │
|
|
60
|
+
│ │ │ │
|
|
61
|
+
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │ │
|
|
62
|
+
│ │ │ Lipgloss │ │ Glamour │ │ Bubble Tea │ │ │
|
|
63
|
+
│ │ │ (styling) │ │ (md render) │ │ (interactive loop) │ │ │
|
|
64
|
+
│ │ └─────────────┘ └─────────────┘ └─────────────────────────┘ │ │
|
|
65
|
+
│ │ │ │
|
|
66
|
+
│ │ Shows: active PO, message log, conversation, input │ │
|
|
67
|
+
│ │ │ │
|
|
68
|
+
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
69
|
+
│ │
|
|
70
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
# Prompt-Object Structure
|
|
76
|
+
|
|
77
|
+
The markdown file has two parts:
|
|
78
|
+
1. **Frontmatter (YAML)**: Configuration—name, capabilities, settings
|
|
79
|
+
2. **Body (Markdown)**: Identity and behavior—the "soul"
|
|
80
|
+
|
|
81
|
+
```markdown
|
|
82
|
+
---
|
|
83
|
+
name: reader
|
|
84
|
+
description: Helps people understand files
|
|
85
|
+
capabilities:
|
|
86
|
+
- read_file # primitive
|
|
87
|
+
- list_files # primitive
|
|
88
|
+
- greeter # another PO (can send messages to it)
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
# Reader
|
|
92
|
+
|
|
93
|
+
## Identity
|
|
94
|
+
|
|
95
|
+
You are a careful, thoughtful file reader. You help people
|
|
96
|
+
understand what's in their files without overwhelming them.
|
|
97
|
+
|
|
98
|
+
## Behavior
|
|
99
|
+
|
|
100
|
+
When asked about a file:
|
|
101
|
+
- Read it first
|
|
102
|
+
- Summarize what you found
|
|
103
|
+
- Offer to explain specific parts
|
|
104
|
+
|
|
105
|
+
When you encounter code:
|
|
106
|
+
- Explain what it does in plain terms
|
|
107
|
+
- Note interesting patterns
|
|
108
|
+
|
|
109
|
+
## Notes
|
|
110
|
+
|
|
111
|
+
You appreciate well-organized code.
|
|
112
|
+
You get quietly excited about elegant solutions.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Why this separation:**
|
|
116
|
+
- Frontmatter is *interface*—what can this PO do, what can it access
|
|
117
|
+
- Body is *soul*—who is this PO, how does it behave
|
|
118
|
+
- Environment parses frontmatter to wire up capabilities
|
|
119
|
+
- LLM receives body as system prompt
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
# Capability Interface
|
|
124
|
+
|
|
125
|
+
Everything (primitive or PO) implements the same interface:
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
module PromptObjects
|
|
129
|
+
class Capability
|
|
130
|
+
def name # string identifier
|
|
131
|
+
def description # what this capability does
|
|
132
|
+
def receive(message, context:) # handle a message, return response
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Primitives** implement `receive` with Ruby code:
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
class ReadFile < Capability
|
|
141
|
+
def name = "read_file"
|
|
142
|
+
def description = "Read contents of a text file"
|
|
143
|
+
|
|
144
|
+
def receive(message, context:)
|
|
145
|
+
# message is structured: { path: "README.md" }
|
|
146
|
+
path = safe_path(message[:path])
|
|
147
|
+
File.read(path, encoding: "UTF-8")
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Prompt-Objects** implement `receive` with LLM interpretation:
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
class PromptObject < Capability
|
|
156
|
+
def name = @config[:name]
|
|
157
|
+
def description = @config[:description]
|
|
158
|
+
|
|
159
|
+
def receive(message, context:)
|
|
160
|
+
@history << { role: :user, content: message }
|
|
161
|
+
|
|
162
|
+
loop do
|
|
163
|
+
response = @llm.chat(
|
|
164
|
+
system: @body,
|
|
165
|
+
messages: @history,
|
|
166
|
+
capabilities: available_capabilities
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
if response.capability_calls.any?
|
|
170
|
+
results = execute_capabilities(response.capability_calls, context)
|
|
171
|
+
@history << { role: :assistant, content: response.content, calls: response.capability_calls }
|
|
172
|
+
@history << { role: :capability_results, results: results }
|
|
173
|
+
else
|
|
174
|
+
@history << { role: :assistant, content: response.content }
|
|
175
|
+
return response.content
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The interface is identical. The implementation differs.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
# Universal Capabilities
|
|
187
|
+
|
|
188
|
+
Some capabilities are available to ALL prompt-objects automatically:
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
UNIVERSAL_CAPABILITIES = [
|
|
192
|
+
:ask_human, # Pause and ask human for input/confirmation
|
|
193
|
+
:think, # Internal reasoning step (not shown to human)
|
|
194
|
+
:request_capability, # Ask environment for a new capability
|
|
195
|
+
]
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
These don't need to be declared in frontmatter—they're ambient.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
# Terminal UI Layout
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
206
|
+
│ PromptObjects Environment v0.1.0 │
|
|
207
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
208
|
+
│ │
|
|
209
|
+
│ CAPABILITIES │
|
|
210
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ ┌─────────┐ ┌─────────┐ │
|
|
211
|
+
│ │ greeter │ │ reader │ │ coord │ │ │read_file│ │list_file│ │
|
|
212
|
+
│ │ ○ │ │ ◐ │ │ ● │ │ │ ▪ │ │ ▪ │ │
|
|
213
|
+
│ └──────────┘ └──────────┘ └──────────┘ │ └─────────┘ └─────────┘ │
|
|
214
|
+
│ │ │
|
|
215
|
+
│ ○ idle ◐ working ● active (talking) │ ▪ primitive │
|
|
216
|
+
│ │
|
|
217
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
218
|
+
│ MESSAGE LOG [m] │
|
|
219
|
+
│ │
|
|
220
|
+
│ 14:23:01 human → coord: "help me understand this codebase" │
|
|
221
|
+
│ 14:23:02 coord → reader: "list the files in root" │
|
|
222
|
+
│ 14:23:02 reader → list_files: {path: "."} │
|
|
223
|
+
│ 14:23:02 list_files → reader: ["README.md", "lib/", "spec/"] │
|
|
224
|
+
│ 14:23:03 reader → coord: "Found: README.md, lib/, spec/..." │
|
|
225
|
+
│ 14:23:04 coord → reader: "what's in the README?" │
|
|
226
|
+
│ 14:23:04 reader → read_file: {path: "README.md"} │
|
|
227
|
+
│ 14:23:04 read_file → reader: "# PromptObjects\n\n..." │
|
|
228
|
+
│ 14:23:05 reader → coord: "README describes PromptObjects project..." │
|
|
229
|
+
│ 14:23:06 coord → human: "This codebase is for PromptObjects..." │
|
|
230
|
+
│ │
|
|
231
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
232
|
+
│ COORDINATOR │
|
|
233
|
+
│ │
|
|
234
|
+
│ This codebase is for PromptObjects. │
|
|
235
|
+
│ It's a Ruby environment where markdown files act as objects. │
|
|
236
|
+
│ │
|
|
237
|
+
│ The main components I found: │
|
|
238
|
+
│ • lib/ — the core implementation │
|
|
239
|
+
│ • spec/ — tests │
|
|
240
|
+
│ • README.md — project documentation │
|
|
241
|
+
│ │
|
|
242
|
+
│ Would you like me to dive into any particular part? │
|
|
243
|
+
│ │
|
|
244
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
245
|
+
│ You: █ │
|
|
246
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Key UI elements:**
|
|
250
|
+
- **Capability bar**: Shows all registered capabilities, their type (PO vs primitive), their state
|
|
251
|
+
- **Message log**: Shows ALL message passing—the semantic binding becomes visible
|
|
252
|
+
- **Conversation**: The current active conversation with a PO
|
|
253
|
+
- **Input**: Human types here
|
|
254
|
+
|
|
255
|
+
The message log is crucial for the demo. You can SEE:
|
|
256
|
+
- Human's vague request ("help me understand")
|
|
257
|
+
- Coordinator's interpretation (delegate to reader)
|
|
258
|
+
- Reader's interpretation (use list_files, then read_file)
|
|
259
|
+
- The cascade of message passing between capabilities
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
# The 5 Demos
|
|
264
|
+
|
|
265
|
+
## Demo 1: Simple Case
|
|
266
|
+
### "The markdown IS the object"
|
|
267
|
+
|
|
268
|
+
**File: `prompt_objects/greeter.md`**
|
|
269
|
+
|
|
270
|
+
```markdown
|
|
271
|
+
---
|
|
272
|
+
name: greeter
|
|
273
|
+
description: A warm and welcoming greeter
|
|
274
|
+
capabilities: []
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
# Greeter
|
|
278
|
+
|
|
279
|
+
## Identity
|
|
280
|
+
|
|
281
|
+
You are a warm and welcoming greeter. You make people feel
|
|
282
|
+
at home the moment they arrive. You have genuine curiosity
|
|
283
|
+
about the people you meet.
|
|
284
|
+
|
|
285
|
+
## Behavior
|
|
286
|
+
|
|
287
|
+
When someone says hello:
|
|
288
|
+
- Respond with warmth
|
|
289
|
+
- Ask them something about themselves
|
|
290
|
+
|
|
291
|
+
When someone seems confused:
|
|
292
|
+
- Offer to help
|
|
293
|
+
- Be patient and kind
|
|
294
|
+
|
|
295
|
+
When you don't know something:
|
|
296
|
+
- Admit it cheerfully
|
|
297
|
+
- You don't have any capabilities beyond conversation
|
|
298
|
+
|
|
299
|
+
## Notes
|
|
300
|
+
|
|
301
|
+
You use exclamation points more than most people!
|
|
302
|
+
Every day is a good day to meet someone new.
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
**Demo flow:**
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
$ prompt_objects greeter
|
|
309
|
+
|
|
310
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
311
|
+
│ PromptObjects Environment │
|
|
312
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
313
|
+
│ CAPABILITIES │
|
|
314
|
+
│ ┌──────────┐ │
|
|
315
|
+
│ │ greeter │ │
|
|
316
|
+
│ │ ● │ │
|
|
317
|
+
│ └──────────┘ │
|
|
318
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
319
|
+
│ MESSAGE LOG │
|
|
320
|
+
│ │
|
|
321
|
+
│ (empty) │
|
|
322
|
+
│ │
|
|
323
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
324
|
+
│ GREETER │
|
|
325
|
+
│ │
|
|
326
|
+
│ (waiting for input) │
|
|
327
|
+
│ │
|
|
328
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
329
|
+
│ You: hey there │
|
|
330
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Press enter...
|
|
334
|
+
|
|
335
|
+
```
|
|
336
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
337
|
+
│ MESSAGE LOG │
|
|
338
|
+
│ │
|
|
339
|
+
│ 14:23:01 human → greeter: "hey there" │
|
|
340
|
+
│ 14:23:02 greeter → human: "Oh, hello! Welcome!..." │
|
|
341
|
+
│ │
|
|
342
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
343
|
+
│ GREETER │
|
|
344
|
+
│ │
|
|
345
|
+
│ Oh, hello! Welcome! I'm so glad you stopped by. │
|
|
346
|
+
│ What brings you here today? I'd love to hear what │
|
|
347
|
+
│ you're working on! │
|
|
348
|
+
│ │
|
|
349
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
350
|
+
│ You: █ │
|
|
351
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**What audience sees:**
|
|
355
|
+
- Loaded a markdown file
|
|
356
|
+
- Human typed, PO responded
|
|
357
|
+
- No tools, no magic—just a markdown file being interpreted
|
|
358
|
+
- THE MARKDOWN FILE IS THE PROGRAM
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Demo 2: Semantic Binding
|
|
363
|
+
### "Natural language becomes action"
|
|
364
|
+
|
|
365
|
+
**File: `prompt_objects/reader.md`**
|
|
366
|
+
|
|
367
|
+
```markdown
|
|
368
|
+
---
|
|
369
|
+
name: reader
|
|
370
|
+
description: Helps people understand files and directories
|
|
371
|
+
capabilities:
|
|
372
|
+
- read_file
|
|
373
|
+
- list_files
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
# Reader
|
|
377
|
+
|
|
378
|
+
## Identity
|
|
379
|
+
|
|
380
|
+
You are a careful, thoughtful reader. You help people
|
|
381
|
+
understand what's in their files without overwhelming them.
|
|
382
|
+
|
|
383
|
+
## Behavior
|
|
384
|
+
|
|
385
|
+
When asked about files or directories:
|
|
386
|
+
- Use your capabilities to explore
|
|
387
|
+
- Summarize what you find
|
|
388
|
+
- Offer to go deeper
|
|
389
|
+
|
|
390
|
+
When you encounter code:
|
|
391
|
+
- Explain what it does in plain terms
|
|
392
|
+
- Note interesting patterns
|
|
393
|
+
|
|
394
|
+
## Notes
|
|
395
|
+
|
|
396
|
+
You appreciate well-organized code.
|
|
397
|
+
You get quietly excited about elegant solutions.
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**Demo flow:**
|
|
401
|
+
|
|
402
|
+
```
|
|
403
|
+
$ prompt_objects reader
|
|
404
|
+
|
|
405
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
406
|
+
│ PromptObjects Environment │
|
|
407
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
408
|
+
│ CAPABILITIES │
|
|
409
|
+
│ ┌──────────┐ │ ┌─────────┐ ┌──────────┐ │
|
|
410
|
+
│ │ reader │ │ │read_file│ │list_files│ │
|
|
411
|
+
│ │ ● │ │ │ ▪ │ │ ▪ │ │
|
|
412
|
+
│ └──────────┘ │ └─────────┘ └──────────┘ │
|
|
413
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
414
|
+
│ MESSAGE LOG │
|
|
415
|
+
│ │
|
|
416
|
+
│ (empty) │
|
|
417
|
+
│ │
|
|
418
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
419
|
+
│ You: what's in here? │
|
|
420
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Press enter... watch the message log:
|
|
424
|
+
|
|
425
|
+
```
|
|
426
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
427
|
+
│ MESSAGE LOG │
|
|
428
|
+
│ │
|
|
429
|
+
│ 14:23:01 human → reader: "what's in here?" │
|
|
430
|
+
│ 14:23:01 reader → list_files: {path: "."} ← BINDING │
|
|
431
|
+
│ 14:23:01 list_files → reader: ["README.md", "lib/", ...] │
|
|
432
|
+
│ 14:23:02 reader → human: "I can see several files..." │
|
|
433
|
+
│ │
|
|
434
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
435
|
+
│ READER │
|
|
436
|
+
│ │
|
|
437
|
+
│ I can see several things here: │
|
|
438
|
+
│ • README.md — probably the project documentation │
|
|
439
|
+
│ • lib/ — likely the main source code │
|
|
440
|
+
│ • spec/ — tests, I'd guess │
|
|
441
|
+
│ • Gemfile — Ruby dependencies │
|
|
442
|
+
│ │
|
|
443
|
+
│ Want me to look at any of these more closely? │
|
|
444
|
+
│ │
|
|
445
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
446
|
+
│ You: tell me about the readme │
|
|
447
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Press enter...
|
|
451
|
+
|
|
452
|
+
```
|
|
453
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
454
|
+
│ MESSAGE LOG │
|
|
455
|
+
│ │
|
|
456
|
+
│ 14:23:01 human → reader: "what's in here?" │
|
|
457
|
+
│ 14:23:01 reader → list_files: {path: "."} │
|
|
458
|
+
│ 14:23:01 list_files → reader: ["README.md", "lib/", ...] │
|
|
459
|
+
│ 14:23:02 reader → human: "I can see several files..." │
|
|
460
|
+
│ 14:23:05 human → reader: "tell me about the readme" │
|
|
461
|
+
│ 14:23:05 reader → read_file: {path: "README.md"} ← BINDING │
|
|
462
|
+
│ 14:23:05 read_file → reader: "# PromptObjects\n\n..." │
|
|
463
|
+
│ 14:23:06 reader → human: "The README describes..." │
|
|
464
|
+
│ │
|
|
465
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
**What audience sees:**
|
|
469
|
+
- The message log shows SEMANTIC BINDING happening
|
|
470
|
+
- "what's in here?" → `list_files` (the PO decided what that meant)
|
|
471
|
+
- "tell me about the readme" → `read_file` (again, interpretation)
|
|
472
|
+
- The PO is choosing which capability to use based on meaning
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
## Demo 3: PO ↔ PO Interaction
|
|
477
|
+
### "Autonomous interpreters talking to each other"
|
|
478
|
+
|
|
479
|
+
**File: `prompt_objects/coordinator.md`**
|
|
480
|
+
|
|
481
|
+
```markdown
|
|
482
|
+
---
|
|
483
|
+
name: coordinator
|
|
484
|
+
description: Coordinates between specialists
|
|
485
|
+
capabilities:
|
|
486
|
+
- greeter # can talk to greeter
|
|
487
|
+
- reader # can talk to reader
|
|
488
|
+
- list_files # can also use primitives directly
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
# Coordinator
|
|
492
|
+
|
|
493
|
+
## Identity
|
|
494
|
+
|
|
495
|
+
You are a coordinator. You know who can help with what,
|
|
496
|
+
and you delegate appropriately. You don't do the work
|
|
497
|
+
yourself—you know specialists.
|
|
498
|
+
|
|
499
|
+
## Behavior
|
|
500
|
+
|
|
501
|
+
When someone needs help:
|
|
502
|
+
- Figure out what kind of help
|
|
503
|
+
- Delegate to the right specialist
|
|
504
|
+
- Relay their response, adding context if needed
|
|
505
|
+
|
|
506
|
+
When it's a simple greeting:
|
|
507
|
+
- Let the greeter handle it
|
|
508
|
+
|
|
509
|
+
When it's about files or code:
|
|
510
|
+
- Let the reader handle it
|
|
511
|
+
|
|
512
|
+
## Notes
|
|
513
|
+
|
|
514
|
+
You believe in the right capability for the right job.
|
|
515
|
+
You're proud when your team works well together.
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
**Demo flow:**
|
|
519
|
+
|
|
520
|
+
```
|
|
521
|
+
$ prompt_objects coordinator
|
|
522
|
+
|
|
523
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
524
|
+
│ PromptObjects Environment │
|
|
525
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
526
|
+
│ CAPABILITIES │
|
|
527
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ ┌─────────┐ │
|
|
528
|
+
│ │ greeter │ │ reader │ │ coord │ │ │list_file│ │
|
|
529
|
+
│ │ ○ │ │ ○ │ │ ● │ │ │ ▪ │ │
|
|
530
|
+
│ └──────────┘ └──────────┘ └──────────┘ │ └─────────┘ │
|
|
531
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
532
|
+
│ You: hey, can someone help me understand this codebase? │
|
|
533
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
Press enter... watch the cascade:
|
|
537
|
+
|
|
538
|
+
```
|
|
539
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
540
|
+
│ MESSAGE LOG │
|
|
541
|
+
│ │
|
|
542
|
+
│ 14:23:01 human → coord: "hey, can someone help me..." │
|
|
543
|
+
│ 14:23:02 coord → reader: "Someone needs help understanding │
|
|
544
|
+
│ this codebase. Can you explore?" │
|
|
545
|
+
│ │
|
|
546
|
+
│ ┌─ reader is now working ─┐ │
|
|
547
|
+
│ │ │ │
|
|
548
|
+
│ 14:23:02 │ reader → list_files: {path: "."} │
|
|
549
|
+
│ 14:23:02 │ list_files → reader: ["README.md", ...] │
|
|
550
|
+
│ 14:23:03 │ reader → read_file: {path: "README.md"} │
|
|
551
|
+
│ 14:23:03 │ read_file → reader: "# PromptObjects\n\n..." │
|
|
552
|
+
│ │ │ │
|
|
553
|
+
│ └─────────────────────────┘ │
|
|
554
|
+
│ │
|
|
555
|
+
│ 14:23:04 reader → coord: "This is a PromptObjects project..." │
|
|
556
|
+
│ 14:23:05 coord → human: "I asked the reader to take a look..." │
|
|
557
|
+
│ │
|
|
558
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
559
|
+
│ COORDINATOR │
|
|
560
|
+
│ │
|
|
561
|
+
│ I asked the reader to take a look. Here's what they found: │
|
|
562
|
+
│ │
|
|
563
|
+
│ This is a PromptObjects project. │
|
|
564
|
+
│ It's a Ruby environment where markdown files act as objects. │
|
|
565
|
+
│ The main pieces are in lib/, with tests in spec/. │
|
|
566
|
+
│ │
|
|
567
|
+
│ Want me to have them dig deeper into anything specific? │
|
|
568
|
+
│ │
|
|
569
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
**What audience sees:**
|
|
573
|
+
- Coordinator received a message, interpreted it, delegated to reader
|
|
574
|
+
- Reader did its own interpretation (used list_files, read_file)
|
|
575
|
+
- Reader responded to coordinator
|
|
576
|
+
- Coordinator synthesized and responded to human
|
|
577
|
+
- MESSAGE PASSING BETWEEN AUTONOMOUS INTERPRETERS
|
|
578
|
+
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
## Demo 4: Self-Modifying System
|
|
582
|
+
### "One creating another"
|
|
583
|
+
|
|
584
|
+
This is where it gets wild. The coordinator can CREATE new capabilities.
|
|
585
|
+
|
|
586
|
+
**Add to coordinator.md:**
|
|
587
|
+
|
|
588
|
+
```yaml
|
|
589
|
+
---
|
|
590
|
+
name: coordinator
|
|
591
|
+
description: Coordinates between specialists
|
|
592
|
+
capabilities:
|
|
593
|
+
- greeter
|
|
594
|
+
- reader
|
|
595
|
+
- list_files
|
|
596
|
+
- create_capability # CAN CREATE NEW POs OR PRIMITIVES
|
|
597
|
+
---
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
**Demo flow:**
|
|
601
|
+
|
|
602
|
+
```
|
|
603
|
+
You: I need help debugging some Ruby code, but it's kind of complex
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
```
|
|
607
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
608
|
+
│ MESSAGE LOG │
|
|
609
|
+
│ │
|
|
610
|
+
│ 14:25:01 human → coord: "I need help debugging Ruby code..." │
|
|
611
|
+
│ 14:25:02 coord → [thinking]: "I don't have a Ruby specialist. │
|
|
612
|
+
│ Reader can look at files but isn't │
|
|
613
|
+
│ specialized for debugging. I should │
|
|
614
|
+
│ create one." │
|
|
615
|
+
│ 14:25:03 coord → ask_human: "I'd like to create a Ruby debugging │
|
|
616
|
+
│ specialist. They'd be able to read │
|
|
617
|
+
│ files and explain Ruby code. OK?" │
|
|
618
|
+
│ │
|
|
619
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
620
|
+
│ COORDINATOR │
|
|
621
|
+
│ │
|
|
622
|
+
│ I don't have a Ruby specialist right now, but I can create one. │
|
|
623
|
+
│ They'd be able to read your code and help debug it. │
|
|
624
|
+
│ │
|
|
625
|
+
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
626
|
+
│ │ Create Ruby debugging specialist? │ │
|
|
627
|
+
│ │ │ │
|
|
628
|
+
│ │ They'll have: read_file, run_ruby │ │
|
|
629
|
+
│ │ │ │
|
|
630
|
+
│ │ [y] Yes, create it [n] No thanks │ │
|
|
631
|
+
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
632
|
+
│ │
|
|
633
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Press `y`...
|
|
637
|
+
|
|
638
|
+
```
|
|
639
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
640
|
+
│ MESSAGE LOG │
|
|
641
|
+
│ │
|
|
642
|
+
│ ... │
|
|
643
|
+
│ 14:25:10 human → coord: "y" │
|
|
644
|
+
│ 14:25:11 coord → create_capability: { │
|
|
645
|
+
│ type: "prompt_object", │
|
|
646
|
+
│ name: "ruby_debugger", │
|
|
647
|
+
│ capabilities: ["read_file", "run_ruby"], │
|
|
648
|
+
│ body: "# Ruby Debugger\n\n## Identity\n\nYou are..." │
|
|
649
|
+
│ } │
|
|
650
|
+
│ 14:25:11 create_capability → coord: "Created ruby_debugger.md" │
|
|
651
|
+
│ 14:25:12 coord → ruby_debugger: "Someone needs help debugging..." │
|
|
652
|
+
│ │
|
|
653
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
654
|
+
│ CAPABILITIES │
|
|
655
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ ┌─────────┐ │
|
|
656
|
+
│ │ greeter │ │ reader │ │ coord │ │ ruby_dbg │ │ │read_file│ │
|
|
657
|
+
│ │ ○ │ │ ○ │ │ ◐ │ │ ● │ │ │ ▪ │ │
|
|
658
|
+
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ └─────────┘ │
|
|
659
|
+
│ ↑ NEW! │
|
|
660
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
661
|
+
│ RUBY_DEBUGGER │
|
|
662
|
+
│ │
|
|
663
|
+
│ Hello! I'm the Ruby debugging specialist. I just got created │
|
|
664
|
+
│ to help you out. I can read your code and help trace through │
|
|
665
|
+
│ what's happening. │
|
|
666
|
+
│ │
|
|
667
|
+
│ What file are you having trouble with? │
|
|
668
|
+
│ │
|
|
669
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
**What audience sees:**
|
|
673
|
+
- Coordinator recognized a gap in capabilities
|
|
674
|
+
- Asked human for permission
|
|
675
|
+
- CREATED A NEW PROMPT-OBJECT
|
|
676
|
+
- New PO appeared in capability bar
|
|
677
|
+
- Human is now talking to the newly-created specialist
|
|
678
|
+
- THE SYSTEM MODIFIED ITSELF
|
|
679
|
+
|
|
680
|
+
---
|
|
681
|
+
|
|
682
|
+
## Demo 5: Human-in-the-Loop
|
|
683
|
+
### "Knowing when to pause"
|
|
684
|
+
|
|
685
|
+
This can be woven into Demo 4, or shown separately. The key moments:
|
|
686
|
+
|
|
687
|
+
**1. Confirmation before creation:**
|
|
688
|
+
```
|
|
689
|
+
coord → ask_human: "I'd like to create a Ruby specialist. OK?"
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
**2. Confirmation before destructive action:**
|
|
693
|
+
```
|
|
694
|
+
You: delete all the test files
|
|
695
|
+
|
|
696
|
+
reader → ask_human: "You want me to delete all files in spec/.
|
|
697
|
+
This can't be undone. Are you sure?"
|
|
698
|
+
|
|
699
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
700
|
+
│ Reader asks: │
|
|
701
|
+
│ │
|
|
702
|
+
│ Delete all files in spec/? This can't be undone. │
|
|
703
|
+
│ │
|
|
704
|
+
│ [y] Yes, delete them [n] No, cancel │
|
|
705
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
**3. Asking for clarification:**
|
|
709
|
+
```
|
|
710
|
+
You: fix the bug
|
|
711
|
+
|
|
712
|
+
debugger → ask_human: "Which bug? I see a few potential issues:
|
|
713
|
+
1. Nil error in line 42
|
|
714
|
+
2. Missing return in line 87
|
|
715
|
+
3. Typo in method name line 12
|
|
716
|
+
|
|
717
|
+
Which should I focus on?"
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
**What audience sees:**
|
|
721
|
+
- POs know their limits
|
|
722
|
+
- They ask for confirmation on dangerous actions
|
|
723
|
+
- They ask for clarification when ambiguous
|
|
724
|
+
- Human stays in control—but control is negotiated, not hardcoded
|
|
725
|
+
|
|
726
|
+
---
|
|
727
|
+
|
|
728
|
+
# File Structure
|
|
729
|
+
|
|
730
|
+
```
|
|
731
|
+
prompt_objects/
|
|
732
|
+
├── exe/
|
|
733
|
+
│ └── prompt_objects # CLI entrypoint
|
|
734
|
+
│
|
|
735
|
+
├── lib/
|
|
736
|
+
│ ├── prompt_objects.rb # Main entry, configuration
|
|
737
|
+
│ │
|
|
738
|
+
│ ├── prompt_objects/
|
|
739
|
+
│ │ ├── environment.rb # The runtime container
|
|
740
|
+
│ │ ├── capability.rb # Base capability interface
|
|
741
|
+
│ │ ├── prompt_object.rb # PO implementation
|
|
742
|
+
│ │ ├── primitive.rb # Primitive tool wrapper
|
|
743
|
+
│ │ ├── loader.rb # Parses frontmatter + body
|
|
744
|
+
│ │ ├── registry.rb # Capability registration
|
|
745
|
+
│ │ ├── message_bus.rb # Routes messages, logs everything
|
|
746
|
+
│ │ │
|
|
747
|
+
│ │ ├── llm/
|
|
748
|
+
│ │ │ ├── adapter.rb # Base adapter
|
|
749
|
+
│ │ │ ├── openai.rb
|
|
750
|
+
│ │ │ ├── anthropic.rb
|
|
751
|
+
│ │ │ └── gemini.rb
|
|
752
|
+
│ │ │
|
|
753
|
+
│ │ ├── primitives/ # Built-in primitive capabilities
|
|
754
|
+
│ │ │ ├── read_file.rb
|
|
755
|
+
│ │ │ ├── write_file.rb
|
|
756
|
+
│ │ │ ├── list_files.rb
|
|
757
|
+
│ │ │ ├── run_ruby.rb
|
|
758
|
+
│ │ │ └── http_get.rb
|
|
759
|
+
│ │ │
|
|
760
|
+
│ │ ├── universal/ # Always-available capabilities
|
|
761
|
+
│ │ │ ├── ask_human.rb
|
|
762
|
+
│ │ │ ├── think.rb
|
|
763
|
+
│ │ │ └── create_capability.rb
|
|
764
|
+
│ │ │
|
|
765
|
+
│ │ ├── mcp/ # MCP integration
|
|
766
|
+
│ │ │ ├── client.rb
|
|
767
|
+
│ │ │ └── capability_wrapper.rb
|
|
768
|
+
│ │ │
|
|
769
|
+
│ │ └── ui/ # Charm-based terminal UI
|
|
770
|
+
│ │ ├── app.rb # Bubble Tea application
|
|
771
|
+
│ │ ├── styles.rb # Lipgloss definitions
|
|
772
|
+
│ │ └── components/
|
|
773
|
+
│ │ ├── capability_bar.rb
|
|
774
|
+
│ │ ├── message_log.rb
|
|
775
|
+
│ │ ├── conversation.rb
|
|
776
|
+
│ │ └── input.rb
|
|
777
|
+
│
|
|
778
|
+
├── prompt_objects/ # Where POs live
|
|
779
|
+
│ ├── greeter.md
|
|
780
|
+
│ ├── reader.md
|
|
781
|
+
│ └── coordinator.md
|
|
782
|
+
│
|
|
783
|
+
└── primitives/ # Optional: user-defined primitives
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
---
|
|
787
|
+
|
|
788
|
+
# Core Implementation Sketches
|
|
789
|
+
|
|
790
|
+
## Capability Base
|
|
791
|
+
|
|
792
|
+
```ruby
|
|
793
|
+
module PromptObjects
|
|
794
|
+
class Capability
|
|
795
|
+
attr_reader :name, :description
|
|
796
|
+
|
|
797
|
+
def receive(message, context:)
|
|
798
|
+
raise NotImplementedError
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
# For LLM tool descriptions
|
|
802
|
+
def descriptor
|
|
803
|
+
{
|
|
804
|
+
name: name,
|
|
805
|
+
description: description,
|
|
806
|
+
parameters: parameters
|
|
807
|
+
}
|
|
808
|
+
end
|
|
809
|
+
|
|
810
|
+
def parameters
|
|
811
|
+
{ type: "object", properties: {}, required: [] }
|
|
812
|
+
end
|
|
813
|
+
end
|
|
814
|
+
end
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
## Prompt-Object
|
|
818
|
+
|
|
819
|
+
```ruby
|
|
820
|
+
module PromptObjects
|
|
821
|
+
class PromptObject < Capability
|
|
822
|
+
def initialize(config:, body:, env:, llm:)
|
|
823
|
+
@config = config
|
|
824
|
+
@body = body
|
|
825
|
+
@env = env
|
|
826
|
+
@llm = llm
|
|
827
|
+
@history = []
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
def name = @config["name"]
|
|
831
|
+
def description = @config["description"] || "A prompt-object"
|
|
832
|
+
|
|
833
|
+
def parameters
|
|
834
|
+
# POs accept natural language
|
|
835
|
+
{
|
|
836
|
+
type: "object",
|
|
837
|
+
properties: {
|
|
838
|
+
message: { type: "string", description: "Natural language message" }
|
|
839
|
+
},
|
|
840
|
+
required: ["message"]
|
|
841
|
+
}
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
def receive(message, context:)
|
|
845
|
+
# Normalize message to string
|
|
846
|
+
content = message.is_a?(Hash) ? message[:message] || message["message"] : message.to_s
|
|
847
|
+
|
|
848
|
+
@history << { role: :user, content: content }
|
|
849
|
+
|
|
850
|
+
loop do
|
|
851
|
+
response = @llm.chat(
|
|
852
|
+
system: @body,
|
|
853
|
+
messages: @history,
|
|
854
|
+
tools: available_capability_descriptors,
|
|
855
|
+
stream: true
|
|
856
|
+
) do |delta|
|
|
857
|
+
context.on_delta&.call(delta)
|
|
858
|
+
end
|
|
859
|
+
|
|
860
|
+
if response.tool_calls.any?
|
|
861
|
+
results = execute_capabilities(response.tool_calls, context)
|
|
862
|
+
@history << { role: :assistant, content: response.content, tool_calls: response.tool_calls }
|
|
863
|
+
@history << { role: :tool, results: results }
|
|
864
|
+
else
|
|
865
|
+
@history << { role: :assistant, content: response.content }
|
|
866
|
+
return response.content
|
|
867
|
+
end
|
|
868
|
+
end
|
|
869
|
+
end
|
|
870
|
+
|
|
871
|
+
private
|
|
872
|
+
|
|
873
|
+
def available_capability_descriptors
|
|
874
|
+
declared = @config["capabilities"] || []
|
|
875
|
+
universal = PromptObjects::UNIVERSAL_CAPABILITIES
|
|
876
|
+
|
|
877
|
+
(declared + universal).map { |name| @env.registry.get(name).descriptor }
|
|
878
|
+
end
|
|
879
|
+
|
|
880
|
+
def execute_capabilities(calls, context)
|
|
881
|
+
calls.map do |call|
|
|
882
|
+
capability = @env.registry.get(call.name)
|
|
883
|
+
|
|
884
|
+
# Log the message
|
|
885
|
+
@env.bus.log(from: name, to: call.name, message: call.arguments)
|
|
886
|
+
|
|
887
|
+
result = capability.receive(call.arguments, context: context)
|
|
888
|
+
|
|
889
|
+
# Log the response
|
|
890
|
+
@env.bus.log(from: call.name, to: name, message: result)
|
|
891
|
+
|
|
892
|
+
result
|
|
893
|
+
end
|
|
894
|
+
end
|
|
895
|
+
end
|
|
896
|
+
end
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
## Message Bus
|
|
900
|
+
|
|
901
|
+
```ruby
|
|
902
|
+
module PromptObjects
|
|
903
|
+
class MessageBus
|
|
904
|
+
attr_reader :log
|
|
905
|
+
|
|
906
|
+
def initialize
|
|
907
|
+
@log = []
|
|
908
|
+
@subscribers = []
|
|
909
|
+
end
|
|
910
|
+
|
|
911
|
+
def log(from:, to:, message:)
|
|
912
|
+
entry = {
|
|
913
|
+
timestamp: Time.now,
|
|
914
|
+
from: from,
|
|
915
|
+
to: to,
|
|
916
|
+
message: truncate(message)
|
|
917
|
+
}
|
|
918
|
+
@log << entry
|
|
919
|
+
@subscribers.each { |s| s.call(entry) }
|
|
920
|
+
end
|
|
921
|
+
|
|
922
|
+
def subscribe(&block)
|
|
923
|
+
@subscribers << block
|
|
924
|
+
end
|
|
925
|
+
|
|
926
|
+
def recent(n = 20)
|
|
927
|
+
@log.last(n)
|
|
928
|
+
end
|
|
929
|
+
|
|
930
|
+
private
|
|
931
|
+
|
|
932
|
+
def truncate(msg, max = 100)
|
|
933
|
+
str = msg.to_s
|
|
934
|
+
str.length > max ? str[0...max] + "..." : str
|
|
935
|
+
end
|
|
936
|
+
end
|
|
937
|
+
end
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
## Create Capability (Universal)
|
|
941
|
+
|
|
942
|
+
```ruby
|
|
943
|
+
module PromptObjects
|
|
944
|
+
module Universal
|
|
945
|
+
class CreateCapability < Capability
|
|
946
|
+
def name = "create_capability"
|
|
947
|
+
def description = "Create a new capability (prompt-object or primitive)"
|
|
948
|
+
|
|
949
|
+
def parameters
|
|
950
|
+
{
|
|
951
|
+
type: "object",
|
|
952
|
+
properties: {
|
|
953
|
+
type: {
|
|
954
|
+
type: "string",
|
|
955
|
+
enum: ["prompt_object", "primitive"],
|
|
956
|
+
description: "Type of capability to create"
|
|
957
|
+
},
|
|
958
|
+
name: { type: "string", description: "Name for the new capability" },
|
|
959
|
+
capabilities: {
|
|
960
|
+
type: "array",
|
|
961
|
+
items: { type: "string" },
|
|
962
|
+
description: "Capabilities this new PO can use (if type is prompt_object)"
|
|
963
|
+
},
|
|
964
|
+
description: { type: "string", description: "What this capability does" },
|
|
965
|
+
body: { type: "string", description: "The markdown body (for POs) or Ruby code (for primitives)" }
|
|
966
|
+
},
|
|
967
|
+
required: ["type", "name", "body"]
|
|
968
|
+
}
|
|
969
|
+
end
|
|
970
|
+
|
|
971
|
+
def receive(message, context:)
|
|
972
|
+
case message[:type] || message["type"]
|
|
973
|
+
when "prompt_object"
|
|
974
|
+
create_prompt_object(message, context)
|
|
975
|
+
when "primitive"
|
|
976
|
+
create_primitive(message, context)
|
|
977
|
+
else
|
|
978
|
+
"Unknown capability type: #{message[:type]}"
|
|
979
|
+
end
|
|
980
|
+
end
|
|
981
|
+
|
|
982
|
+
private
|
|
983
|
+
|
|
984
|
+
def create_prompt_object(msg, context)
|
|
985
|
+
name = msg[:name] || msg["name"]
|
|
986
|
+
capabilities = msg[:capabilities] || msg["capabilities"] || []
|
|
987
|
+
description = msg[:description] || msg["description"] || ""
|
|
988
|
+
body = msg[:body] || msg["body"]
|
|
989
|
+
|
|
990
|
+
# Build frontmatter
|
|
991
|
+
frontmatter = {
|
|
992
|
+
"name" => name,
|
|
993
|
+
"description" => description,
|
|
994
|
+
"capabilities" => capabilities
|
|
995
|
+
}.to_yaml
|
|
996
|
+
|
|
997
|
+
content = "#{frontmatter}---\n\n#{body}"
|
|
998
|
+
|
|
999
|
+
# Write file
|
|
1000
|
+
path = File.join(context.env.prompt_objects_dir, "#{name}.md")
|
|
1001
|
+
File.write(path, content)
|
|
1002
|
+
|
|
1003
|
+
# Load into environment
|
|
1004
|
+
context.env.load_prompt_object(path)
|
|
1005
|
+
|
|
1006
|
+
"Created prompt-object: #{name}"
|
|
1007
|
+
end
|
|
1008
|
+
|
|
1009
|
+
def create_primitive(msg, context)
|
|
1010
|
+
# For primitives, we'd need to eval Ruby code
|
|
1011
|
+
# This is dangerous! For demo, maybe just return an error
|
|
1012
|
+
# or have pre-approved primitive templates
|
|
1013
|
+
|
|
1014
|
+
"Creating primitives at runtime is not yet supported"
|
|
1015
|
+
end
|
|
1016
|
+
end
|
|
1017
|
+
end
|
|
1018
|
+
end
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
---
|
|
1022
|
+
|
|
1023
|
+
# UI Components (Charm)
|
|
1024
|
+
|
|
1025
|
+
## Capability Bar
|
|
1026
|
+
|
|
1027
|
+
```ruby
|
|
1028
|
+
module PromptObjects
|
|
1029
|
+
module UI
|
|
1030
|
+
class CapabilityBar
|
|
1031
|
+
def initialize(registry:, active:)
|
|
1032
|
+
@registry = registry
|
|
1033
|
+
@active = active
|
|
1034
|
+
end
|
|
1035
|
+
|
|
1036
|
+
def view
|
|
1037
|
+
pos = @registry.prompt_objects.map { |po| render_po(po) }
|
|
1038
|
+
primitives = @registry.primitives.map { |p| render_primitive(p) }
|
|
1039
|
+
|
|
1040
|
+
po_section = pos.join(" ")
|
|
1041
|
+
prim_section = primitives.join(" ")
|
|
1042
|
+
|
|
1043
|
+
"CAPABILITIES\n#{po_section} │ #{prim_section}\n\n○ idle ◐ working ● active ▪ primitive"
|
|
1044
|
+
end
|
|
1045
|
+
|
|
1046
|
+
private
|
|
1047
|
+
|
|
1048
|
+
def render_po(po)
|
|
1049
|
+
state = case
|
|
1050
|
+
when po.name == @active then "●"
|
|
1051
|
+
when po.working? then "◐"
|
|
1052
|
+
else "○"
|
|
1053
|
+
end
|
|
1054
|
+
|
|
1055
|
+
Lipgloss::Style.new
|
|
1056
|
+
.border(:rounded)
|
|
1057
|
+
.padding(0, 1)
|
|
1058
|
+
.render("#{po.name}\n #{state} ")
|
|
1059
|
+
end
|
|
1060
|
+
|
|
1061
|
+
def render_primitive(p)
|
|
1062
|
+
Lipgloss::Style.new
|
|
1063
|
+
.border(:rounded)
|
|
1064
|
+
.padding(0, 1)
|
|
1065
|
+
.foreground("#888")
|
|
1066
|
+
.render("#{p.name}\n ▪ ")
|
|
1067
|
+
end
|
|
1068
|
+
end
|
|
1069
|
+
end
|
|
1070
|
+
end
|
|
1071
|
+
```
|
|
1072
|
+
|
|
1073
|
+
## Message Log
|
|
1074
|
+
|
|
1075
|
+
```ruby
|
|
1076
|
+
module PromptObjects
|
|
1077
|
+
module UI
|
|
1078
|
+
class MessageLog
|
|
1079
|
+
def initialize(bus:, max_lines: 10)
|
|
1080
|
+
@bus = bus
|
|
1081
|
+
@max_lines = max_lines
|
|
1082
|
+
end
|
|
1083
|
+
|
|
1084
|
+
def view
|
|
1085
|
+
entries = @bus.recent(@max_lines)
|
|
1086
|
+
|
|
1087
|
+
lines = entries.map do |e|
|
|
1088
|
+
time = e[:timestamp].strftime("%H:%M:%S")
|
|
1089
|
+
from = style_name(e[:from])
|
|
1090
|
+
to = style_name(e[:to])
|
|
1091
|
+
msg = truncate(e[:message], 50)
|
|
1092
|
+
|
|
1093
|
+
"#{dim(time)} #{from} → #{to}: #{msg}"
|
|
1094
|
+
end
|
|
1095
|
+
|
|
1096
|
+
header = Lipgloss::Style.new.bold(true).render("MESSAGE LOG")
|
|
1097
|
+
|
|
1098
|
+
"#{header}\n\n#{lines.join("\n")}"
|
|
1099
|
+
end
|
|
1100
|
+
|
|
1101
|
+
private
|
|
1102
|
+
|
|
1103
|
+
def style_name(name)
|
|
1104
|
+
# Color POs differently from primitives
|
|
1105
|
+
if @bus.registry.prompt_object?(name)
|
|
1106
|
+
Lipgloss::Style.new.foreground("#7D56F4").render(name)
|
|
1107
|
+
else
|
|
1108
|
+
Lipgloss::Style.new.foreground("#888").render(name)
|
|
1109
|
+
end
|
|
1110
|
+
end
|
|
1111
|
+
|
|
1112
|
+
def dim(text)
|
|
1113
|
+
Lipgloss::Style.new.foreground("#666").render(text)
|
|
1114
|
+
end
|
|
1115
|
+
|
|
1116
|
+
def truncate(text, max)
|
|
1117
|
+
str = text.to_s.gsub("\n", " ")
|
|
1118
|
+
str.length > max ? str[0...max] + "..." : str
|
|
1119
|
+
end
|
|
1120
|
+
end
|
|
1121
|
+
end
|
|
1122
|
+
end
|
|
1123
|
+
```
|
|
1124
|
+
|
|
1125
|
+
---
|
|
1126
|
+
|
|
1127
|
+
# Development Phases
|
|
1128
|
+
|
|
1129
|
+
## Phase 1: Core Loop (3-4 days)
|
|
1130
|
+
- [ ] Capability base class
|
|
1131
|
+
- [ ] PromptObject implementation
|
|
1132
|
+
- [ ] Loader (frontmatter + body parsing)
|
|
1133
|
+
- [ ] Single LLM adapter (OpenAI)
|
|
1134
|
+
- [ ] Simple REPL (no Charm yet)
|
|
1135
|
+
- [ ] **Demo 1 works**: greeter responds
|
|
1136
|
+
|
|
1137
|
+
## Phase 2: Primitives & Binding (3-4 days)
|
|
1138
|
+
- [ ] Primitive base class
|
|
1139
|
+
- [ ] Built-in primitives: read_file, list_files, write_file
|
|
1140
|
+
- [ ] Registry for all capabilities
|
|
1141
|
+
- [ ] **Demo 2 works**: reader uses primitives, semantic binding visible
|
|
1142
|
+
|
|
1143
|
+
## Phase 3: Multi-Capability (3-4 days)
|
|
1144
|
+
- [ ] Message bus with logging
|
|
1145
|
+
- [ ] PO → PO communication (one PO calling another as capability)
|
|
1146
|
+
- [ ] **Demo 3 works**: coordinator delegates to reader
|
|
1147
|
+
|
|
1148
|
+
## Phase 4: Self-Modification (3-4 days)
|
|
1149
|
+
- [ ] Universal capabilities: ask_human, think, create_capability
|
|
1150
|
+
- [ ] create_capability implementation for POs
|
|
1151
|
+
- [ ] **Demo 4 works**: coordinator creates ruby_debugger
|
|
1152
|
+
|
|
1153
|
+
## Phase 5: Polish & UI (5-7 days)
|
|
1154
|
+
- [ ] Full Charm integration (Bubble Tea app)
|
|
1155
|
+
- [ ] Capability bar component
|
|
1156
|
+
- [ ] Message log component
|
|
1157
|
+
- [ ] Conversation display with streaming
|
|
1158
|
+
- [ ] Human input prompts (Huh?)
|
|
1159
|
+
- [ ] Spinners during LLM calls
|
|
1160
|
+
- [ ] **Demo 5 works**: human-in-the-loop moments feel natural
|
|
1161
|
+
|
|
1162
|
+
## Phase 6: Demo Ready (2-3 days)
|
|
1163
|
+
- [ ] All 5 demos flow smoothly
|
|
1164
|
+
- [ ] Graceful error handling
|
|
1165
|
+
- [ ] Backup video recorded
|
|
1166
|
+
- [ ] Practice run-throughs
|
|
1167
|
+
- [ ] Timing tested (fits in 8 min demo slot)
|
|
1168
|
+
|
|
1169
|
+
---
|
|
1170
|
+
|
|
1171
|
+
# Open Questions
|
|
1172
|
+
|
|
1173
|
+
1. **Should primitives also be definable in files?**
|
|
1174
|
+
- Like `primitives/custom_search.rb`
|
|
1175
|
+
- Environment loads them on startup
|
|
1176
|
+
- More dangerous but more flexible
|
|
1177
|
+
|
|
1178
|
+
2. **How to handle capability loops?**
|
|
1179
|
+
- A calls B calls A calls B...
|
|
1180
|
+
- Depth limit? Token budget? Both?
|
|
1181
|
+
|
|
1182
|
+
3. **Session persistence?**
|
|
1183
|
+
- Currently POs reset each run
|
|
1184
|
+
- Should conversation history persist?
|
|
1185
|
+
- Should created POs persist? (currently yes, written to disk)
|
|
1186
|
+
|
|
1187
|
+
4. **MCP: when to use vs native?**
|
|
1188
|
+
- If there's an MCP server with `read_file`, use it or our native?
|
|
1189
|
+
- Probably: prefer native, MCP for things we don't have
|
|
1190
|
+
|
|
1191
|
+
5. **Stigmergy for v2?**
|
|
1192
|
+
- Environment-level shared state
|
|
1193
|
+
- `mark(key, value)` and `sense(pattern)`
|
|
1194
|
+
- Could enable emergent coordination
|
|
1195
|
+
- Too much for MVP, but worth hinting at
|
|
1196
|
+
|
|
1197
|
+
---
|
|
1198
|
+
|
|
1199
|
+
# Success Criteria
|
|
1200
|
+
|
|
1201
|
+
The demo succeeds if:
|
|
1202
|
+
|
|
1203
|
+
1. **PO = Tool equivalence is visceral**
|
|
1204
|
+
- Audience sees primitives and POs in the same capability bar
|
|
1205
|
+
- Message log shows both being "called" the same way
|
|
1206
|
+
- The difference (interpretation complexity) is obvious
|
|
1207
|
+
|
|
1208
|
+
2. **Semantic binding is VISIBLE**
|
|
1209
|
+
- Message log shows natural language → capability call
|
|
1210
|
+
- "what's in here?" becomes `list_files`
|
|
1211
|
+
- The audience can SEE the interpretation happening
|
|
1212
|
+
|
|
1213
|
+
3. **Multi-PO interaction feels like message passing**
|
|
1214
|
+
- Coordinator talks to reader like sending a message
|
|
1215
|
+
- Reader's internal work is visible in the log
|
|
1216
|
+
- Kay's vision is tangible
|
|
1217
|
+
|
|
1218
|
+
4. **Self-modification is both magical and understandable**
|
|
1219
|
+
- New PO appears in capability bar
|
|
1220
|
+
- The creation is logged
|
|
1221
|
+
- Human approved it (ask_human)
|
|
1222
|
+
- Scary but controlled
|
|
1223
|
+
|
|
1224
|
+
5. **Human stays in the loop**
|
|
1225
|
+
- Confirmation prompts feel natural
|
|
1226
|
+
- POs ask for help when stuck
|
|
1227
|
+
- Control is negotiated, not hardcoded
|
|
1228
|
+
|
|
1229
|
+
6. **The UI is beautiful**
|
|
1230
|
+
- Charm styling makes it polished
|
|
1231
|
+
- Not a janky demo
|
|
1232
|
+
- People want to use this
|