hypervibe 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/LICENSE +7 -0
- data/PLAN.md +1484 -0
- data/VERSION +1 -0
- data/bin/vibe +3 -0
- data/hypervibe.gemspec +27 -0
- data/lib/hypervibe.rb +0 -0
- metadata +103 -0
data/PLAN.md
ADDED
@@ -0,0 +1,1484 @@
|
|
1
|
+
# Hypervibe: Ruby Framework for AI & Vibe-Coders
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
Hypervibe is a Ruby framework where **AI is the primary developer** and humans provide high-level guidance. Built on the principle that LLMs excel at SQL and JSON Schema but struggle with ORM abstractions, Hypervibe embraces explicit, observable, and streaming-first patterns.
|
6
|
+
|
7
|
+
## Core Philosophy
|
8
|
+
|
9
|
+
- **SQL as source of truth** - LLMs write SQL directly, no ORM abstractions
|
10
|
+
- **Tools & Flows** - Declarative orchestration with machine-readable contracts
|
11
|
+
- **Streaming first** - Every operation can stream partial results
|
12
|
+
- **Observable by default** - Event logs, traces, and replay capabilities
|
13
|
+
- **Schema everywhere** - JSON Schema validation on all boundaries
|
14
|
+
- **AI-native testing** - Golden tests, dry-runs, and prompt versioning
|
15
|
+
|
16
|
+
## Installation & Setup
|
17
|
+
|
18
|
+
```bash
|
19
|
+
# Install the gem
|
20
|
+
gem install hypervibe
|
21
|
+
|
22
|
+
# Create a new project
|
23
|
+
vibe new my_app
|
24
|
+
cd my_app
|
25
|
+
|
26
|
+
# Start development server with trace viewer
|
27
|
+
vibe server
|
28
|
+
|
29
|
+
# Generate components
|
30
|
+
vibe generate tool send_notification
|
31
|
+
vibe generate flow user_onboarding
|
32
|
+
vibe generate query users
|
33
|
+
```
|
34
|
+
|
35
|
+
## Core Architecture
|
36
|
+
|
37
|
+
### 1. Data Layer: sqlc-style Repositories
|
38
|
+
|
39
|
+
SQL files define all queries. Hypervibe generates type-safe Ruby methods automatically.
|
40
|
+
|
41
|
+
#### SQL Query Files
|
42
|
+
|
43
|
+
```sql
|
44
|
+
-- queries/users.sql
|
45
|
+
|
46
|
+
-- name: GetUser :one
|
47
|
+
SELECT * FROM users WHERE id = $1;
|
48
|
+
|
49
|
+
-- name: FindByEmail :one
|
50
|
+
SELECT id, email, name, created_at
|
51
|
+
FROM users
|
52
|
+
WHERE email = $1
|
53
|
+
LIMIT 1;
|
54
|
+
|
55
|
+
-- name: ListActiveUsers :many
|
56
|
+
-- cache: 5.minutes
|
57
|
+
SELECT id, name, email
|
58
|
+
FROM users
|
59
|
+
WHERE deleted_at IS NULL
|
60
|
+
ORDER BY created_at DESC
|
61
|
+
LIMIT $1;
|
62
|
+
|
63
|
+
-- name: CreateUser :one
|
64
|
+
INSERT INTO users (email, name, password_hash)
|
65
|
+
VALUES ($1, $2, $3)
|
66
|
+
RETURNING *;
|
67
|
+
|
68
|
+
-- name: UpdateLastLogin :exec
|
69
|
+
UPDATE users
|
70
|
+
SET last_login_at = NOW()
|
71
|
+
WHERE id = $1;
|
72
|
+
|
73
|
+
-- name: CountUserPosts :one
|
74
|
+
SELECT COUNT(*) as post_count
|
75
|
+
FROM posts
|
76
|
+
WHERE user_id = $1 AND published = true;
|
77
|
+
|
78
|
+
-- name: GetUserWithStats :one
|
79
|
+
WITH post_stats AS (
|
80
|
+
SELECT
|
81
|
+
user_id,
|
82
|
+
COUNT(*) as post_count,
|
83
|
+
MAX(created_at) as last_post_at
|
84
|
+
FROM posts
|
85
|
+
WHERE user_id = $1
|
86
|
+
GROUP BY user_id
|
87
|
+
)
|
88
|
+
SELECT
|
89
|
+
u.*,
|
90
|
+
COALESCE(ps.post_count, 0) as post_count,
|
91
|
+
ps.last_post_at
|
92
|
+
FROM users u
|
93
|
+
LEFT JOIN post_stats ps ON ps.user_id = u.id
|
94
|
+
WHERE u.id = $1;
|
95
|
+
```
|
96
|
+
|
97
|
+
#### Auto-Generated Repository Module
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
# Auto-generated from queries/users.sql
|
101
|
+
Repo[:users] do
|
102
|
+
query :get_user,
|
103
|
+
sql: "SELECT * FROM users WHERE id = $1",
|
104
|
+
args: [{id: :uuid}],
|
105
|
+
returns: User
|
106
|
+
|
107
|
+
query :find_by_email,
|
108
|
+
sql: "SELECT id, email, name, created_at FROM users WHERE email = $1 LIMIT 1",
|
109
|
+
args: [{email: :string}],
|
110
|
+
returns: {id: :uuid, email: :string, name: :string, created_at: :timestamp}
|
111
|
+
|
112
|
+
query :list_active_users,
|
113
|
+
sql: "SELECT id, name, email FROM users WHERE deleted_at IS NULL ORDER BY created_at DESC LIMIT $1",
|
114
|
+
args: [{limit: :int}],
|
115
|
+
returns: [{id: :uuid, name: :string, email: :string}],
|
116
|
+
cache: 5.minutes
|
117
|
+
|
118
|
+
cmd :create_user,
|
119
|
+
sql: "INSERT INTO users (email, name, password_hash) VALUES ($1, $2, $3) RETURNING *",
|
120
|
+
args: [{email: :string}, {name: :string}, {password_hash: :string}],
|
121
|
+
returns: User
|
122
|
+
|
123
|
+
cmd :update_last_login,
|
124
|
+
sql: "UPDATE users SET last_login_at = NOW() WHERE id = $1",
|
125
|
+
args: [{id: :uuid}]
|
126
|
+
|
127
|
+
query :count_user_posts,
|
128
|
+
sql: "SELECT COUNT(*) as post_count FROM posts WHERE user_id = $1 AND published = true",
|
129
|
+
args: [{user_id: :uuid}],
|
130
|
+
returns: {post_count: :integer}
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
#### Usage in Code
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
# Simple queries
|
138
|
+
user = Repo[:users].get_user(id: params[:id])
|
139
|
+
user = Repo[:users].find_by_email(email: "alice@example.com")
|
140
|
+
|
141
|
+
# Returns array of hashes
|
142
|
+
active_users = Repo[:users].list_active_users(limit: 10)
|
143
|
+
|
144
|
+
# Commands
|
145
|
+
new_user = Repo[:users].create_user(
|
146
|
+
email: "bob@example.com",
|
147
|
+
name: "Bob",
|
148
|
+
password_hash: BCrypt::Password.create("secret")
|
149
|
+
)
|
150
|
+
|
151
|
+
# Every query result includes metadata
|
152
|
+
result = Repo[:users].get_user(id: 123)
|
153
|
+
result._sql # => "SELECT * FROM users WHERE id = $1"
|
154
|
+
result._timing # => 23.5 (ms)
|
155
|
+
result._explain # => Query plan
|
156
|
+
```
|
157
|
+
|
158
|
+
### 2. Vector & Document Stores
|
159
|
+
|
160
|
+
Native support for modern AI data patterns:
|
161
|
+
|
162
|
+
```sql
|
163
|
+
-- queries/embeddings.sql
|
164
|
+
|
165
|
+
-- name: SearchSimilarDocs :many
|
166
|
+
-- adapter: pgvector
|
167
|
+
SELECT
|
168
|
+
id,
|
169
|
+
content,
|
170
|
+
metadata,
|
171
|
+
1 - (embedding <=> $1) as similarity
|
172
|
+
FROM documents
|
173
|
+
WHERE 1 - (embedding <=> $1) > $2
|
174
|
+
ORDER BY embedding <=> $1
|
175
|
+
LIMIT $3;
|
176
|
+
|
177
|
+
-- name: StoreDocument :one
|
178
|
+
-- adapter: jsonb
|
179
|
+
INSERT INTO documents (content, metadata, embedding)
|
180
|
+
VALUES ($1, $2, $3)
|
181
|
+
RETURNING id;
|
182
|
+
|
183
|
+
-- name: UpdateDocumentMetadata :exec
|
184
|
+
-- adapter: jsonb
|
185
|
+
UPDATE documents
|
186
|
+
SET metadata = metadata || $2
|
187
|
+
WHERE id = $1;
|
188
|
+
```
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
# Vector search
|
192
|
+
similar_docs = Repo[:embeddings].search_similar_docs(
|
193
|
+
embedding: AI.embed("user query"),
|
194
|
+
threshold: 0.7,
|
195
|
+
limit: 10
|
196
|
+
)
|
197
|
+
|
198
|
+
# Document storage with JSONB
|
199
|
+
doc_id = Repo[:embeddings].store_document(
|
200
|
+
content: "Full text content...",
|
201
|
+
metadata: {source: "upload", user_id: 123},
|
202
|
+
embedding: AI.embed("Full text content...")
|
203
|
+
)
|
204
|
+
```
|
205
|
+
|
206
|
+
### 3. Tools: Single Definition, Multiple Interfaces
|
207
|
+
|
208
|
+
Tools are atomic units of side-effects with JSON Schema contracts:
|
209
|
+
|
210
|
+
```ruby
|
211
|
+
class Tools::SendEmail
|
212
|
+
include Hypervibe::Tool
|
213
|
+
|
214
|
+
tool "send_email",
|
215
|
+
desc: "Send a transactional email",
|
216
|
+
examples: [
|
217
|
+
{
|
218
|
+
input: {
|
219
|
+
to: "user@example.com",
|
220
|
+
subject: "Welcome!",
|
221
|
+
body_html: "<p>Hello</p>"
|
222
|
+
},
|
223
|
+
output: {
|
224
|
+
message_id: "msg_123",
|
225
|
+
status: "sent"
|
226
|
+
}
|
227
|
+
}
|
228
|
+
],
|
229
|
+
input: {
|
230
|
+
to: {type: "string", format: "email", required: true},
|
231
|
+
subject: {type: "string", maxLength: 200, required: true},
|
232
|
+
body_html: {type: "string", required: true},
|
233
|
+
reply_to: {type: "string", format: "email"},
|
234
|
+
attachments: {
|
235
|
+
type: "array",
|
236
|
+
items: {
|
237
|
+
type: "object",
|
238
|
+
properties: {
|
239
|
+
filename: {type: "string"},
|
240
|
+
content: {type: "string", format: "base64"}
|
241
|
+
}
|
242
|
+
}
|
243
|
+
}
|
244
|
+
},
|
245
|
+
output: {
|
246
|
+
message_id: {type: "string"},
|
247
|
+
status: {type: "string", enum: ["sent", "queued"]},
|
248
|
+
delivered_at: {type: "string", format: "date-time"}
|
249
|
+
},
|
250
|
+
errors: {
|
251
|
+
invalid_email: "The provided email address is not valid",
|
252
|
+
rate_limited: "Too many emails sent recently",
|
253
|
+
attachment_too_large: "Attachment exceeds 10MB limit"
|
254
|
+
},
|
255
|
+
capabilities: ["email:send"]
|
256
|
+
|
257
|
+
def call(to:, subject:, body_html:, reply_to: nil, attachments: [])
|
258
|
+
validate_rate_limit!(to)
|
259
|
+
validate_attachments!(attachments)
|
260
|
+
|
261
|
+
id = Mailer.deliver(
|
262
|
+
to: to,
|
263
|
+
subject: subject,
|
264
|
+
html: body_html,
|
265
|
+
reply_to: reply_to,
|
266
|
+
attachments: process_attachments(attachments)
|
267
|
+
)
|
268
|
+
|
269
|
+
emit :email_sent, {
|
270
|
+
to: to,
|
271
|
+
message_id: id,
|
272
|
+
timestamp: Time.now
|
273
|
+
}
|
274
|
+
|
275
|
+
{
|
276
|
+
message_id: id,
|
277
|
+
status: "sent",
|
278
|
+
delivered_at: Time.now.iso8601
|
279
|
+
}
|
280
|
+
rescue RateLimitError
|
281
|
+
error! :rate_limited
|
282
|
+
rescue AttachmentError => e
|
283
|
+
error! :attachment_too_large, details: e.message
|
284
|
+
end
|
285
|
+
|
286
|
+
private
|
287
|
+
|
288
|
+
def validate_attachments!(attachments)
|
289
|
+
attachments.each do |att|
|
290
|
+
size = Base64.decode64(att[:content]).bytesize
|
291
|
+
raise AttachmentError if size > 10.megabytes
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
```
|
296
|
+
|
297
|
+
This single tool definition automatically generates:
|
298
|
+
|
299
|
+
- HTTP endpoint: `POST /tools/send_email`
|
300
|
+
- CLI command: `vibe tool send_email --to user@example.com --subject "Test"`
|
301
|
+
- LLM tool spec (OpenAI/Anthropic format)
|
302
|
+
- OpenAPI documentation
|
303
|
+
- Contract tests from examples
|
304
|
+
- GraphQL mutation (optional)
|
305
|
+
|
306
|
+
### 4. Flows: Declarative Orchestration
|
307
|
+
|
308
|
+
Flows compose tools, AI calls, and queries into resumable workflows:
|
309
|
+
|
310
|
+
```ruby
|
311
|
+
flow "onboard_user",
|
312
|
+
budget_cents: 5_00,
|
313
|
+
timeout: 30.seconds,
|
314
|
+
stream: true,
|
315
|
+
capabilities: [
|
316
|
+
"repo:users:write",
|
317
|
+
"repo:profiles:write",
|
318
|
+
"tool:send_email",
|
319
|
+
"tool:schedule_job"
|
320
|
+
] do
|
321
|
+
|
322
|
+
# Step 1: Validate and create user
|
323
|
+
step :user do
|
324
|
+
existing = Repo[:users].find_by_email(email: params[:email])
|
325
|
+
error!(:user_exists) if existing
|
326
|
+
|
327
|
+
Repo[:users].create_user(
|
328
|
+
email: params[:email],
|
329
|
+
name: params[:name],
|
330
|
+
password_hash: hash_password(params[:password])
|
331
|
+
)
|
332
|
+
end
|
333
|
+
|
334
|
+
# Step 2: Generate personalized content with AI
|
335
|
+
ai :welcome_content,
|
336
|
+
model: "gpt-4o-mini",
|
337
|
+
temperature: 0.7,
|
338
|
+
cache: 1.hour,
|
339
|
+
max_tokens: 500,
|
340
|
+
schema: {
|
341
|
+
type: "object",
|
342
|
+
properties: {
|
343
|
+
subject: {type: "string", maxLength: 100},
|
344
|
+
body_html: {type: "string"},
|
345
|
+
onboarding_tips: {
|
346
|
+
type: "array",
|
347
|
+
items: {type: "string"},
|
348
|
+
minItems: 3,
|
349
|
+
maxItems: 5
|
350
|
+
},
|
351
|
+
suggested_connections: {
|
352
|
+
type: "array",
|
353
|
+
items: {
|
354
|
+
type: "object",
|
355
|
+
properties: {
|
356
|
+
user_id: {type: "integer"},
|
357
|
+
reason: {type: "string"}
|
358
|
+
}
|
359
|
+
}
|
360
|
+
}
|
361
|
+
},
|
362
|
+
required: ["subject", "body_html", "onboarding_tips"]
|
363
|
+
} do
|
364
|
+
system """
|
365
|
+
You are a friendly onboarding assistant. Be concise and helpful.
|
366
|
+
The app is a professional networking platform.
|
367
|
+
"""
|
368
|
+
|
369
|
+
user """
|
370
|
+
Create a welcome email for a new user:
|
371
|
+
Name: {{ steps.user.name }}
|
372
|
+
Email: {{ steps.user.email }}
|
373
|
+
Company domain: {{ steps.user.email.split('@').last }}
|
374
|
+
Signed up: {{ steps.user.created_at }}
|
375
|
+
|
376
|
+
Include personalized onboarding tips based on their email domain.
|
377
|
+
Suggest potential connections if you recognize the company.
|
378
|
+
"""
|
379
|
+
end
|
380
|
+
|
381
|
+
# Step 3: Create user profile
|
382
|
+
step :profile do
|
383
|
+
Repo[:profiles].create(
|
384
|
+
user_id: steps.user.id,
|
385
|
+
onboarding_tips: steps.welcome_content["onboarding_tips"],
|
386
|
+
suggested_connections: steps.welcome_content["suggested_connections"],
|
387
|
+
stage: "welcome_sent"
|
388
|
+
)
|
389
|
+
end
|
390
|
+
|
391
|
+
# Step 4: Send welcome email
|
392
|
+
tool :email, Tools::SendEmail,
|
393
|
+
to: params[:email],
|
394
|
+
subject: steps.welcome_content["subject"],
|
395
|
+
body_html: steps.welcome_content["body_html"]
|
396
|
+
|
397
|
+
# Step 5: Schedule follow-up sequence
|
398
|
+
parallel do
|
399
|
+
tool :follow_up_1, Tools::ScheduleJob,
|
400
|
+
job_type: "engagement_email",
|
401
|
+
run_at: 1.day.from_now,
|
402
|
+
params: {user_id: steps.user.id, email_type: "tips"}
|
403
|
+
|
404
|
+
tool :follow_up_2, Tools::ScheduleJob,
|
405
|
+
job_type: "engagement_email",
|
406
|
+
run_at: 3.days.from_now,
|
407
|
+
params: {user_id: steps.user.id, email_type: "connections"}
|
408
|
+
|
409
|
+
tool :follow_up_3, Tools::ScheduleJob,
|
410
|
+
job_type: "engagement_email",
|
411
|
+
run_at: 7.days.from_now,
|
412
|
+
params: {user_id: steps.user.id, email_type: "check_in"}
|
413
|
+
end
|
414
|
+
|
415
|
+
# Completion
|
416
|
+
emit :onboarding_complete, {
|
417
|
+
user_id: steps.user.id,
|
418
|
+
email_sent: steps.email.message_id,
|
419
|
+
follow_ups_scheduled: [
|
420
|
+
steps.follow_up_1.job_id,
|
421
|
+
steps.follow_up_2.job_id,
|
422
|
+
steps.follow_up_3.job_id
|
423
|
+
]
|
424
|
+
}
|
425
|
+
end
|
426
|
+
```
|
427
|
+
|
428
|
+
### 5. Event Log & Replay System
|
429
|
+
|
430
|
+
Every flow execution creates an immutable event log:
|
431
|
+
|
432
|
+
```ruby
|
433
|
+
# Event log structure
|
434
|
+
[
|
435
|
+
{
|
436
|
+
id: "evt_001",
|
437
|
+
type: "flow_started",
|
438
|
+
flow: "onboard_user",
|
439
|
+
run_id: "run_abc123",
|
440
|
+
params: {email: "alice@example.com"},
|
441
|
+
timestamp: "2024-01-15T10:00:00Z",
|
442
|
+
correlation_id: "req_xyz"
|
443
|
+
},
|
444
|
+
{
|
445
|
+
id: "evt_002",
|
446
|
+
type: "step_started",
|
447
|
+
step: "user",
|
448
|
+
run_id: "run_abc123",
|
449
|
+
timestamp: "2024-01-15T10:00:00.100Z"
|
450
|
+
},
|
451
|
+
{
|
452
|
+
id: "evt_003",
|
453
|
+
type: "sql_executed",
|
454
|
+
query: "SELECT id, email FROM users WHERE email = $1",
|
455
|
+
args: ["alice@example.com"],
|
456
|
+
duration_ms: 23,
|
457
|
+
rows_returned: 0,
|
458
|
+
run_id: "run_abc123",
|
459
|
+
timestamp: "2024-01-15T10:00:00.123Z"
|
460
|
+
},
|
461
|
+
{
|
462
|
+
id: "evt_004",
|
463
|
+
type: "sql_executed",
|
464
|
+
query: "INSERT INTO users...",
|
465
|
+
duration_ms: 45,
|
466
|
+
rows_affected: 1,
|
467
|
+
run_id: "run_abc123",
|
468
|
+
timestamp: "2024-01-15T10:00:00.168Z"
|
469
|
+
},
|
470
|
+
{
|
471
|
+
id: "evt_005",
|
472
|
+
type: "step_completed",
|
473
|
+
step: "user",
|
474
|
+
result: {id: 123, email: "alice@example.com"},
|
475
|
+
run_id: "run_abc123",
|
476
|
+
timestamp: "2024-01-15T10:00:00.213Z"
|
477
|
+
},
|
478
|
+
{
|
479
|
+
id: "evt_006",
|
480
|
+
type: "ai_request",
|
481
|
+
model: "gpt-4o-mini",
|
482
|
+
prompt_tokens: 234,
|
483
|
+
temperature: 0.7,
|
484
|
+
run_id: "run_abc123",
|
485
|
+
timestamp: "2024-01-15T10:00:00.250Z"
|
486
|
+
},
|
487
|
+
{
|
488
|
+
id: "evt_007",
|
489
|
+
type: "ai_response",
|
490
|
+
completion_tokens: 156,
|
491
|
+
total_tokens: 390,
|
492
|
+
cost_cents: 0.0234,
|
493
|
+
result: {subject: "Welcome!", body_html: "..."},
|
494
|
+
run_id: "run_abc123",
|
495
|
+
timestamp: "2024-01-15T10:00:01.750Z"
|
496
|
+
},
|
497
|
+
{
|
498
|
+
id: "evt_008",
|
499
|
+
type: "tool_called",
|
500
|
+
tool: "send_email",
|
501
|
+
input: {to: "alice@example.com", subject: "Welcome!"},
|
502
|
+
run_id: "run_abc123",
|
503
|
+
timestamp: "2024-01-15T10:00:01.800Z"
|
504
|
+
},
|
505
|
+
{
|
506
|
+
id: "evt_009",
|
507
|
+
type: "tool_completed",
|
508
|
+
tool: "send_email",
|
509
|
+
output: {message_id: "msg_789", status: "sent"},
|
510
|
+
duration_ms: 234,
|
511
|
+
run_id: "run_abc123",
|
512
|
+
timestamp: "2024-01-15T10:00:02.034Z"
|
513
|
+
},
|
514
|
+
{
|
515
|
+
id: "evt_010",
|
516
|
+
type: "flow_completed",
|
517
|
+
flow: "onboard_user",
|
518
|
+
run_id: "run_abc123",
|
519
|
+
duration_ms: 2034,
|
520
|
+
total_cost_cents: 0.0234,
|
521
|
+
timestamp: "2024-01-15T10:00:02.034Z"
|
522
|
+
}
|
523
|
+
]
|
524
|
+
```
|
525
|
+
|
526
|
+
#### Replay & Time Travel
|
527
|
+
|
528
|
+
```ruby
|
529
|
+
# Replay a flow with fixed responses (for testing)
|
530
|
+
replay = Flow.replay("onboard_user", run_id: "run_abc123") do
|
531
|
+
mock_ai :welcome_content, returns: {
|
532
|
+
subject: "Welcome to the platform!",
|
533
|
+
body_html: "<p>We're excited to have you...</p>",
|
534
|
+
onboarding_tips: ["Complete your profile", "Connect with colleagues", "Join groups"]
|
535
|
+
}
|
536
|
+
mock_tool :email, returns: {message_id: "msg_test", status: "sent"}
|
537
|
+
end
|
538
|
+
|
539
|
+
# Inspect any historical run
|
540
|
+
run = Flow.runs.find("run_abc123")
|
541
|
+
run.trace # Full execution trace
|
542
|
+
run.cost # Token costs: $0.0234
|
543
|
+
run.duration # 2034ms
|
544
|
+
run.events # All events
|
545
|
+
run.replay(until: :email) # Partial replay up to email step
|
546
|
+
|
547
|
+
# Time travel debugging
|
548
|
+
Flow.debug("run_abc123") do |debugger|
|
549
|
+
debugger.step_forward # Execute next event
|
550
|
+
debugger.inspect(:user) # See state at this point
|
551
|
+
debugger.rewind_to(:ai_request) # Go back in time
|
552
|
+
debugger.modify_and_continue( # What-if analysis
|
553
|
+
ai_response: {subject: "Different subject"}
|
554
|
+
)
|
555
|
+
end
|
556
|
+
```
|
557
|
+
|
558
|
+
### 6. Prompts as Code
|
559
|
+
|
560
|
+
Prompts are versioned, tested, and compiled:
|
561
|
+
|
562
|
+
```ruby
|
563
|
+
# prompts/summarizer.prompt.rb
|
564
|
+
prompt "summarize_document",
|
565
|
+
model: "gpt-4o",
|
566
|
+
temperature: 0.3,
|
567
|
+
version: "1.2.0",
|
568
|
+
changelog: {
|
569
|
+
"1.2.0" => "Added sentiment analysis",
|
570
|
+
"1.1.0" => "Improved key point extraction",
|
571
|
+
"1.0.0" => "Initial version"
|
572
|
+
},
|
573
|
+
schema: {
|
574
|
+
type: "object",
|
575
|
+
properties: {
|
576
|
+
summary: {type: "string", minLength: 100, maxLength: 500},
|
577
|
+
key_points: {
|
578
|
+
type: "array",
|
579
|
+
items: {type: "string"},
|
580
|
+
minItems: 3,
|
581
|
+
maxItems: 7
|
582
|
+
},
|
583
|
+
sentiment: {type: "string", enum: ["positive", "neutral", "negative"]},
|
584
|
+
confidence: {type: "number", minimum: 0, maximum: 1}
|
585
|
+
},
|
586
|
+
required: ["summary", "key_points", "sentiment", "confidence"]
|
587
|
+
} do
|
588
|
+
|
589
|
+
system """
|
590
|
+
You are a document summarizer. Be concise and extract key information.
|
591
|
+
Focus on actionable insights and main arguments.
|
592
|
+
Always assess the overall sentiment and your confidence level.
|
593
|
+
"""
|
594
|
+
|
595
|
+
user """
|
596
|
+
Summarize this document:
|
597
|
+
|
598
|
+
{{ document }}
|
599
|
+
|
600
|
+
Document metadata:
|
601
|
+
- Length: {{ document.length }} characters
|
602
|
+
- Source: {{ metadata.source }}
|
603
|
+
- Date: {{ metadata.date }}
|
604
|
+
"""
|
605
|
+
|
606
|
+
# Golden examples for testing
|
607
|
+
example "earnings_report" do
|
608
|
+
input document: "Q3 2024 Earnings Report: Revenue increased 23% YoY to $4.5B...",
|
609
|
+
metadata: {source: "SEC Filing", date: "2024-10-15"}
|
610
|
+
|
611
|
+
output {
|
612
|
+
summary: "Q3 earnings exceeded expectations with 23% YoY revenue growth...",
|
613
|
+
key_points: [
|
614
|
+
"Revenue up 23% to $4.5B",
|
615
|
+
"New product line contributed 30% of growth",
|
616
|
+
"Guidance raised for Q4"
|
617
|
+
],
|
618
|
+
sentiment: "positive",
|
619
|
+
confidence: 0.95
|
620
|
+
}
|
621
|
+
end
|
622
|
+
|
623
|
+
example "incident_report" do
|
624
|
+
input document: "Service Outage Report: At 14:30 UTC, our primary database...",
|
625
|
+
metadata: {source: "Internal", date: "2024-10-20"}
|
626
|
+
|
627
|
+
output {
|
628
|
+
summary: "Database outage affected 15% of users for 45 minutes...",
|
629
|
+
key_points: [
|
630
|
+
"45-minute outage on October 20",
|
631
|
+
"15% of users affected",
|
632
|
+
"Root cause: connection pool exhaustion"
|
633
|
+
],
|
634
|
+
sentiment: "negative",
|
635
|
+
confidence: 0.90
|
636
|
+
}
|
637
|
+
end
|
638
|
+
|
639
|
+
# Validation rules
|
640
|
+
validate do |output|
|
641
|
+
# Summary should mention key numbers from input
|
642
|
+
if input[:document].include?("23%") && !output[:summary].include?("23")
|
643
|
+
error "Summary should include key metrics"
|
644
|
+
end
|
645
|
+
|
646
|
+
# Confidence should be lower for ambiguous content
|
647
|
+
if output[:sentiment] == "neutral" && output[:confidence] > 0.8
|
648
|
+
warning "High confidence unusual for neutral sentiment"
|
649
|
+
end
|
650
|
+
end
|
651
|
+
end
|
652
|
+
```
|
653
|
+
|
654
|
+
Usage:
|
655
|
+
|
656
|
+
```ruby
|
657
|
+
# Use the prompt
|
658
|
+
result = Prompts.summarize_document(
|
659
|
+
document: File.read("report.pdf"),
|
660
|
+
metadata: {source: "uploaded", date: Date.today}
|
661
|
+
)
|
662
|
+
|
663
|
+
# Access versioned prompts
|
664
|
+
result_v1 = Prompts.summarize_document.v("1.0.0").call(document: "...")
|
665
|
+
|
666
|
+
# Test prompts
|
667
|
+
Prompts.test("summarize_document") do
|
668
|
+
run_golden_examples # Runs all defined examples
|
669
|
+
run_validation_suite # Checks edge cases
|
670
|
+
run_regression_tests # Ensures backwards compatibility
|
671
|
+
end
|
672
|
+
```
|
673
|
+
|
674
|
+
### 7. Streaming & Real-time Updates
|
675
|
+
|
676
|
+
All flows stream by default via Server-Sent Events (SSE):
|
677
|
+
|
678
|
+
```ruby
|
679
|
+
# HTTP endpoint automatically streams
|
680
|
+
GET /flows/onboard_user/run?email=user@example.com
|
681
|
+
|
682
|
+
# Response stream:
|
683
|
+
event: flow_started
|
684
|
+
data: {"flow": "onboard_user", "run_id": "run_abc123"}
|
685
|
+
|
686
|
+
event: step_completed
|
687
|
+
data: {"step": "user", "result": {"id": 123, "email": "user@example.com"}}
|
688
|
+
|
689
|
+
event: ai_token
|
690
|
+
data: {"content": "Welcome"}
|
691
|
+
|
692
|
+
event: ai_token
|
693
|
+
data: {"content": " to"}
|
694
|
+
|
695
|
+
event: ai_token
|
696
|
+
data: {"content": " our platform!"}
|
697
|
+
|
698
|
+
event: step_completed
|
699
|
+
data: {"step": "welcome_content", "tokens_used": 156, "cost_cents": 0.0234}
|
700
|
+
|
701
|
+
event: progress
|
702
|
+
data: {"percent": 75, "message": "Sending email..."}
|
703
|
+
|
704
|
+
event: tool_completed
|
705
|
+
data: {"tool": "send_email", "result": {"message_id": "msg_123"}}
|
706
|
+
|
707
|
+
event: flow_completed
|
708
|
+
data: {"run_id": "run_abc123", "duration_ms": 2341, "cost_cents": 0.0234}
|
709
|
+
```
|
710
|
+
|
711
|
+
Ruby client with streaming:
|
712
|
+
|
713
|
+
```ruby
|
714
|
+
Flow.run("onboard_user", email: "test@example.com") do |event|
|
715
|
+
case event.type
|
716
|
+
when :ai_token
|
717
|
+
print event.data[:content] # Stream AI response char by char
|
718
|
+
when :step_completed
|
719
|
+
puts "\n✓ #{event.step} completed"
|
720
|
+
when :progress
|
721
|
+
update_progress_bar(event.data[:percent])
|
722
|
+
when :error
|
723
|
+
handle_error(event.data)
|
724
|
+
end
|
725
|
+
end
|
726
|
+
```
|
727
|
+
|
728
|
+
JavaScript client:
|
729
|
+
|
730
|
+
```javascript
|
731
|
+
const events = new EventSource(
|
732
|
+
"/flows/onboard_user/run?email=test@example.com",
|
733
|
+
);
|
734
|
+
|
735
|
+
events.addEventListener("ai_token", (e) => {
|
736
|
+
const data = JSON.parse(e.data);
|
737
|
+
document.getElementById("output").innerHTML += data.content;
|
738
|
+
});
|
739
|
+
|
740
|
+
events.addEventListener("flow_completed", (e) => {
|
741
|
+
const data = JSON.parse(e.data);
|
742
|
+
console.log(
|
743
|
+
`Completed in ${data.duration_ms}ms, cost: $${data.cost_cents / 100}`,
|
744
|
+
);
|
745
|
+
events.close();
|
746
|
+
});
|
747
|
+
```
|
748
|
+
|
749
|
+
### 8. Development Tools
|
750
|
+
|
751
|
+
#### Interactive Console
|
752
|
+
|
753
|
+
```ruby
|
754
|
+
$ vibe console
|
755
|
+
|
756
|
+
> experiment do
|
757
|
+
# Test queries with immediate feedback
|
758
|
+
user = Repo[:users].find_by_email("test@example.com")
|
759
|
+
see_sql # Shows: SELECT id, email, name FROM users WHERE email = $1
|
760
|
+
see_explain # Shows query plan
|
761
|
+
|
762
|
+
# Test AI generations
|
763
|
+
draft = AI.generate("Write a haiku about #{user.name}")
|
764
|
+
show_tokens # Token count: 45 prompt, 17 completion
|
765
|
+
show_cost # Cost: $0.0003
|
766
|
+
|
767
|
+
# Compare variations
|
768
|
+
test_with temperature: 0.3
|
769
|
+
test_with temperature: 0.9
|
770
|
+
compare_results # Shows diff between outputs
|
771
|
+
|
772
|
+
# Modify and retry
|
773
|
+
undo_last
|
774
|
+
retry_with model: "gpt-4o"
|
775
|
+
end
|
776
|
+
|
777
|
+
> # Direct repository access
|
778
|
+
> Repo[:users].get_user(id: 123)._timing
|
779
|
+
=> 12.3 # milliseconds
|
780
|
+
|
781
|
+
> # Test tools in isolation
|
782
|
+
> Tools::SendEmail.dry_run(to: "test@example.com", subject: "Test")
|
783
|
+
=> {message_id: "dry_run_msg_123", status: "sent", dry_run: true}
|
784
|
+
```
|
785
|
+
|
786
|
+
#### Web-based Trace Viewer
|
787
|
+
|
788
|
+
```ruby
|
789
|
+
# Built-in web UI at http://localhost:3000/__traces__
|
790
|
+
# Features:
|
791
|
+
# - Flow execution DAG visualization
|
792
|
+
# - Step-by-step replay
|
793
|
+
# - SQL queries with EXPLAIN ANALYZE
|
794
|
+
# - AI prompts/responses with token counts
|
795
|
+
# - Tool inputs/outputs
|
796
|
+
# - Waterfall timing diagram
|
797
|
+
# - Cost breakdown by step
|
798
|
+
# - Event log browser
|
799
|
+
# - Performance profiling
|
800
|
+
```
|
801
|
+
|
802
|
+
#### Golden Tests
|
803
|
+
|
804
|
+
```ruby
|
805
|
+
# test/flows/onboard_user_test.rb
|
806
|
+
describe Flow["onboard_user"] do
|
807
|
+
test "successful onboarding" do
|
808
|
+
# Use recorded AI responses for deterministic tests
|
809
|
+
with_golden_fixtures do
|
810
|
+
result = run_flow(
|
811
|
+
email: "newuser@example.com",
|
812
|
+
name: "Alice",
|
813
|
+
password: "secure123"
|
814
|
+
)
|
815
|
+
|
816
|
+
# Assert on each step
|
817
|
+
assert_step :user do |user|
|
818
|
+
assert_equal "Alice", user.name
|
819
|
+
assert_equal "newuser@example.com", user.email
|
820
|
+
end
|
821
|
+
|
822
|
+
assert_ai :welcome_content do |prompt, response|
|
823
|
+
assert_includes prompt, "Alice"
|
824
|
+
assert response["onboarding_tips"].length >= 3
|
825
|
+
end
|
826
|
+
|
827
|
+
assert_tool :email do |input, output|
|
828
|
+
assert_equal "newuser@example.com", input[:to]
|
829
|
+
assert output[:message_id].present?
|
830
|
+
end
|
831
|
+
|
832
|
+
assert_event :onboarding_complete
|
833
|
+
assert_total_cost_under 0.10 # $0.10
|
834
|
+
assert_duration_under 3000 # 3 seconds
|
835
|
+
end
|
836
|
+
end
|
837
|
+
|
838
|
+
test "handles existing user" do
|
839
|
+
create_user(email: "existing@example.com")
|
840
|
+
|
841
|
+
assert_flow_error :user_exists do
|
842
|
+
run_flow(email: "existing@example.com")
|
843
|
+
end
|
844
|
+
end
|
845
|
+
end
|
846
|
+
```
|
847
|
+
|
848
|
+
#### Contract Testing
|
849
|
+
|
850
|
+
```ruby
|
851
|
+
# Automatically generated from tool definitions
|
852
|
+
describe Tools::SendEmail do
|
853
|
+
test_contract do
|
854
|
+
# Tests each example defined in the tool
|
855
|
+
example "welcome_email" do
|
856
|
+
assert_input_valid
|
857
|
+
assert_output_matches_schema
|
858
|
+
assert_idempotent
|
859
|
+
end
|
860
|
+
|
861
|
+
# Property-based testing
|
862
|
+
property "handles all valid emails" do
|
863
|
+
email = generate(:email)
|
864
|
+
result = call(to: email, subject: "Test", body_html: "<p>Test</p>")
|
865
|
+
assert result[:message_id].present?
|
866
|
+
end
|
867
|
+
end
|
868
|
+
end
|
869
|
+
```
|
870
|
+
|
871
|
+
### 9. Security & Governance
|
872
|
+
|
873
|
+
#### Capability-Based Security
|
874
|
+
|
875
|
+
```ruby
|
876
|
+
flow "process_payment",
|
877
|
+
capabilities: [
|
878
|
+
"repo:orders:read",
|
879
|
+
"repo:payments:write",
|
880
|
+
"tool:charge_card:max_amount:10000",
|
881
|
+
"tool:send_email:domain:receipts@myapp.com",
|
882
|
+
"ai:gpt-4o-mini:max_tokens:500"
|
883
|
+
] do
|
884
|
+
|
885
|
+
# Runtime enforces capability boundaries
|
886
|
+
step :order do
|
887
|
+
Repo[:orders].find(id: params[:order_id]) # ✓ Allowed (read)
|
888
|
+
# Repo[:orders].update(...) would fail - no write capability
|
889
|
+
end
|
890
|
+
|
891
|
+
tool :charge, Tools::ChargeCard,
|
892
|
+
amount: steps.order.amount # Runtime validates against max_amount
|
893
|
+
|
894
|
+
# This would fail - domain not in capabilities
|
895
|
+
# tool :email, Tools::SendEmail, to: "admin@evil.com"
|
896
|
+
end
|
897
|
+
|
898
|
+
# Capabilities can be delegated
|
899
|
+
sub_flow "send_receipt",
|
900
|
+
inherit_capabilities: ["tool:send_email:domain:receipts@myapp.com"] do
|
901
|
+
# Can only send to receipts@myapp.com
|
902
|
+
end
|
903
|
+
```
|
904
|
+
|
905
|
+
#### Policy Engine
|
906
|
+
|
907
|
+
```yaml
|
908
|
+
# config/policies.yml
|
909
|
+
policies:
|
910
|
+
flows:
|
911
|
+
- pattern: "process_payment"
|
912
|
+
rules:
|
913
|
+
max_amount_cents: 100000
|
914
|
+
require_human_approval: "amount_cents > 50000"
|
915
|
+
rate_limit: "100/hour"
|
916
|
+
require_2fa: true
|
917
|
+
|
918
|
+
- pattern: "delete_*"
|
919
|
+
rules:
|
920
|
+
require_human_approval: true
|
921
|
+
audit_log: enhanced
|
922
|
+
|
923
|
+
tools:
|
924
|
+
- name: "send_email"
|
925
|
+
rules:
|
926
|
+
forbidden_domains: ["competitor.com", "spam.net"]
|
927
|
+
rate_limit: "10/minute per recipient"
|
928
|
+
require_spf_check: true
|
929
|
+
|
930
|
+
- name: "charge_card"
|
931
|
+
rules:
|
932
|
+
test_mode: "Rails.env.development?"
|
933
|
+
fraud_check: "amount > 1000"
|
934
|
+
|
935
|
+
ai:
|
936
|
+
- model: "gpt-4o"
|
937
|
+
rules:
|
938
|
+
max_tokens: 2000
|
939
|
+
require_approval: "tokens > 1000"
|
940
|
+
log_prompts: true
|
941
|
+
|
942
|
+
- model: "gpt-4o-mini"
|
943
|
+
rules:
|
944
|
+
max_tokens: 500
|
945
|
+
temperature_max: 0.8
|
946
|
+
```
|
947
|
+
|
948
|
+
#### Audit Logging
|
949
|
+
|
950
|
+
```ruby
|
951
|
+
# Every action is logged with full context
|
952
|
+
AuditLog.recent
|
953
|
+
# =>
|
954
|
+
# [
|
955
|
+
# {
|
956
|
+
# action: "flow.executed",
|
957
|
+
# flow: "process_payment",
|
958
|
+
# user_id: 123,
|
959
|
+
# run_id: "run_xyz",
|
960
|
+
# capabilities_used: ["repo:payments:write", "tool:charge_card"],
|
961
|
+
# cost_cents: 0.0234,
|
962
|
+
# ip: "192.168.1.1",
|
963
|
+
# timestamp: "2024-01-15T10:00:00Z"
|
964
|
+
# }
|
965
|
+
# ]
|
966
|
+
```
|
967
|
+
|
968
|
+
### 10. Deployment & Production
|
969
|
+
|
970
|
+
#### Configuration
|
971
|
+
|
972
|
+
```ruby
|
973
|
+
# config/hypervibe.rb
|
974
|
+
Hypervibe.configure do |config|
|
975
|
+
# Database pools
|
976
|
+
config.database_url = ENV["DATABASE_URL"]
|
977
|
+
config.read_replica_urls = [
|
978
|
+
ENV["READ_REPLICA_1"],
|
979
|
+
ENV["READ_REPLICA_2"]
|
980
|
+
]
|
981
|
+
|
982
|
+
# Redis for caching & queues
|
983
|
+
config.redis_url = ENV["REDIS_URL"]
|
984
|
+
|
985
|
+
# AI Models
|
986
|
+
config.ai_providers = {
|
987
|
+
openai: {api_key: ENV["OPENAI_API_KEY"]},
|
988
|
+
anthropic: {api_key: ENV["ANTHROPIC_API_KEY"]},
|
989
|
+
local: {url: "http://localhost:11434"} # Ollama
|
990
|
+
}
|
991
|
+
|
992
|
+
# Observability
|
993
|
+
config.telemetry = {
|
994
|
+
provider: :datadog,
|
995
|
+
api_key: ENV["DD_API_KEY"],
|
996
|
+
service_name: "hypervibe-app"
|
997
|
+
}
|
998
|
+
|
999
|
+
# Storage
|
1000
|
+
config.event_store = :postgres # or :kafka, :eventstore
|
1001
|
+
config.file_storage = :s3 # or :disk, :gcs
|
1002
|
+
end
|
1003
|
+
```
|
1004
|
+
|
1005
|
+
#### Deployment Commands
|
1006
|
+
|
1007
|
+
```bash
|
1008
|
+
# Database setup
|
1009
|
+
vibe db:create
|
1010
|
+
vibe db:migrate
|
1011
|
+
vibe db:seed
|
1012
|
+
|
1013
|
+
# Generate TypeScript types from schemas
|
1014
|
+
vibe generate:types
|
1015
|
+
|
1016
|
+
# Run tests
|
1017
|
+
vibe test
|
1018
|
+
vibe test:golden # Run golden prompt tests
|
1019
|
+
vibe test:contracts # Test tool contracts
|
1020
|
+
|
1021
|
+
# Production deployment
|
1022
|
+
vibe deploy --environment production
|
1023
|
+
|
1024
|
+
# Scale workers
|
1025
|
+
vibe scale flow_workers=10
|
1026
|
+
vibe scale tool_workers=5
|
1027
|
+
|
1028
|
+
# Monitor
|
1029
|
+
vibe logs --tail
|
1030
|
+
vibe metrics
|
1031
|
+
vibe traces --flow onboard_user
|
1032
|
+
```
|
1033
|
+
|
1034
|
+
### 11. Migration from Rails/Sinatra
|
1035
|
+
|
1036
|
+
Hypervibe can be gradually adopted in existing apps:
|
1037
|
+
|
1038
|
+
```ruby
|
1039
|
+
# In Rails app - mount Hypervibe
|
1040
|
+
# config/routes.rb
|
1041
|
+
Rails.application.routes.draw do
|
1042
|
+
mount Hypervibe::Web, at: "/flows"
|
1043
|
+
|
1044
|
+
# Existing routes still work
|
1045
|
+
resources :users
|
1046
|
+
resources :posts
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
# Gradually migrate controllers to flows
|
1050
|
+
class UsersController < ApplicationController
|
1051
|
+
def create
|
1052
|
+
# Old code still works
|
1053
|
+
@user = User.create!(user_params)
|
1054
|
+
|
1055
|
+
# But trigger flows for new functionality
|
1056
|
+
Flow.run_async("onboard_user",
|
1057
|
+
email: @user.email,
|
1058
|
+
name: @user.name
|
1059
|
+
)
|
1060
|
+
|
1061
|
+
redirect_to @user
|
1062
|
+
end
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
# Share database connections
|
1066
|
+
Hypervibe.configure do |config|
|
1067
|
+
config.database = ActiveRecord::Base.connection_pool
|
1068
|
+
end
|
1069
|
+
|
1070
|
+
# Reuse existing models if needed (read-only)
|
1071
|
+
class User < ActiveRecord::Base
|
1072
|
+
# Existing AR model
|
1073
|
+
end
|
1074
|
+
|
1075
|
+
# But write through repositories
|
1076
|
+
Repo[:users].create_user(...) # Uses SQL directly
|
1077
|
+
```
|
1078
|
+
|
1079
|
+
### 12. Project Structure
|
1080
|
+
|
1081
|
+
```
|
1082
|
+
my_app/
|
1083
|
+
├── app/
|
1084
|
+
│ ├── queries/ # SQL files (source of truth)
|
1085
|
+
│ │ ├── users.sql
|
1086
|
+
│ │ ├── posts.sql
|
1087
|
+
│ │ ├── embeddings.sql
|
1088
|
+
│ │ └── analytics.sql
|
1089
|
+
│ ├── tools/ # Reusable tools with contracts
|
1090
|
+
│ │ ├── send_email.rb
|
1091
|
+
│ │ ├── charge_card.rb
|
1092
|
+
│ │ ├── generate_pdf.rb
|
1093
|
+
│ │ └── slack_notifier.rb
|
1094
|
+
│ ├── flows/ # Business workflows
|
1095
|
+
│ │ ├── onboard_user.rb
|
1096
|
+
│ │ ├── process_order.rb
|
1097
|
+
│ │ ├── weekly_digest.rb
|
1098
|
+
│ │ └── data_pipeline.rb
|
1099
|
+
│ ├── prompts/ # Versioned prompt templates
|
1100
|
+
│ │ ├── summarizer.prompt.rb
|
1101
|
+
│ │ ├── email_writer.prompt.rb
|
1102
|
+
│ │ └── code_reviewer.prompt.rb
|
1103
|
+
│ └── projections/ # Event projections for read models
|
1104
|
+
│ ├── user_stats.rb
|
1105
|
+
│ └── revenue_dashboard.rb
|
1106
|
+
│
|
1107
|
+
├── config/
|
1108
|
+
│ ├── hypervibe.rb # Main configuration
|
1109
|
+
│ ├── policies.yml # Security policies
|
1110
|
+
│ └── database.yml # Database configuration
|
1111
|
+
│
|
1112
|
+
├── generated/ # Auto-generated (git-ignored)
|
1113
|
+
│ ├── repos/ # Type-safe query modules
|
1114
|
+
│ ├── openapi.json # API documentation
|
1115
|
+
│ ├── tool_specs.json # LLM tool definitions
|
1116
|
+
│ └── types.ts # TypeScript definitions
|
1117
|
+
│
|
1118
|
+
├── test/
|
1119
|
+
│ ├── flows/ # Flow tests with golden examples
|
1120
|
+
│ ├── tools/ # Tool contract tests
|
1121
|
+
│ ├── prompts/ # Prompt regression tests
|
1122
|
+
│ └── fixtures/ # Recorded AI responses
|
1123
|
+
│
|
1124
|
+
├── web/ # Optional web UI
|
1125
|
+
│ ├── traces/ # Trace viewer
|
1126
|
+
│ └── dashboard/ # Metrics dashboard
|
1127
|
+
│
|
1128
|
+
└── Vibefile # Project configuration
|
1129
|
+
```
|
1130
|
+
|
1131
|
+
### 13. Example: Complete Feature Implementation
|
1132
|
+
|
1133
|
+
Here's how you'd implement a complete feature in Hypervibe:
|
1134
|
+
|
1135
|
+
```sql
|
1136
|
+
-- queries/articles.sql
|
1137
|
+
|
1138
|
+
-- name: CreateArticle :one
|
1139
|
+
INSERT INTO articles (author_id, title, content, status)
|
1140
|
+
VALUES ($1, $2, $3, 'draft')
|
1141
|
+
RETURNING *;
|
1142
|
+
|
1143
|
+
-- name: PublishArticle :one
|
1144
|
+
UPDATE articles
|
1145
|
+
SET status = 'published', published_at = NOW()
|
1146
|
+
WHERE id = $1
|
1147
|
+
RETURNING *;
|
1148
|
+
|
1149
|
+
-- name: GetArticleWithAuthor :one
|
1150
|
+
SELECT
|
1151
|
+
a.*,
|
1152
|
+
u.name as author_name,
|
1153
|
+
u.email as author_email
|
1154
|
+
FROM articles a
|
1155
|
+
JOIN users u ON u.id = a.author_id
|
1156
|
+
WHERE a.id = $1;
|
1157
|
+
```
|
1158
|
+
|
1159
|
+
```ruby
|
1160
|
+
# tools/moderate_content.rb
|
1161
|
+
class Tools::ModerateContent
|
1162
|
+
include Hypervibe::Tool
|
1163
|
+
|
1164
|
+
tool "moderate_content",
|
1165
|
+
desc: "Check content for policy violations",
|
1166
|
+
input: {
|
1167
|
+
content: {type: "string", required: true},
|
1168
|
+
content_type: {type: "string", enum: ["article", "comment"]}
|
1169
|
+
},
|
1170
|
+
output: {
|
1171
|
+
safe: {type: "boolean"},
|
1172
|
+
issues: {type: "array", items: {type: "string"}},
|
1173
|
+
confidence: {type: "number"}
|
1174
|
+
}
|
1175
|
+
|
1176
|
+
def call(content:, content_type:)
|
1177
|
+
result = AI.moderate(content)
|
1178
|
+
|
1179
|
+
{
|
1180
|
+
safe: result[:safe],
|
1181
|
+
issues: result[:issues],
|
1182
|
+
confidence: result[:confidence]
|
1183
|
+
}
|
1184
|
+
end
|
1185
|
+
end
|
1186
|
+
```
|
1187
|
+
|
1188
|
+
```ruby
|
1189
|
+
# flows/publish_article.rb
|
1190
|
+
flow "publish_article",
|
1191
|
+
stream: true,
|
1192
|
+
timeout: 30.seconds do
|
1193
|
+
|
1194
|
+
step :article do
|
1195
|
+
Repo[:articles].get_article_with_author(id: params[:article_id])
|
1196
|
+
end
|
1197
|
+
|
1198
|
+
guard "author must match" do
|
1199
|
+
steps.article.author_id == params[:user_id]
|
1200
|
+
end
|
1201
|
+
|
1202
|
+
tool :moderation, Tools::ModerateContent,
|
1203
|
+
content: steps.article.content,
|
1204
|
+
content_type: "article"
|
1205
|
+
|
1206
|
+
guard "content must be safe" do
|
1207
|
+
steps.moderation.safe
|
1208
|
+
end
|
1209
|
+
|
1210
|
+
ai :improvements,
|
1211
|
+
model: "gpt-4o-mini",
|
1212
|
+
optional: true, # Don't fail if AI is down
|
1213
|
+
schema: {
|
1214
|
+
type: "object",
|
1215
|
+
properties: {
|
1216
|
+
seo_title: {type: "string", maxLength: 60},
|
1217
|
+
meta_description: {type: "string", maxLength: 160},
|
1218
|
+
tags: {type: "array", items: {type: "string"}}
|
1219
|
+
}
|
1220
|
+
} do
|
1221
|
+
system "You are an SEO expert."
|
1222
|
+
user "Generate SEO metadata for: {{ steps.article.title }}"
|
1223
|
+
end
|
1224
|
+
|
1225
|
+
step :publish do
|
1226
|
+
article = Repo[:articles].publish_article(id: params[:article_id])
|
1227
|
+
|
1228
|
+
if steps.improvements
|
1229
|
+
Repo[:articles].update_metadata(
|
1230
|
+
id: article.id,
|
1231
|
+
seo_title: steps.improvements["seo_title"],
|
1232
|
+
meta_description: steps.improvements["meta_description"],
|
1233
|
+
tags: steps.improvements["tags"]
|
1234
|
+
)
|
1235
|
+
end
|
1236
|
+
|
1237
|
+
article
|
1238
|
+
end
|
1239
|
+
|
1240
|
+
parallel do
|
1241
|
+
tool :notify_followers, Tools::NotifyFollowers,
|
1242
|
+
author_id: steps.article.author_id,
|
1243
|
+
article_id: steps.article.id
|
1244
|
+
|
1245
|
+
tool :index_search, Tools::IndexForSearch,
|
1246
|
+
type: "article",
|
1247
|
+
id: steps.article.id,
|
1248
|
+
content: steps.article.content
|
1249
|
+
|
1250
|
+
tool :social_share, Tools::ScheduleSocialPosts,
|
1251
|
+
article_id: steps.article.id,
|
1252
|
+
platforms: ["twitter", "linkedin"]
|
1253
|
+
end
|
1254
|
+
|
1255
|
+
emit :article_published, {
|
1256
|
+
article_id: steps.article.id,
|
1257
|
+
author_id: steps.article.author_id,
|
1258
|
+
published_at: steps.publish.published_at
|
1259
|
+
}
|
1260
|
+
end
|
1261
|
+
```
|
1262
|
+
|
1263
|
+
### 14. Advanced Features
|
1264
|
+
|
1265
|
+
#### Sub-flows and Composition
|
1266
|
+
|
1267
|
+
```ruby
|
1268
|
+
flow "process_batch",
|
1269
|
+
concurrency: 10 do
|
1270
|
+
|
1271
|
+
step :items do
|
1272
|
+
Repo[:queue].get_pending_items(limit: 100)
|
1273
|
+
end
|
1274
|
+
|
1275
|
+
# Process items in parallel with sub-flows
|
1276
|
+
map :processed, over: steps.items do |item|
|
1277
|
+
sub_flow "process_item",
|
1278
|
+
item_id: item.id,
|
1279
|
+
inherit_capabilities: true
|
1280
|
+
end
|
1281
|
+
|
1282
|
+
# Aggregate results
|
1283
|
+
reduce :summary do
|
1284
|
+
{
|
1285
|
+
total: steps.processed.count,
|
1286
|
+
successful: steps.processed.count(&:success?),
|
1287
|
+
failed: steps.processed.count(&:failed?),
|
1288
|
+
total_cost: steps.processed.sum(&:cost)
|
1289
|
+
}
|
1290
|
+
end
|
1291
|
+
|
1292
|
+
emit :batch_complete, steps.summary
|
1293
|
+
end
|
1294
|
+
```
|
1295
|
+
|
1296
|
+
#### Human-in-the-Loop
|
1297
|
+
|
1298
|
+
```ruby
|
1299
|
+
flow "content_review" do
|
1300
|
+
ai :draft,
|
1301
|
+
model: "gpt-4o" do
|
1302
|
+
system "Write an article about AI safety"
|
1303
|
+
end
|
1304
|
+
|
1305
|
+
# Pause for human review
|
1306
|
+
checkpoint :human_review,
|
1307
|
+
timeout: 24.hours,
|
1308
|
+
notify: ["editor@example.com"] do
|
1309
|
+
|
1310
|
+
present steps.draft
|
1311
|
+
|
1312
|
+
actions [
|
1313
|
+
{label: "Approve", value: "approve"},
|
1314
|
+
{label: "Request Changes", value: "revise"},
|
1315
|
+
{label: "Reject", value: "reject"}
|
1316
|
+
]
|
1317
|
+
end
|
1318
|
+
|
1319
|
+
case steps.human_review.action
|
1320
|
+
when "approve"
|
1321
|
+
tool :publish, Tools::PublishArticle,
|
1322
|
+
content: steps.draft
|
1323
|
+
when "revise"
|
1324
|
+
ai :revision do
|
1325
|
+
system "Revise based on feedback"
|
1326
|
+
user "Feedback: {{ steps.human_review.feedback }}"
|
1327
|
+
end
|
1328
|
+
# Loop back to human review
|
1329
|
+
goto :human_review
|
1330
|
+
when "reject"
|
1331
|
+
emit :rejected
|
1332
|
+
end
|
1333
|
+
end
|
1334
|
+
```
|
1335
|
+
|
1336
|
+
#### Event Projections
|
1337
|
+
|
1338
|
+
```ruby
|
1339
|
+
# projections/user_activity.rb
|
1340
|
+
projection "user_activity" do
|
1341
|
+
# Subscribe to events
|
1342
|
+
on "user_created" do |event|
|
1343
|
+
Repo[:analytics].increment_counter(
|
1344
|
+
metric: "users.total",
|
1345
|
+
date: event.timestamp.to_date
|
1346
|
+
)
|
1347
|
+
end
|
1348
|
+
|
1349
|
+
on "article_published" do |event|
|
1350
|
+
Repo[:analytics].track_activity(
|
1351
|
+
user_id: event.author_id,
|
1352
|
+
action: "published_article",
|
1353
|
+
timestamp: event.timestamp
|
1354
|
+
)
|
1355
|
+
end
|
1356
|
+
|
1357
|
+
# Materialize views
|
1358
|
+
every 5.minutes do
|
1359
|
+
Repo[:analytics].refresh_materialized_view("user_stats")
|
1360
|
+
end
|
1361
|
+
end
|
1362
|
+
```
|
1363
|
+
|
1364
|
+
## Getting Started
|
1365
|
+
|
1366
|
+
### Quick Start
|
1367
|
+
|
1368
|
+
```bash
|
1369
|
+
# Install Hypervibe
|
1370
|
+
gem install hypervibe
|
1371
|
+
|
1372
|
+
# Create a new project
|
1373
|
+
vibe new my_app
|
1374
|
+
cd my_app
|
1375
|
+
|
1376
|
+
# Set up the database
|
1377
|
+
vibe db:setup
|
1378
|
+
|
1379
|
+
# Generate your first flow
|
1380
|
+
vibe generate flow hello_world
|
1381
|
+
|
1382
|
+
# Start the development server
|
1383
|
+
vibe server
|
1384
|
+
|
1385
|
+
# In another terminal, run the flow
|
1386
|
+
vibe run hello_world name="World"
|
1387
|
+
```
|
1388
|
+
|
1389
|
+
### Your First Flow
|
1390
|
+
|
1391
|
+
```ruby
|
1392
|
+
# app/flows/hello_world.rb
|
1393
|
+
flow "hello_world" do
|
1394
|
+
ai :greeting,
|
1395
|
+
model: "gpt-4o-mini",
|
1396
|
+
schema: {
|
1397
|
+
type: "object",
|
1398
|
+
properties: {
|
1399
|
+
message: {type: "string"},
|
1400
|
+
emoji: {type: "string"}
|
1401
|
+
}
|
1402
|
+
} do
|
1403
|
+
system "You are a friendly greeter"
|
1404
|
+
user "Say hello to {{ params.name }}"
|
1405
|
+
end
|
1406
|
+
|
1407
|
+
emit :greeted, {
|
1408
|
+
name: params.name,
|
1409
|
+
message: steps.greeting["message"],
|
1410
|
+
emoji: steps.greeting["emoji"]
|
1411
|
+
}
|
1412
|
+
end
|
1413
|
+
```
|
1414
|
+
|
1415
|
+
### CLI Commands
|
1416
|
+
|
1417
|
+
```bash
|
1418
|
+
# Development
|
1419
|
+
vibe server # Start dev server with hot reload
|
1420
|
+
vibe console # Interactive Ruby console
|
1421
|
+
vibe test # Run test suite
|
1422
|
+
|
1423
|
+
# Generators
|
1424
|
+
vibe generate flow <name>
|
1425
|
+
vibe generate tool <name>
|
1426
|
+
vibe generate query <name>
|
1427
|
+
|
1428
|
+
# Database
|
1429
|
+
vibe db:create
|
1430
|
+
vibe db:migrate
|
1431
|
+
vibe db:seed
|
1432
|
+
vibe db:reset
|
1433
|
+
|
1434
|
+
# Production
|
1435
|
+
vibe deploy
|
1436
|
+
vibe scale <process>=<count>
|
1437
|
+
vibe logs --tail
|
1438
|
+
vibe metrics
|
1439
|
+
|
1440
|
+
# Tools & Flows
|
1441
|
+
vibe run <flow> [params]
|
1442
|
+
vibe tool <name> [params]
|
1443
|
+
vibe replay <run_id>
|
1444
|
+
|
1445
|
+
# Maintenance
|
1446
|
+
vibe traces:clean --before 30d
|
1447
|
+
vibe cache:clear
|
1448
|
+
vibe events:compact
|
1449
|
+
```
|
1450
|
+
|
1451
|
+
## Philosophy
|
1452
|
+
|
1453
|
+
Hypervibe embraces several core principles:
|
1454
|
+
|
1455
|
+
1. **SQL is a Feature, Not a Bug**: LLMs understand SQL better than ORMs. We generate types from SQL, not the other way around.
|
1456
|
+
|
1457
|
+
2. **Observability Over Debugging**: Every action leaves a trace. You can replay any flow execution and see exactly what happened.
|
1458
|
+
|
1459
|
+
3. **Streams Over Requests**: AI is slow. Users need feedback. Everything streams by default.
|
1460
|
+
|
1461
|
+
4. **Contracts Over Documentation**: JSON Schema defines everything. Tools, flows, and prompts all have contracts that are enforced at runtime.
|
1462
|
+
|
1463
|
+
5. **Events Over State**: Event sourcing provides natural versioning, auditing, and time-travel debugging.
|
1464
|
+
|
1465
|
+
6. **Composition Over Inheritance**: Small tools and flows compose into larger systems. No base classes or deep hierarchies.
|
1466
|
+
|
1467
|
+
## Contributing
|
1468
|
+
|
1469
|
+
Hypervibe is open source and welcomes contributions. See [CONTRIBUTING.md](https://github.com/hypervibe/hypervibe/blob/main/CONTRIBUTING.md) for details.
|
1470
|
+
|
1471
|
+
## License
|
1472
|
+
|
1473
|
+
MIT License. See [LICENSE](https://github.com/hypervibe/hypervibe/blob/main/LICENSE) for details.
|
1474
|
+
|
1475
|
+
## Learn More
|
1476
|
+
|
1477
|
+
- **Documentation**: [hypervibe.dev](https://hypervibe.dev)
|
1478
|
+
- **Examples**: [github.com/hypervibe/examples](https://github.com/hypervibe/examples)
|
1479
|
+
- **Discord**: [discord.gg/hypervibe](https://discord.gg/hypervibe)
|
1480
|
+
- **Twitter**: [@hypervibeframework](https://twitter.com/hypervibeframework)
|
1481
|
+
|
1482
|
+
---
|
1483
|
+
|
1484
|
+
Built for the age of AI-assisted development, where LLMs write the SQL and humans define the workflows.
|