mio-agent-runtime 0.16.1 → 0.17.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.
- package/README.md +3 -0
- package/bin/mio.js +35 -1
- package/package.json +2 -2
- package/server/creativity-engine.js +220 -7
- package/server/mio-intelligence-mcp/index.js +8 -0
package/README.md
CHANGED
|
@@ -58,6 +58,7 @@ mio creativity list List creativity hypotheses (--status active|validat
|
|
|
58
58
|
mio creativity generate Generate hypotheses from 2+ --source "name|content" (or --from-insights; calls an LLM)
|
|
59
59
|
mio creativity ferment Review and refine active hypotheses (calls an LLM)
|
|
60
60
|
mio creativity adopt Record an adoption event for a stored hypothesis (writes traces only)
|
|
61
|
+
mio creativity feedback Show feedback runtime mode, snapshot metrics, and rollout decision (read-only)
|
|
61
62
|
mio idea generate --goal "..." Grounded idea pipeline: auto sources + novelty gate, persisted (calls an LLM)
|
|
62
63
|
mio insight status Insight counts: total, reported, unreported, high-value (needs @akemi-mio/insight)
|
|
63
64
|
mio insight list List insights (--unreported, --min-score N, --detector X, --limit N)
|
|
@@ -384,6 +385,7 @@ Historically risky: ...
|
|
|
384
385
|
mio creativity status
|
|
385
386
|
mio creativity list --status rejected --limit 10
|
|
386
387
|
mio creativity list --json
|
|
388
|
+
mio creativity feedback
|
|
387
389
|
```
|
|
388
390
|
|
|
389
391
|
```text
|
|
@@ -404,6 +406,7 @@ Recent top ideas:
|
|
|
404
406
|
- **`status` 计入 `draft`。** `draft` 是发酵中的真实状态(`ferment` 会同时处理 `draft` 与 `active`),此前漏计导致 `status` 与 `list --status draft` 对不上。
|
|
405
407
|
- **空存储是正常状态。** 引擎运行之前,`status` 报告全零计数,`list` 显示 "No hypotheses match." 两者都是有效输出,不是错误。
|
|
406
408
|
- **缓存会在文件变化时失效。** 三个 JSONL 的读取按 `(size, mtime)` 校验,因此另一个进程写入后长驻的 MCP 服务端无需重启就能看到——与 `server/insight-store.js` 同一套守卫。
|
|
409
|
+
- **`feedback` 只读展示正向反馈闭环状态**(`mio.creativity.feedback` 同面):当前运行时模式(`off`/`shadow`/`canary`/`full`,默认 `off` = 逐字节原行为)、不可变快照指标(概念对数、归因覆盖率、重复加权率)、观测运行摘要(`feedback-runtime-runs.jsonl`,`shadow`/`canary`/`full` 模式下 generate 自动落盘),以及按预固定阈值算出的放量判定(`rollback`/`advance`/`hold`)。阈值走 `MIO_FEEDBACK_MIN_STAGE_RUNS`/`MIO_FEEDBACK_MIN_COVERAGE`/`MIO_FEEDBACK_MAX_NEGATIVE_RATE`,fail-closed 解析。反馈只影响生成,**永不进 `checkPromotionGate`/`checkSubmitGate`**。
|
|
407
410
|
|
|
408
411
|
### 采用证据链(adopt / status / list)
|
|
409
412
|
|
package/bin/mio.js
CHANGED
|
@@ -2060,6 +2060,7 @@ function creativityUsage(write = console.log) {
|
|
|
2060
2060
|
write(`Usage:
|
|
2061
2061
|
mio creativity status Show hypothesis counts, recent top ideas, adoption join
|
|
2062
2062
|
mio creativity list List hypotheses (--status active|validated|rejected|draft, --sort novelty, --limit N)
|
|
2063
|
+
mio creativity feedback Show feedback runtime mode, snapshot metrics, and rollout decision (read-only)
|
|
2063
2064
|
|
|
2064
2065
|
mio creativity generate Generate hypotheses by combining sources (needs 2+ --source, calls an LLM)
|
|
2065
2066
|
mio creativity ferment Review/refine active hypotheses (calls an LLM)
|
|
@@ -2078,6 +2079,7 @@ Examples:
|
|
|
2078
2079
|
mio creativity status
|
|
2079
2080
|
mio creativity list --status rejected --limit 10
|
|
2080
2081
|
mio creativity list --sort novelty
|
|
2082
|
+
mio creativity feedback
|
|
2081
2083
|
mio creativity generate --source "auth|token rotation" --source "cache|write-through"
|
|
2082
2084
|
mio creativity generate --from-insights --source "auth|token rotation"
|
|
2083
2085
|
|
|
@@ -2428,7 +2430,7 @@ function creativityCommand(args, useJson) {
|
|
|
2428
2430
|
if (sub === 'ferment') return creativityFermentCommand(args, useJson)
|
|
2429
2431
|
if (sub === 'adopt') return creativityAdoptCommand(args, useJson)
|
|
2430
2432
|
|
|
2431
|
-
if (!['status', 'list'].includes(sub)) {
|
|
2433
|
+
if (!['status', 'list', 'feedback'].includes(sub)) {
|
|
2432
2434
|
console.error(`Unknown creativity subcommand: ${sub}`)
|
|
2433
2435
|
creativityUsage(console.error)
|
|
2434
2436
|
process.exitCode = 1
|
|
@@ -2442,6 +2444,8 @@ function creativityCommand(args, useJson) {
|
|
|
2442
2444
|
try {
|
|
2443
2445
|
if (sub === 'status') {
|
|
2444
2446
|
result = engine.status()
|
|
2447
|
+
} else if (sub === 'feedback') {
|
|
2448
|
+
result = engine.feedbackStatus()
|
|
2445
2449
|
} else {
|
|
2446
2450
|
const sort = optionValue(flags, '--sort')
|
|
2447
2451
|
if (sort !== undefined && sort !== 'novelty') {
|
|
@@ -2463,9 +2467,38 @@ function creativityCommand(args, useJson) {
|
|
|
2463
2467
|
|
|
2464
2468
|
if (useJson) return jsonOrText(result, true)
|
|
2465
2469
|
if (sub === 'status') printCreativityStatus(result)
|
|
2470
|
+
else if (sub === 'feedback') printCreativityFeedback(result)
|
|
2466
2471
|
else printCreativityList(result)
|
|
2467
2472
|
}
|
|
2468
2473
|
|
|
2474
|
+
function printCreativityFeedback(status) {
|
|
2475
|
+
console.log('Feedback runtime (positive feedback loop):')
|
|
2476
|
+
console.log(` mode: ${status.mode}`)
|
|
2477
|
+
const snap = status.snapshot
|
|
2478
|
+
if (snap) {
|
|
2479
|
+
console.log(
|
|
2480
|
+
` snapshot: v${snap.version} pairs=${snap.pairs} events=${snap.eventCount} coverage=${snap.attributionCoverage.toFixed(3)} dupRate=${snap.duplicateApplicationRate.toFixed(3)}`,
|
|
2481
|
+
)
|
|
2482
|
+
} else {
|
|
2483
|
+
console.log(' snapshot: unavailable')
|
|
2484
|
+
}
|
|
2485
|
+
const t = status.thresholds
|
|
2486
|
+
const r = status.rollout
|
|
2487
|
+
console.log(
|
|
2488
|
+
` runs: shadow=${r.metrics.shadowRuns} canary=${r.metrics.canaryRuns} (min per stage ${t.minStageRuns})`,
|
|
2489
|
+
)
|
|
2490
|
+
console.log(` rollout: ${r.decision}${r.to ? ` → ${r.to}` : ''} (from ${r.from})`)
|
|
2491
|
+
for (const reason of r.reasons) console.log(` - ${reason}`)
|
|
2492
|
+
if (status.recentRuns.length > 0) {
|
|
2493
|
+
console.log(' recent runs:')
|
|
2494
|
+
for (const run of status.recentRuns) {
|
|
2495
|
+
console.log(
|
|
2496
|
+
` ${new Date(run.at).toISOString().slice(0, 19)} ${run.mode} v${run.snapshotVersion || '-'} pairs=${run.pairCount} applied=${run.applied}`,
|
|
2497
|
+
)
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2469
2502
|
function mechanismUsage(write = console.log) {
|
|
2470
2503
|
write(`mio mechanism <subcommand> — mechanism registry (promotion/rollback)
|
|
2471
2504
|
|
|
@@ -3582,6 +3615,7 @@ Usage:
|
|
|
3582
3615
|
mio experience reuse --source-agent A --target-agent B --experience-id X Record a reuse
|
|
3583
3616
|
mio creativity status Show creativity hypothesis counts, recent top ideas, adoption join
|
|
3584
3617
|
mio creativity list List creativity hypotheses (--status active|validated|rejected|draft, --sort novelty, --limit N)
|
|
3618
|
+
mio creativity feedback Show feedback runtime mode, snapshot metrics, and rollout decision (read-only)
|
|
3585
3619
|
mio creativity generate Generate hypotheses from 2+ --source "name|content" (or --from-insights; calls an LLM)
|
|
3586
3620
|
mio creativity ferment Review and refine active hypotheses (calls an LLM)
|
|
3587
3621
|
mio creativity adopt Record an adoption event for a stored hypothesis (writes traces only)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mio-agent-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Mio Agent Runtime installer and MCP intelligence layer",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@akemi-mio/evolution-scheduler": "^0.1.0",
|
|
37
37
|
"@akemi-mio/observer": "^0.1.5",
|
|
38
38
|
"@akemi-mio/insight": "^0.1.1",
|
|
39
|
-
"@akemi-mio/creativity": "^0.
|
|
39
|
+
"@akemi-mio/creativity": "^0.2.0"
|
|
40
40
|
},
|
|
41
41
|
"main": "index.js",
|
|
42
42
|
"directories": {
|
|
@@ -5,6 +5,24 @@ const path = require('path')
|
|
|
5
5
|
const crypto = require('crypto')
|
|
6
6
|
const { readJsonl } = require('./memory-store.js')
|
|
7
7
|
const { evaluateNovelty } = require('@akemi-mio/creativity/NoveltyScorer')
|
|
8
|
+
const { JsonlFeedbackEventStore } = require('@akemi-mio/creativity/JsonlFeedbackEventStore')
|
|
9
|
+
const {
|
|
10
|
+
buildFeedbackSnapshot,
|
|
11
|
+
buildFeedbackPromptBlock,
|
|
12
|
+
replayFeedbackAdjustment,
|
|
13
|
+
DEFAULT_FEEDBACK_LEARNING_CONFIG,
|
|
14
|
+
} = require('@akemi-mio/creativity/FeedbackLearning')
|
|
15
|
+
const {
|
|
16
|
+
resolveFeedbackRuntime,
|
|
17
|
+
isFeedbackApplied,
|
|
18
|
+
isFeedbackObserved,
|
|
19
|
+
DEFAULT_FEEDBACK_RUNTIME,
|
|
20
|
+
FEEDBACK_MODES,
|
|
21
|
+
} = require('@akemi-mio/creativity/FeedbackRuntime')
|
|
22
|
+
const {
|
|
23
|
+
resolveFeedbackRolloutThresholds,
|
|
24
|
+
buildFeedbackRolloutReport,
|
|
25
|
+
} = require('@akemi-mio/creativity/FeedbackRollout')
|
|
8
26
|
|
|
9
27
|
// Cache validity key for a JSONL file: a (size, mtimeMs) pair, the same
|
|
10
28
|
// guard server/insight-store.js uses (commit 0ba7fa8). Every getter below used
|
|
@@ -172,7 +190,7 @@ function pairSourceKey(name) {
|
|
|
172
190
|
return String(name == null ? '' : name).trim().slice(0, 120)
|
|
173
191
|
}
|
|
174
192
|
|
|
175
|
-
function mixConcepts(sources, exploredPairs, rng, strategy) {
|
|
193
|
+
function mixConcepts(sources, exploredPairs, rng, strategy, pairAdjustments) {
|
|
176
194
|
if (sources.length < 2) return []
|
|
177
195
|
// Dedup identity is (pair, strategy): `strategy` picks the prompt and the
|
|
178
196
|
// temperature, so the documented fallback "hit 409, retry with another
|
|
@@ -199,6 +217,16 @@ function mixConcepts(sources, exploredPairs, rng, strategy) {
|
|
|
199
217
|
const j = Math.floor(rng() * (i + 1))
|
|
200
218
|
;[pairs[i], pairs[j]] = [pairs[j], pairs[i]]
|
|
201
219
|
}
|
|
220
|
+
// 正向反馈闭环 P3:按概念对反馈调整重排(缺省 pairAdjustments = 原顺序不变)。
|
|
221
|
+
// key 与快照 conceptPairKey 对齐(origin 优先,name 回退)。
|
|
222
|
+
if (pairAdjustments && pairAdjustments.size > 0) {
|
|
223
|
+
const fbId = (s) => (s.origin ? s.origin : `name:${s.name}`)
|
|
224
|
+
const adj = ([a, b]) => {
|
|
225
|
+
const k = [fbId(a), fbId(b)].sort().join('|')
|
|
226
|
+
return pairAdjustments.get(k) || 0
|
|
227
|
+
}
|
|
228
|
+
pairs.sort((x, y) => adj(y) - adj(x))
|
|
229
|
+
}
|
|
202
230
|
return pairs.slice(0, Math.min(5, pairs.length))
|
|
203
231
|
}
|
|
204
232
|
|
|
@@ -312,9 +340,109 @@ class CreativityEngine {
|
|
|
312
340
|
this.strategyIndex = 0
|
|
313
341
|
this.exploredPairs = new Set()
|
|
314
342
|
this.rng = mulberry32(Date.now())
|
|
343
|
+
// 正向反馈闭环 P0:CLI 侧证据事件只追加 feedback-events.jsonl(交换格式),
|
|
344
|
+
// 权威真相源在主进程 SQLite,由 ingest 单向导入。
|
|
345
|
+
this.feedbackEvents = new JsonlFeedbackEventStore(dataDir)
|
|
346
|
+
// 正向反馈闭环 P3:受控启用运行时(默认 off,行为不变)。
|
|
347
|
+
this.feedbackRuntime = resolveFeedbackRuntime()
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** P3:热切换反馈模式(回滚 = 切回 off)。 */
|
|
351
|
+
setFeedbackRuntime(cfg) {
|
|
352
|
+
this.feedbackRuntime = cfg || DEFAULT_FEEDBACK_RUNTIME
|
|
353
|
+
return this.feedbackRuntime
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** P3:用本机 JSONL 事件 + 假设 provenance 构建不可变快照。 */
|
|
357
|
+
buildFeedbackSnapshot() {
|
|
358
|
+
const events = this.feedbackEvents.list()
|
|
359
|
+
const hypotheses = {}
|
|
360
|
+
for (const h of this.store.getHypotheses()) {
|
|
361
|
+
hypotheses[h.id] = h.provenance ? { sources: h.provenance.sources } : undefined
|
|
362
|
+
}
|
|
363
|
+
return buildFeedbackSnapshot(events, hypotheses, DEFAULT_FEEDBACK_LEARNING_CONFIG)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** P3:把快照转成 mixer 调整表(key = 排序 conceptId 对)。 */
|
|
367
|
+
buildPairAdjustments(snapshot) {
|
|
368
|
+
const map = new Map()
|
|
369
|
+
for (const pair of snapshot.pairs) {
|
|
370
|
+
map.set(pair.pairKey, replayFeedbackAdjustment(pair, DEFAULT_FEEDBACK_LEARNING_CONFIG).clampedAdjustment)
|
|
371
|
+
}
|
|
372
|
+
return map
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** P3 收尾:观测运行摘要落 feedback-runtime-runs.jsonl(append-only,供放量报告)。 */
|
|
376
|
+
recordRuntimeRun(run) {
|
|
377
|
+
if (!fs.existsSync(this.dataDir)) fs.mkdirSync(this.dataDir, { recursive: true })
|
|
378
|
+
fs.appendFileSync(path.join(this.dataDir, 'feedback-runtime-runs.jsonl'), JSON.stringify(run) + '\n', 'utf8')
|
|
315
379
|
}
|
|
316
380
|
|
|
317
|
-
|
|
381
|
+
readRuntimeRuns() {
|
|
382
|
+
const file = path.join(this.dataDir, 'feedback-runtime-runs.jsonl')
|
|
383
|
+
if (!fs.existsSync(file)) return []
|
|
384
|
+
const out = []
|
|
385
|
+
for (const line of fs.readFileSync(file, 'utf8').split('\n')) {
|
|
386
|
+
if (!line.trim()) continue
|
|
387
|
+
try {
|
|
388
|
+
const row = JSON.parse(line)
|
|
389
|
+
if (
|
|
390
|
+
row &&
|
|
391
|
+
typeof row === 'object' &&
|
|
392
|
+
typeof row.mode === 'string' &&
|
|
393
|
+
row.mode !== 'off' &&
|
|
394
|
+
FEEDBACK_MODES.includes(row.mode)
|
|
395
|
+
) {
|
|
396
|
+
out.push({
|
|
397
|
+
at: Number(row.at) || 0,
|
|
398
|
+
mode: row.mode,
|
|
399
|
+
snapshotVersion: typeof row.snapshotVersion === 'string' ? row.snapshotVersion : null,
|
|
400
|
+
pairCount: Number(row.pairCount) || 0,
|
|
401
|
+
applied: !!row.applied,
|
|
402
|
+
})
|
|
403
|
+
}
|
|
404
|
+
} catch (_) {
|
|
405
|
+
// 损坏行跳过(同 JsonlFeedbackEventStore 策略)
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return out
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** P3 收尾:放量状态 + 判定报告(只读;阈值走 env 配置,缺省预固定默认)。 */
|
|
412
|
+
feedbackStatus() {
|
|
413
|
+
let snapshot = null
|
|
414
|
+
try {
|
|
415
|
+
snapshot = this.buildFeedbackSnapshot()
|
|
416
|
+
} catch (_) {
|
|
417
|
+
snapshot = null
|
|
418
|
+
}
|
|
419
|
+
const runs = this.readRuntimeRuns()
|
|
420
|
+
const thresholds = resolveFeedbackRolloutThresholds()
|
|
421
|
+
const rollout = buildFeedbackRolloutReport({
|
|
422
|
+
snapshot,
|
|
423
|
+
runs,
|
|
424
|
+
currentMode: this.feedbackRuntime.mode,
|
|
425
|
+
thresholds,
|
|
426
|
+
})
|
|
427
|
+
return {
|
|
428
|
+
mode: this.feedbackRuntime.mode,
|
|
429
|
+
config: this.feedbackRuntime,
|
|
430
|
+
snapshot: snapshot
|
|
431
|
+
? {
|
|
432
|
+
version: snapshot.version,
|
|
433
|
+
pairs: snapshot.pairs.length,
|
|
434
|
+
eventCount: snapshot.eventCount,
|
|
435
|
+
attributionCoverage: snapshot.attributionCoverage,
|
|
436
|
+
duplicateApplicationRate: snapshot.duplicateApplicationRate,
|
|
437
|
+
}
|
|
438
|
+
: null,
|
|
439
|
+
recentRuns: runs.slice(-5),
|
|
440
|
+
thresholds,
|
|
441
|
+
rollout,
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async generate(sourceLabels, strategy, feedbackBlockArg) {
|
|
318
446
|
// A missing sources array used to reach .map() and surface as
|
|
319
447
|
// "Cannot read properties of undefined", which tells the caller nothing.
|
|
320
448
|
if (!Array.isArray(sourceLabels)) {
|
|
@@ -325,6 +453,8 @@ class CreativityEngine {
|
|
|
325
453
|
content: s.content || '',
|
|
326
454
|
type: s.type || 'knowledge',
|
|
327
455
|
weight: s.weight || 0.5,
|
|
456
|
+
// 正向反馈闭环 P2:保留 origin 供快照 pair 对齐(缺省 = 不影响既有行为)
|
|
457
|
+
...(s.origin ? { origin: s.origin } : {}),
|
|
328
458
|
}))
|
|
329
459
|
|
|
330
460
|
if (sources.length < 2) {
|
|
@@ -349,7 +479,53 @@ class CreativityEngine {
|
|
|
349
479
|
})
|
|
350
480
|
)
|
|
351
481
|
)
|
|
352
|
-
|
|
482
|
+
// 正向反馈闭环 P3:受控启用。
|
|
483
|
+
// off = 原行为(缺省,逐字节不变)
|
|
484
|
+
// shadow = 只观测:计算快照与调整,记录版本,不应用到排序/prompt
|
|
485
|
+
// canary = 按比例应用(canaryFraction 缩放 λ)
|
|
486
|
+
// full = 全部应用
|
|
487
|
+
let feedbackBlock = feedbackBlockArg
|
|
488
|
+
let pairAdjustments = null
|
|
489
|
+
const feedbackInfo = { mode: this.feedbackRuntime.mode, version: null, pairs: 0, applied: false }
|
|
490
|
+
if (isFeedbackObserved(this.feedbackRuntime.mode) && !feedbackBlock) {
|
|
491
|
+
try {
|
|
492
|
+
const snapshot = this.buildFeedbackSnapshot()
|
|
493
|
+
feedbackInfo.version = snapshot.version
|
|
494
|
+
feedbackInfo.pairs = snapshot.pairs.length
|
|
495
|
+
if (snapshot.pairs.length > 0) {
|
|
496
|
+
const block = buildFeedbackPromptBlock(snapshot)
|
|
497
|
+
const adjustments = this.buildPairAdjustments(snapshot)
|
|
498
|
+
if (this.feedbackRuntime.mode === 'canary') {
|
|
499
|
+
const scale = this.feedbackRuntime.canaryFraction
|
|
500
|
+
for (const [k, v] of adjustments) adjustments.set(k, v * scale)
|
|
501
|
+
}
|
|
502
|
+
if (isFeedbackApplied(this.feedbackRuntime.mode)) {
|
|
503
|
+
feedbackBlock = block
|
|
504
|
+
pairAdjustments = adjustments
|
|
505
|
+
feedbackInfo.applied = true
|
|
506
|
+
}
|
|
507
|
+
// shadow:保持 block=null / adjustments=null,仅观测
|
|
508
|
+
}
|
|
509
|
+
} catch (_) {
|
|
510
|
+
// best-effort:反馈构建失败不得阻断生成
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
// P3 收尾:观测模式下落运行摘要,供放量报告统计阶段门槛。
|
|
514
|
+
if (isFeedbackObserved(this.feedbackRuntime.mode)) {
|
|
515
|
+
try {
|
|
516
|
+
this.recordRuntimeRun({
|
|
517
|
+
at: Date.now(),
|
|
518
|
+
mode: this.feedbackRuntime.mode,
|
|
519
|
+
snapshotVersion: feedbackInfo.version,
|
|
520
|
+
pairCount: feedbackInfo.pairs,
|
|
521
|
+
applied: feedbackInfo.applied,
|
|
522
|
+
})
|
|
523
|
+
} catch (_) {
|
|
524
|
+
// best-effort:观测落盘失败不得阻断生成
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const pairs = mixConcepts(sources, exploredPairs, this.rng.bind(this), strat, pairAdjustments)
|
|
353
529
|
|
|
354
530
|
if (pairs.length === 0) {
|
|
355
531
|
// The bare string made a deterministic failure look like "this strategy
|
|
@@ -362,6 +538,7 @@ class CreativityEngine {
|
|
|
362
538
|
reason: `all pairs already explored for strategy "${strat}" (${sources.length} sources, ${candidatePairs} candidate pairs)`,
|
|
363
539
|
strategy: strat,
|
|
364
540
|
pairsAvailable: 0,
|
|
541
|
+
feedback: feedbackInfo,
|
|
365
542
|
}
|
|
366
543
|
}
|
|
367
544
|
|
|
@@ -373,7 +550,7 @@ class CreativityEngine {
|
|
|
373
550
|
// down, the JSON was unparseable, or every pair really was a duplicate.
|
|
374
551
|
const errors = []
|
|
375
552
|
for (const [a, b] of pairs) {
|
|
376
|
-
const prompt = this._buildGenerationPrompt(a, b, strat, recent.slice(-10), rejected.slice(-5))
|
|
553
|
+
const prompt = this._buildGenerationPrompt(a, b, strat, recent.slice(-10), rejected.slice(-5), feedbackBlock)
|
|
377
554
|
try {
|
|
378
555
|
const result = await this.chatJson(prompt, {
|
|
379
556
|
system: [
|
|
@@ -444,7 +621,7 @@ class CreativityEngine {
|
|
|
444
621
|
}
|
|
445
622
|
}
|
|
446
623
|
|
|
447
|
-
return { ideas, strategy: strat, pairsAttempted: pairs.length, errors }
|
|
624
|
+
return { ideas, strategy: strat, pairsAttempted: pairs.length, errors, feedback: feedbackInfo }
|
|
448
625
|
}
|
|
449
626
|
|
|
450
627
|
async ferment(limit = 5) {
|
|
@@ -549,6 +726,23 @@ Output JSON with: title, idea, expectedBenefit, risk, novelty (0-100), feasibili
|
|
|
549
726
|
}
|
|
550
727
|
|
|
551
728
|
this.store.updateHypothesis(h.id, patch)
|
|
729
|
+
|
|
730
|
+
// 正向反馈闭环 P0:ferment 终局追加证据事件(validated/rejected)。
|
|
731
|
+
// sourceId 按 ferment 轮次确定 → 同轮重放幂等;keep/merge 不产事件。
|
|
732
|
+
// 只写 JSONL 交换层,不改假设状态、不进晋级门。
|
|
733
|
+
if (patch.status === 'validated' || patch.status === 'rejected') {
|
|
734
|
+
const feedback = this.feedbackEvents.append({
|
|
735
|
+
hypothesisId: h.id,
|
|
736
|
+
eventType: patch.status,
|
|
737
|
+
sourceType: 'experiment',
|
|
738
|
+
sourceId: `ferment:${h.id}:${patch.fermentCount}`,
|
|
739
|
+
evidenceRef: typeof result.data.reason === 'string' ? result.data.reason.slice(0, 200) : undefined,
|
|
740
|
+
})
|
|
741
|
+
if (!feedback.appended && !feedback.deduped && feedback.quarantine) {
|
|
742
|
+
errors.push({ id: h.id, title: h.title, error: `feedback quarantine: ${feedback.quarantine.reason}` })
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
552
746
|
results.push({ id: h.id, title: h.title, verdict, reason: result.data.reason })
|
|
553
747
|
} catch (err) {
|
|
554
748
|
errors.push({ id: h.id, title: h.title, error: String((err && err.message) || err) })
|
|
@@ -644,7 +838,7 @@ Output JSON with: title, idea, expectedBenefit, risk, novelty (0-100), feasibili
|
|
|
644
838
|
.map(decorate)
|
|
645
839
|
}
|
|
646
840
|
|
|
647
|
-
_buildGenerationPrompt(a, b, strategy, recentHypotheses, rejectedHypotheses) {
|
|
841
|
+
_buildGenerationPrompt(a, b, strategy, recentHypotheses, rejectedHypotheses, feedbackBlock) {
|
|
648
842
|
// Long sources used to be pasted unbounded, crowding the instructions out
|
|
649
843
|
// of the context window; 600 chars keeps the pairing signal without the
|
|
650
844
|
// flood.
|
|
@@ -685,6 +879,12 @@ Output JSON with: title, idea, expectedBenefit, risk, novelty (0-100), feasibili
|
|
|
685
879
|
}
|
|
686
880
|
}
|
|
687
881
|
|
|
882
|
+
// 正向反馈闭环 P2:结构化快照块(已验证来源 + 失败边界)。缺省 = 提示词不变。
|
|
883
|
+
if (feedbackBlock) {
|
|
884
|
+
const { buildFeedbackBlockSection } = require('@akemi-mio/creativity/CreativityPrompt')
|
|
885
|
+
prompt += `\n${buildFeedbackBlockSection(feedbackBlock)}`
|
|
886
|
+
}
|
|
887
|
+
|
|
688
888
|
prompt += `\nOutput JSON: {"title": "...", "idea": "...", "expectedBenefit": "...", "risk": "...", "novelty": 0-100, "feasibility": 0-100, "impact": 0-100, "logic": 0-100}`
|
|
689
889
|
return prompt
|
|
690
890
|
}
|
|
@@ -737,8 +937,9 @@ function adoptHypothesis(engine, args = {}, ingest, dataRoot) {
|
|
|
737
937
|
}
|
|
738
938
|
const taskId = String(args.taskId || '').trim()
|
|
739
939
|
const note = String(args.note || '').trim()
|
|
940
|
+
const occurredAt = Date.now()
|
|
740
941
|
const result = ingest({
|
|
741
|
-
trace_id: `creativity-adopt:${hypothesisId}:${
|
|
942
|
+
trace_id: `creativity-adopt:${hypothesisId}:${occurredAt}`,
|
|
742
943
|
event_type: 'creativity.adopt',
|
|
743
944
|
outcome: 'success',
|
|
744
945
|
payload: {
|
|
@@ -748,10 +949,22 @@ function adoptHypothesis(engine, args = {}, ingest, dataRoot) {
|
|
|
748
949
|
...(note ? { note } : {}),
|
|
749
950
|
},
|
|
750
951
|
})
|
|
952
|
+
// 正向反馈闭环 P0:adopt 是选择证据(weight 0.2 档),追加 adopted 事件到
|
|
953
|
+
// feedback-events.jsonl 交换层。store 状态不变(D1),不写 Memory(D4)。
|
|
954
|
+
// 每次有意的 adopt 调用各记一条事件;P1 聚合时在假设级归并,不虚增样本。
|
|
955
|
+
const feedback = engine.feedbackEvents.append({
|
|
956
|
+
hypothesisId,
|
|
957
|
+
eventType: 'adopted',
|
|
958
|
+
sourceType: 'adoption',
|
|
959
|
+
sourceId: `adopt:${hypothesisId}:${occurredAt}`,
|
|
960
|
+
...(memoryId ? { evidenceRef: `memory:${memoryId}` } : {}),
|
|
961
|
+
occurredAt,
|
|
962
|
+
})
|
|
751
963
|
return {
|
|
752
964
|
recorded: true,
|
|
753
965
|
hypothesisId,
|
|
754
966
|
event: (result && result.event) || result,
|
|
967
|
+
feedback: { appended: feedback.appended, deduped: feedback.deduped },
|
|
755
968
|
}
|
|
756
969
|
}
|
|
757
970
|
|
|
@@ -803,6 +803,12 @@ const TOOLS = [
|
|
|
803
803
|
required: ['hypothesisId'],
|
|
804
804
|
},
|
|
805
805
|
},
|
|
806
|
+
{
|
|
807
|
+
name: 'mio.creativity.feedback',
|
|
808
|
+
description:
|
|
809
|
+
'Read-only positive-feedback-loop status: runtime mode (off/shadow/canary/full), immutable snapshot metrics (pairs, attribution coverage, duplicate application rate), recent observed runs, and the pre-fixed rollout decision (rollback/advance/hold with reasons). Rollout thresholds come from env (MIO_FEEDBACK_MIN_STAGE_RUNS / MIO_FEEDBACK_MIN_COVERAGE / MIO_FEEDBACK_MAX_NEGATIVE_RATE). Feedback never gates promotion or submit.',
|
|
810
|
+
inputSchema: { type: 'object', properties: {} },
|
|
811
|
+
},
|
|
806
812
|
{
|
|
807
813
|
name: 'mio.mechanism.registry',
|
|
808
814
|
description:
|
|
@@ -1234,6 +1240,8 @@ async function callTool(name, args = {}) {
|
|
|
1234
1240
|
return creativityEngine.ferment(args.limit)
|
|
1235
1241
|
case 'mio.creativity.adopt':
|
|
1236
1242
|
return adoptHypothesis(creativityEngine, args, ingestObservation, dataDir)
|
|
1243
|
+
case 'mio.creativity.feedback':
|
|
1244
|
+
return creativityEngine.feedbackStatus()
|
|
1237
1245
|
case 'mio.mechanism.registry':
|
|
1238
1246
|
return mechanismRegistry(args, dataDir)
|
|
1239
1247
|
case 'mio.mechanism.promote':
|