rlhf-feedback-loop 0.6.6 → 0.6.8
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.
- package/README.md +29 -32
- package/bin/cli.js +76 -17
- package/package.json +14 -5
- package/scripts/billing.js +102 -12
- package/scripts/deploy-gcp.sh +19 -0
- package/scripts/feedback-loop.js +35 -6
- package/scripts/prove-adapters.js +4 -2
- package/scripts/prove-lancedb.js +2 -2
- package/src/api/server.js +62 -0
package/README.md
CHANGED
|
@@ -1,30 +1,39 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Agentic Feedback Studio — The Veto Layer & RLHF-Ready Dataset Engine
|
|
2
2
|
|
|
3
3
|
[](https://github.com/IgorGanapolsky/rlhf-feedback-loop/actions/workflows/ci.yml)
|
|
4
|
-
[](adapters/mcp/server-stdio.js)
|
|
7
|
-
[](scripts/export-dpo-pairs.js)
|
|
4
|
+
[](docs/ANTHROPIC_MARKETPLACE_STRATEGY.md)
|
|
5
|
+
[](docs/VERIFICATION_EVIDENCE.md)
|
|
8
6
|
|
|
9
|
-
**The
|
|
7
|
+
**The operational layer for high-density preference data.** Stop vibe-coding and start context engineering. The Agentic Feedback Studio provides the **Veto Layer** for AI workflows, capturing human feedback to generate **RLHF-ready datasets** and enforce kernel-level guardrails.
|
|
10
8
|
|
|
11
|
-
##
|
|
9
|
+
## Why This Matters: From Vibes to Verification (V2V)
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
Most AI agents run on "vibes." We provide the infrastructure to convert those vibes into **Hard Evidence** for continuous improvement.
|
|
14
12
|
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **Recalls** relevant past feedback mid-conversation (in-session context injection)
|
|
19
|
-
- **Exports** DPO training pairs (prompt/chosen/rejected) for model fine-tuning
|
|
13
|
+
- **Veto Layer (Governance):** Convert subjective user feedback into non-bypassable architectural constraints (`CLAUDE.md`).
|
|
14
|
+
- **RLHF-Ready Datasets:** Automatically generate high-density DPO (Direct Preference Optimization) pairs from real-world agent interactions.
|
|
15
|
+
- **Online Bayesian Reward Estimation:** Uses Thompson Sampling to model user preferences in real-time, providing a local "Reward Signal" without heavy training.
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
## True Plug-and-Play: Zero-Config Integration
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
The Feedback Studio is a **Universal Agent Skill**. You can drop it into any repository without manual setup.
|
|
24
20
|
|
|
25
|
-
|
|
21
|
+
- **Zero-Config Discovery:** Automatically detects project context. If no local `.rlhf/` directory exists, it safely fallbacks to a project-scoped global store in `~/.rlhf/`.
|
|
22
|
+
- **Global Skill Installation:** Run one command to make the Studio available to all your agents across all projects.
|
|
26
23
|
|
|
27
|
-
|
|
24
|
+
### Quick Start (One Command)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx rlhf-feedback-loop install
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This will auto-detect your platforms (Claude, Codex, Gemini, Cursor) and install the feedback skill globally.
|
|
31
|
+
|
|
32
|
+
## Use Cases
|
|
33
|
+
|
|
34
|
+
- **Automated Code Reviews:** Capture PR feedback to enforce team-specific style guides autonomously.
|
|
35
|
+
- **Self-Healing Multi-Agent Systems:** Share Veto rules across a swarm of agents to avoid systemic bottlenecks.
|
|
36
|
+
- **DPO Dataset Engineering:** Collect proprietary preference data to fine-tune smaller, faster models that perform like GPT-4 on your specific codebase.
|
|
28
37
|
|
|
29
38
|
## Get Started
|
|
30
39
|
|
|
@@ -37,14 +46,11 @@ One command. Pick your platform:
|
|
|
37
46
|
| **Gemini** | `gemini mcp add rlhf "npx -y rlhf-feedback-loop serve"` |
|
|
38
47
|
| **Amp** | `amp mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
|
|
39
48
|
| **Cursor** | `cursor mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
|
|
40
|
-
| **All at once** | `npx add-mcp rlhf-feedback-loop` |
|
|
41
|
-
|
|
42
|
-
That's it. Your agent can now capture feedback, recall past learnings mid-conversation, and block repeated mistakes. Run once per project — the MCP server starts automatically on each session.
|
|
43
49
|
|
|
44
50
|
## How It Works
|
|
45
51
|
|
|
46
52
|
```
|
|
47
|
-
|
|
53
|
+
Subjective Signal (Vibe)
|
|
48
54
|
|
|
|
49
55
|
v
|
|
50
56
|
Capture → JSONL log
|
|
@@ -57,14 +63,14 @@ Thumbs up/down
|
|
|
57
63
|
Good Bad
|
|
58
64
|
| |
|
|
59
65
|
v v
|
|
60
|
-
Learn
|
|
66
|
+
Learn Veto Layer (Rule)
|
|
61
67
|
| |
|
|
62
68
|
v v
|
|
63
69
|
LanceDB ShieldCortex
|
|
64
70
|
vectors context packs
|
|
65
71
|
|
|
|
66
72
|
v
|
|
67
|
-
DPO export →
|
|
73
|
+
DPO export → RLHF / Fine-tune your model
|
|
68
74
|
```
|
|
69
75
|
|
|
70
76
|
All data stored locally as **JSONL** files — fully transparent, fully portable, no vendor lock-in. **LanceDB** indexes memories as vector embeddings for semantic search. **ShieldCortex** assembles context packs so your agent starts each task informed.
|
|
@@ -80,18 +86,9 @@ The open-source package is fully functional and free forever. Cloud Pro is for t
|
|
|
80
86
|
| DPO export | CLI command | API endpoint |
|
|
81
87
|
| Setup | `mcp add` one-liner | Provisioned API key |
|
|
82
88
|
| Team sharing | Manual (share JSONL) | Built-in (shared API) |
|
|
83
|
-
| Support | GitHub Issues | Email |
|
|
84
|
-
| Uptime | You manage | We manage (99.9% SLA) |
|
|
85
89
|
|
|
86
90
|
[Get Cloud Pro](https://buy.stripe.com/bJe14neyU4r4f0leOD3sI02) | [Live API](https://rlhf-feedback-loop-710216278770.us-central1.run.app)
|
|
87
91
|
|
|
88
|
-
## Deep Dive
|
|
89
|
-
|
|
90
|
-
- [API Reference](openapi/openapi.yaml) — full OpenAPI spec
|
|
91
|
-
- [Context Engine](docs/CONTEXTFS.md) — multi-agent memory orchestration
|
|
92
|
-
- [Autonomous GitOps](docs/AUTONOMOUS_GITOPS.md) — self-healing CI/CD
|
|
93
|
-
- [Contributing](CONTRIBUTING.md)
|
|
94
|
-
|
|
95
92
|
## License
|
|
96
93
|
|
|
97
94
|
MIT. See [LICENSE](LICENSE).
|
package/bin/cli.js
CHANGED
|
@@ -6,11 +6,8 @@
|
|
|
6
6
|
* npx rlhf-feedback-loop init # scaffold .rlhf/ config + .mcp.json
|
|
7
7
|
* npx rlhf-feedback-loop capture # capture feedback
|
|
8
8
|
* npx rlhf-feedback-loop export-dpo # export DPO training pairs
|
|
9
|
-
* npx rlhf-feedback-loop stats # feedback analytics
|
|
10
|
-
* npx rlhf-feedback-loop
|
|
11
|
-
* npx rlhf-feedback-loop self-heal # run self-healing check + fix
|
|
12
|
-
* npx rlhf-feedback-loop prove # run proof harness
|
|
13
|
-
* npx rlhf-feedback-loop start-api # start HTTPS API server
|
|
9
|
+
* npx rlhf-feedback-loop stats # feedback analytics + Revenue-at-Risk
|
|
10
|
+
* npx rlhf-feedback-loop pro # upgrade to Cloud Pro
|
|
14
11
|
*/
|
|
15
12
|
|
|
16
13
|
'use strict';
|
|
@@ -201,7 +198,7 @@ function capture() {
|
|
|
201
198
|
const { captureFeedback, analyzeFeedback, feedbackSummary, writePreventionRules } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
|
|
202
199
|
|
|
203
200
|
if (args.stats) {
|
|
204
|
-
|
|
201
|
+
stats();
|
|
205
202
|
return;
|
|
206
203
|
}
|
|
207
204
|
|
|
@@ -248,7 +245,42 @@ function capture() {
|
|
|
248
245
|
|
|
249
246
|
function stats() {
|
|
250
247
|
const { analyzeFeedback } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
|
|
251
|
-
|
|
248
|
+
const data = analyzeFeedback();
|
|
249
|
+
|
|
250
|
+
console.log('\n📊 RLHF Performance Metrics');
|
|
251
|
+
console.log('─'.repeat(50));
|
|
252
|
+
console.log(` Total Signals : ${data.total}`);
|
|
253
|
+
console.log(` Approval Rate : ${Math.round(data.approvalRate * 100)}%`);
|
|
254
|
+
console.log(` Recent Trend : ${Math.round(data.recentRate * 100)}%`);
|
|
255
|
+
|
|
256
|
+
// The Pitch: Revenue-at-Risk
|
|
257
|
+
const avgCostOfMistake = 2.50; // $2.50 per agent turn/fix
|
|
258
|
+
const revenueAtRisk = (data.totalNegative * avgCostOfMistake).toFixed(2);
|
|
259
|
+
|
|
260
|
+
if (data.totalNegative > 0) {
|
|
261
|
+
console.log('\n⚠️ REVENUE-AT-RISK ANALYSIS');
|
|
262
|
+
console.log(` Repeated Failures detected: ${data.totalNegative}`);
|
|
263
|
+
console.log(` Estimated Operational Loss: $${revenueAtRisk}`);
|
|
264
|
+
console.log(' Action Required: Run "npx rlhf-feedback-loop rules" to generate guardrails.');
|
|
265
|
+
console.log(' Strategic Recommendation: Upgrade to Cloud Pro to sync these rules across your team.');
|
|
266
|
+
console.log(' Run: npx rlhf-feedback-loop pro');
|
|
267
|
+
} else {
|
|
268
|
+
console.log('\n✅ System is currently high-reliability. No immediate revenue loss detected.');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function pro() {
|
|
273
|
+
const stripeUrl = 'https://buy.stripe.com/bJe14neyU4r4f0leOD3sI02';
|
|
274
|
+
console.log('\n🚀 RLHF Feedback Loop — Cloud Pro');
|
|
275
|
+
console.log('─'.repeat(50));
|
|
276
|
+
console.log('Unlock the full Agentic Control Plane:');
|
|
277
|
+
console.log(' - Hosted Team API (Shared memory across all repos)');
|
|
278
|
+
console.log(' - ShieldCortex Managed Context Packs');
|
|
279
|
+
console.log(' - Automated DPO Training Pipelines');
|
|
280
|
+
console.log(' - SOC2-ready Governance Dashboard');
|
|
281
|
+
console.log('\n👉 Complete your upgrade here:');
|
|
282
|
+
console.log(` ${stripeUrl}`);
|
|
283
|
+
console.log('\nOnce upgraded, run: npx rlhf-feedback-loop init --key=YOUR_PRO_KEY\n');
|
|
252
284
|
}
|
|
253
285
|
|
|
254
286
|
function summary() {
|
|
@@ -307,11 +339,38 @@ function prove() {
|
|
|
307
339
|
}
|
|
308
340
|
|
|
309
341
|
function serve() {
|
|
310
|
-
|
|
342
|
+
const rlhfDir = path.join(CWD, '.rlhf');
|
|
343
|
+
if (!fs.existsSync(rlhfDir) && !fs.existsSync(path.join(CWD, '.claude', 'memory', 'feedback'))) {
|
|
344
|
+
// If not initialized, ensure global fallback exists
|
|
345
|
+
const projectName = path.basename(CWD) || 'default';
|
|
346
|
+
const globalDir = path.join(HOME, '.rlhf', 'projects', projectName);
|
|
347
|
+
if (!fs.existsSync(globalDir)) {
|
|
348
|
+
fs.mkdirSync(globalDir, { recursive: true });
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Start MCP server over stdio
|
|
311
353
|
const mcpServer = path.join(PKG_ROOT, 'adapters', 'mcp', 'server-stdio.js');
|
|
312
354
|
require(mcpServer);
|
|
313
355
|
}
|
|
314
356
|
|
|
357
|
+
function install() {
|
|
358
|
+
console.log('Installing RLHF Feedback Loop as a global MCP skill...');
|
|
359
|
+
const results = [
|
|
360
|
+
setupClaude(),
|
|
361
|
+
setupCodex(),
|
|
362
|
+
setupGemini(),
|
|
363
|
+
setupCursor()
|
|
364
|
+
];
|
|
365
|
+
const success = results.some(r => r === true);
|
|
366
|
+
if (success) {
|
|
367
|
+
console.log('\nSuccess! RLHF Feedback Loop is now available to your agents.');
|
|
368
|
+
console.log('Try asking your agent: "Capture positive feedback for this task"');
|
|
369
|
+
} else {
|
|
370
|
+
console.log('\nRLHF Feedback Loop is already configured.');
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
315
374
|
function startApi() {
|
|
316
375
|
const serverPath = path.join(PKG_ROOT, 'src', 'api', 'server.js');
|
|
317
376
|
try {
|
|
@@ -329,32 +388,29 @@ function help() {
|
|
|
329
388
|
console.log(' init Scaffold .rlhf/ config + MCP server in current project');
|
|
330
389
|
console.log(' serve Start MCP server (stdio) — for claude/codex/gemini mcp add');
|
|
331
390
|
console.log(' capture [flags] Capture feedback (--feedback=up|down --context="..." --tags="...")');
|
|
332
|
-
console.log(' stats Show feedback analytics');
|
|
391
|
+
console.log(' stats Show feedback analytics + Revenue-at-Risk');
|
|
333
392
|
console.log(' summary Human-readable feedback summary');
|
|
334
393
|
console.log(' export-dpo Export DPO training pairs (prompt/chosen/rejected JSONL)');
|
|
335
394
|
console.log(' rules Generate prevention rules from repeated failures');
|
|
336
395
|
console.log(' self-heal Run self-healing check and auto-fix');
|
|
396
|
+
console.log(' pro Upgrade to Cloud Pro ($10/mo)');
|
|
337
397
|
console.log(' prove [--target=X] Run proof harness (adapters|automation|attribution|lancedb|...)');
|
|
338
398
|
console.log(' start-api Start the RLHF HTTPS API server');
|
|
339
399
|
console.log(' help Show this help message');
|
|
340
400
|
console.log('');
|
|
341
401
|
console.log('Examples:');
|
|
342
402
|
console.log(' npx rlhf-feedback-loop init');
|
|
343
|
-
console.log(' npx rlhf-feedback-loop capture --feedback=up --context="all tests pass"');
|
|
344
|
-
console.log(' npx rlhf-feedback-loop capture --feedback=down --context="broke prod" --what-went-wrong="no tests"');
|
|
345
|
-
console.log(' npx rlhf-feedback-loop export-dpo');
|
|
346
403
|
console.log(' npx rlhf-feedback-loop stats');
|
|
347
|
-
console.log('');
|
|
348
|
-
console.log('MCP install (one command per platform):');
|
|
349
|
-
console.log(' claude mcp add rlhf -- npx -y rlhf-feedback-loop serve');
|
|
350
|
-
console.log(' codex mcp add rlhf -- npx -y rlhf-feedback-loop serve');
|
|
351
|
-
console.log(' gemini mcp add rlhf -- npx -y rlhf-feedback-loop serve');
|
|
404
|
+
console.log(' npx rlhf-feedback-loop pro');
|
|
352
405
|
}
|
|
353
406
|
|
|
354
407
|
switch (COMMAND) {
|
|
355
408
|
case 'init':
|
|
356
409
|
init();
|
|
357
410
|
break;
|
|
411
|
+
case 'install':
|
|
412
|
+
install();
|
|
413
|
+
break;
|
|
358
414
|
case 'serve':
|
|
359
415
|
case 'mcp':
|
|
360
416
|
serve();
|
|
@@ -379,6 +435,9 @@ switch (COMMAND) {
|
|
|
379
435
|
case 'self-heal':
|
|
380
436
|
selfHeal();
|
|
381
437
|
break;
|
|
438
|
+
case 'pro':
|
|
439
|
+
pro();
|
|
440
|
+
break;
|
|
382
441
|
case 'prove':
|
|
383
442
|
prove();
|
|
384
443
|
break;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rlhf-feedback-loop",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.6.8",
|
|
4
|
+
"description": "Production RLHF & DPO data pipeline for AI agents. Optimize agentic reliability with Feedback-Driven Development (FDD). Capture human preference signals, generate automated guardrails, and export DPO training pairs. Compatible with Claude, GPT-4, Gemini, and multi-agent systems.",
|
|
5
5
|
"homepage": "https://github.com/IgorGanapolsky/rlhf-feedback-loop#readme",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -91,6 +91,13 @@
|
|
|
91
91
|
"amp",
|
|
92
92
|
"mcp",
|
|
93
93
|
"model-context-protocol",
|
|
94
|
+
"agentic-reliability",
|
|
95
|
+
"feedback-driven-development",
|
|
96
|
+
"agentic-workflows",
|
|
97
|
+
"ai-search-optimization",
|
|
98
|
+
"generative-engine-optimization",
|
|
99
|
+
"fdd",
|
|
100
|
+
"geo",
|
|
94
101
|
"agent-evaluation",
|
|
95
102
|
"prompt-engineering",
|
|
96
103
|
"context-engineering",
|
|
@@ -101,10 +108,12 @@
|
|
|
101
108
|
],
|
|
102
109
|
"license": "MIT",
|
|
103
110
|
"dependencies": {
|
|
104
|
-
"@huggingface/transformers": "^3.8.1",
|
|
105
|
-
"@lancedb/lancedb": "^0.26.2",
|
|
106
111
|
"apache-arrow": "^18.1.0",
|
|
107
112
|
"stripe": "^20.4.0"
|
|
108
113
|
},
|
|
109
|
-
"mcpName": "io.github.IgorGanapolsky/rlhf-feedback-loop"
|
|
114
|
+
"mcpName": "io.github.IgorGanapolsky/rlhf-feedback-loop",
|
|
115
|
+
"optionalDependencies": {
|
|
116
|
+
"@lancedb/lancedb": "^0.26.2",
|
|
117
|
+
"@huggingface/transformers": "^3.8.1"
|
|
118
|
+
}
|
|
110
119
|
}
|
package/scripts/billing.js
CHANGED
|
@@ -25,8 +25,9 @@ const crypto = require('crypto');
|
|
|
25
25
|
|
|
26
26
|
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY || '';
|
|
27
27
|
const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET || '';
|
|
28
|
+
const GITHUB_MARKETPLACE_WEBHOOK_SECRET = process.env.GITHUB_MARKETPLACE_WEBHOOK_SECRET || '';
|
|
28
29
|
const STRIPE_PRICE_ID = process.env.STRIPE_PRICE_ID || 'price_cloud_pro_49_monthly';
|
|
29
|
-
const API_KEYS_PATH = path.resolve(
|
|
30
|
+
const API_KEYS_PATH = process.env._TEST_API_KEYS_PATH || path.resolve(
|
|
30
31
|
__dirname,
|
|
31
32
|
'../.claude/memory/feedback/api-keys.json'
|
|
32
33
|
);
|
|
@@ -443,20 +444,106 @@ function verifyWebhookSignature(rawBody, signature) {
|
|
|
443
444
|
|
|
444
445
|
// Constant-time comparison
|
|
445
446
|
try {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
447
|
+
return crypto.timingSafeEqual(
|
|
448
|
+
Buffer.from(expected, 'hex'),
|
|
449
|
+
Buffer.from(parts.v1, 'hex')
|
|
450
|
+
);
|
|
450
451
|
} catch {
|
|
451
|
-
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
452
454
|
}
|
|
453
|
-
}
|
|
454
455
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
456
|
+
/**
|
|
457
|
+
* Verify a GitHub Marketplace webhook signature.
|
|
458
|
+
* Returns true if valid, false if GITHUB_MARKETPLACE_WEBHOOK_SECRET is not set (local mode).
|
|
459
|
+
*
|
|
460
|
+
* @param {string|Buffer} rawBody - Raw request body bytes
|
|
461
|
+
* @param {string} signature - Value of x-hub-signature-256 header
|
|
462
|
+
* @returns {boolean}
|
|
463
|
+
*/
|
|
464
|
+
function verifyGithubWebhookSignature(rawBody, signature) {
|
|
465
|
+
if (!GITHUB_MARKETPLACE_WEBHOOK_SECRET) {
|
|
466
|
+
// Local mode — skip signature verification
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (!signature || !rawBody) {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const hmac = crypto.createHmac('sha256', GITHUB_MARKETPLACE_WEBHOOK_SECRET);
|
|
475
|
+
const digest = Buffer.from('sha256=' + hmac.update(rawBody).digest('hex'), 'utf8');
|
|
476
|
+
const checksum = Buffer.from(signature, 'utf8');
|
|
477
|
+
|
|
478
|
+
// Constant-time comparison
|
|
479
|
+
return checksum.length === digest.length && crypto.timingSafeEqual(digest, checksum);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Handle a GitHub Marketplace webhook event.
|
|
484
|
+
*
|
|
485
|
+
* Supported actions:
|
|
486
|
+
* purchased — provision API key for the new customer
|
|
487
|
+
* changed — plan update (upgrade/downgrade)
|
|
488
|
+
* cancelled — disable all keys for that customer
|
|
489
|
+
*
|
|
490
|
+
* @param {object} event - Parsed GitHub Marketplace event object
|
|
491
|
+
* @returns {{ handled: boolean, action?: string, result?: object }}
|
|
492
|
+
*/
|
|
493
|
+
function handleGithubWebhook(event) {
|
|
494
|
+
const { action, marketplace_purchase } = event;
|
|
495
|
+
if (!action || !marketplace_purchase) {
|
|
496
|
+
return { handled: false, reason: 'missing_payload_data' };
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const account = marketplace_purchase.account;
|
|
500
|
+
if (!account || !account.id) {
|
|
501
|
+
return { handled: false, reason: 'missing_account_id' };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Map GitHub account to customerId: github_<user|organization>_<id>
|
|
505
|
+
const customerId = `github_${account.type.toLowerCase()}_${account.id}`;
|
|
458
506
|
|
|
459
|
-
|
|
507
|
+
switch (action) {
|
|
508
|
+
case 'purchased': {
|
|
509
|
+
const result = provisionApiKey(customerId);
|
|
510
|
+
return {
|
|
511
|
+
handled: true,
|
|
512
|
+
action: 'provisioned_api_key',
|
|
513
|
+
result,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
case 'cancelled': {
|
|
518
|
+
const result = disableCustomerKeys(customerId);
|
|
519
|
+
return {
|
|
520
|
+
handled: true,
|
|
521
|
+
action: 'disabled_customer_keys',
|
|
522
|
+
result,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
case 'changed': {
|
|
527
|
+
// In this simple model, we just ensure a key exists and is active.
|
|
528
|
+
// Upgrades/downgrades don't change basic API access unless we had tiered features.
|
|
529
|
+
const result = provisionApiKey(customerId);
|
|
530
|
+
return {
|
|
531
|
+
handled: true,
|
|
532
|
+
action: 'plan_changed',
|
|
533
|
+
result,
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
default:
|
|
538
|
+
return { handled: false, reason: `unhandled_action:${action}` };
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// ---------------------------------------------------------------------------
|
|
543
|
+
// Module exports
|
|
544
|
+
// ---------------------------------------------------------------------------
|
|
545
|
+
|
|
546
|
+
module.exports = {
|
|
460
547
|
createCheckoutSession,
|
|
461
548
|
provisionApiKey,
|
|
462
549
|
validateApiKey,
|
|
@@ -464,8 +551,11 @@ module.exports = {
|
|
|
464
551
|
disableCustomerKeys,
|
|
465
552
|
handleWebhook,
|
|
466
553
|
verifyWebhookSignature,
|
|
554
|
+
verifyGithubWebhookSignature,
|
|
555
|
+
handleGithubWebhook,
|
|
467
556
|
loadKeyStore,
|
|
468
557
|
// Expose for testing
|
|
469
558
|
_API_KEYS_PATH: API_KEYS_PATH,
|
|
470
559
|
_LOCAL_MODE: () => LOCAL_MODE,
|
|
471
|
-
};
|
|
560
|
+
};
|
|
561
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# GSD: Deploy RLHF Control Plane to Google Cloud Run
|
|
3
|
+
|
|
4
|
+
PROJECT_ID=$(gcloud config get-value project)
|
|
5
|
+
SERVICE_NAME="rlhf-control-plane"
|
|
6
|
+
REGION="us-central1"
|
|
7
|
+
|
|
8
|
+
echo "🚀 Deploying Agentic Control Plane to $REGION..."
|
|
9
|
+
|
|
10
|
+
gcloud builds submit --tag gcr.io/$PROJECT_ID/$SERVICE_NAME
|
|
11
|
+
gcloud run deploy $SERVICE_NAME \
|
|
12
|
+
--image gcr.io/$PROJECT_ID/$SERVICE_NAME \
|
|
13
|
+
--platform managed \
|
|
14
|
+
--region $REGION \
|
|
15
|
+
--allow-unauthenticated \
|
|
16
|
+
--set-env-vars RLHF_ALLOW_INSECURE=true
|
|
17
|
+
|
|
18
|
+
echo "✅ Success! Your Control Plane is live."
|
|
19
|
+
gcloud run services describe $SERVICE_NAME --region $REGION --format='value(status.url)'
|
package/scripts/feedback-loop.js
CHANGED
|
@@ -29,14 +29,43 @@ const DOMAIN_CATEGORIES = [
|
|
|
29
29
|
'git-workflow', 'documentation', 'debugging', 'architecture', 'data-modeling',
|
|
30
30
|
];
|
|
31
31
|
|
|
32
|
+
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
|
33
|
+
|
|
32
34
|
function getFeedbackPaths() {
|
|
33
|
-
|
|
35
|
+
if (process.env.RLHF_FEEDBACK_DIR) {
|
|
36
|
+
const d = process.env.RLHF_FEEDBACK_DIR;
|
|
37
|
+
return {
|
|
38
|
+
FEEDBACK_DIR: d,
|
|
39
|
+
FEEDBACK_LOG_PATH: path.join(d, 'feedback-log.jsonl'),
|
|
40
|
+
MEMORY_LOG_PATH: path.join(d, 'memory-log.jsonl'),
|
|
41
|
+
SUMMARY_PATH: path.join(d, 'feedback-summary.json'),
|
|
42
|
+
PREVENTION_RULES_PATH: path.join(d, 'prevention-rules.md'),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Auto-discovery order:
|
|
47
|
+
// 1. .rlhf/ (Standard)
|
|
48
|
+
// 2. .claude/memory/feedback/ (Legacy Claude)
|
|
49
|
+
// 3. ~/.rlhf/projects/<cwd-basename>/ (Global fallback for true plug-and-play)
|
|
50
|
+
|
|
51
|
+
const localRlhf = path.join(process.cwd(), '.rlhf');
|
|
52
|
+
const localClaude = path.join(process.cwd(), '.claude', 'memory', 'feedback');
|
|
53
|
+
|
|
54
|
+
let baseDir = localRlhf;
|
|
55
|
+
if (!fs.existsSync(localRlhf) && fs.existsSync(localClaude)) {
|
|
56
|
+
baseDir = localClaude;
|
|
57
|
+
} else if (!fs.existsSync(localRlhf)) {
|
|
58
|
+
// Zero-Config Global Fallback
|
|
59
|
+
const projectName = path.basename(process.cwd()) || 'default';
|
|
60
|
+
baseDir = path.join(HOME, '.rlhf', 'projects', projectName);
|
|
61
|
+
}
|
|
62
|
+
|
|
34
63
|
return {
|
|
35
|
-
FEEDBACK_DIR:
|
|
36
|
-
FEEDBACK_LOG_PATH: path.join(
|
|
37
|
-
MEMORY_LOG_PATH: path.join(
|
|
38
|
-
SUMMARY_PATH: path.join(
|
|
39
|
-
PREVENTION_RULES_PATH: path.join(
|
|
64
|
+
FEEDBACK_DIR: baseDir,
|
|
65
|
+
FEEDBACK_LOG_PATH: path.join(baseDir, 'feedback-log.jsonl'),
|
|
66
|
+
MEMORY_LOG_PATH: path.join(baseDir, 'memory-log.jsonl'),
|
|
67
|
+
SUMMARY_PATH: path.join(baseDir, 'feedback-summary.json'),
|
|
68
|
+
PREVENTION_RULES_PATH: path.join(baseDir, 'prevention-rules.md'),
|
|
40
69
|
};
|
|
41
70
|
}
|
|
42
71
|
|
|
@@ -323,8 +323,10 @@ async function runProof(options = {}) {
|
|
|
323
323
|
} catch (err) {
|
|
324
324
|
addResult('fatal', false, { error: err.message });
|
|
325
325
|
} finally {
|
|
326
|
-
await new Promise((resolve) => server.close(resolve));
|
|
327
|
-
|
|
326
|
+
if (server) await new Promise((resolve) => server.close(resolve));
|
|
327
|
+
try {
|
|
328
|
+
fs.rmSync(tmpFeedbackDir, { recursive: true, force: true });
|
|
329
|
+
} catch (e) {}
|
|
328
330
|
}
|
|
329
331
|
|
|
330
332
|
if (writeArtifacts) {
|
package/scripts/prove-lancedb.js
CHANGED
|
@@ -140,8 +140,8 @@ async function runProof() {
|
|
|
140
140
|
let vec03Status = 'fail';
|
|
141
141
|
let vec03Evidence = '';
|
|
142
142
|
try {
|
|
143
|
-
const arrowSpec = PKG.dependencies['apache-arrow'] || '';
|
|
144
|
-
const lanceSpec = PKG.dependencies['@lancedb/lancedb'] || '';
|
|
143
|
+
const arrowSpec = PKG.dependencies['apache-arrow'] || PKG.optionalDependencies['apache-arrow'] || '';
|
|
144
|
+
const lanceSpec = PKG.dependencies['@lancedb/lancedb'] || PKG.optionalDependencies['@lancedb/lancedb'] || '';
|
|
145
145
|
|
|
146
146
|
// Check if spec pins to <= 18.1.0 (either "18.1.0", "^18.1.0", or "~18.1.0")
|
|
147
147
|
const arrowVersion = arrowSpec.replace(/[\^~>=<]*/g, '').split('.').map(Number);
|
package/src/api/server.js
CHANGED
|
@@ -37,6 +37,8 @@ const {
|
|
|
37
37
|
recordUsage,
|
|
38
38
|
handleWebhook,
|
|
39
39
|
verifyWebhookSignature,
|
|
40
|
+
verifyGithubWebhookSignature,
|
|
41
|
+
handleGithubWebhook,
|
|
40
42
|
loadKeyStore,
|
|
41
43
|
} = require('../../scripts/billing');
|
|
42
44
|
|
|
@@ -195,6 +197,30 @@ function createApiServer() {
|
|
|
195
197
|
return;
|
|
196
198
|
}
|
|
197
199
|
|
|
200
|
+
if (req.method === 'GET' && pathname === '/.well-known/mcp/server-card.json') {
|
|
201
|
+
sendJson(res, 200, {
|
|
202
|
+
name: 'rlhf-feedback-loop',
|
|
203
|
+
description: 'RLHF feedback loop for AI agents. Capture feedback, block mistakes, export DPO data.',
|
|
204
|
+
version: pkg.version,
|
|
205
|
+
tools: [
|
|
206
|
+
{ name: 'recall', description: 'Recall relevant past feedback for current task' },
|
|
207
|
+
{ name: 'capture_feedback', description: 'Capture thumbs up/down feedback' },
|
|
208
|
+
{ name: 'feedback_stats', description: 'Feedback analytics' },
|
|
209
|
+
{ name: 'feedback_summary', description: 'Human-readable feedback summary' },
|
|
210
|
+
{ name: 'prevention_rules', description: 'Generate prevention rules from failures' },
|
|
211
|
+
{ name: 'export_dpo_pairs', description: 'Export DPO training pairs' },
|
|
212
|
+
{ name: 'construct_context_pack', description: 'Build bounded context pack' },
|
|
213
|
+
{ name: 'evaluate_context_pack', description: 'Record context pack outcome' },
|
|
214
|
+
{ name: 'context_provenance', description: 'Audit trail of context decisions' },
|
|
215
|
+
{ name: 'list_intents', description: 'Available action plans' },
|
|
216
|
+
{ name: 'plan_intent', description: 'Generate execution plan' },
|
|
217
|
+
],
|
|
218
|
+
repository: 'https://github.com/IgorGanapolsky/rlhf-feedback-loop',
|
|
219
|
+
homepage: 'https://rlhf-feedback-loop-710216278770.us-central1.run.app',
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
198
224
|
if (req.method === 'GET' && pathname === '/health') {
|
|
199
225
|
sendJson(res, 200, {
|
|
200
226
|
status: 'ok',
|
|
@@ -240,6 +266,42 @@ function createApiServer() {
|
|
|
240
266
|
return;
|
|
241
267
|
}
|
|
242
268
|
|
|
269
|
+
// GitHub Marketplace webhook
|
|
270
|
+
if (req.method === 'POST' && pathname === '/v1/billing/github-webhook') {
|
|
271
|
+
try {
|
|
272
|
+
const rawBody = await new Promise((resolve, reject) => {
|
|
273
|
+
const chunks = [];
|
|
274
|
+
req.on('data', (c) => chunks.push(c));
|
|
275
|
+
req.on('end', () => resolve(Buffer.concat(chunks)));
|
|
276
|
+
req.on('error', reject);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const sig = req.headers['x-hub-signature-256'] || '';
|
|
280
|
+
if (!verifyGithubWebhookSignature(rawBody, sig)) {
|
|
281
|
+
sendJson(res, 400, { error: 'Invalid webhook signature' });
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
let event;
|
|
286
|
+
try {
|
|
287
|
+
event = JSON.parse(rawBody.toString('utf-8'));
|
|
288
|
+
} catch {
|
|
289
|
+
sendJson(res, 400, { error: 'Invalid JSON in webhook body' });
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const result = handleGithubWebhook(event);
|
|
294
|
+
sendJson(res, 200, result);
|
|
295
|
+
} catch (err) {
|
|
296
|
+
if (err.statusCode) {
|
|
297
|
+
sendJson(res, err.statusCode, { error: err.message });
|
|
298
|
+
} else {
|
|
299
|
+
sendJson(res, 500, { error: err.message || 'Internal Server Error' });
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
243
305
|
if (!isAuthorized(req, expectedApiKey)) {
|
|
244
306
|
sendJson(res, 401, { error: 'Unauthorized' });
|
|
245
307
|
return;
|