mio-agent-runtime 0.14.1 → 0.15.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 +32 -4
- package/bin/mio.js +50 -6
- package/package.json +2 -2
- package/server/creativity-engine.js +153 -3
- package/server/memory-store.js +12 -1
- package/server/mio-intelligence-mcp/index.js +25 -3
package/README.md
CHANGED
|
@@ -53,10 +53,11 @@ mio memory forget --ids a,b PERMANENTLY delete records (--yes required; writ
|
|
|
53
53
|
mio memory merge --ids a,b Merge duplicate records into one survivor (--keep/--allow-divergent; --yes required)
|
|
54
54
|
mio memory migrate --ids a,b --scope global|project Move records between the project and global layers
|
|
55
55
|
mio policy check "<action>" Check the historical risk of an action before running it (--project; reads global MIO_HOME)
|
|
56
|
-
mio creativity status Show creativity hypothesis counts
|
|
56
|
+
mio creativity status Show creativity hypothesis counts, recent top ideas, adoption join (reads global MIO_HOME)
|
|
57
57
|
mio creativity list List creativity hypotheses (--status active|validated|rejected|draft, --limit N)
|
|
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
|
+
mio creativity adopt Record an adoption event for a stored hypothesis (writes traces only)
|
|
60
61
|
mio idea generate --goal "..." Grounded idea pipeline: auto sources + novelty gate, persisted (calls an LLM)
|
|
61
62
|
mio insight status Insight counts: total, reported, unreported, high-value (needs @akemi-mio/insight)
|
|
62
63
|
mio insight list List insights (--unreported, --min-score N, --detector X, --limit N)
|
|
@@ -102,7 +103,7 @@ mio --version Print the version and exit (-V)
|
|
|
102
103
|
|
|
103
104
|
## MCP 工具
|
|
104
105
|
|
|
105
|
-
MCP 服务端在 5 大域共暴露
|
|
106
|
+
MCP 服务端在 5 大域共暴露 52 个工具:
|
|
106
107
|
|
|
107
108
|
### 记忆(11)
|
|
108
109
|
`mio.memory.query` · `mio.memory.record` · `mio.memory.archive` · `mio.memory.merge` · `mio.memory.migrate` · `mio.memory.analyze` · `mio.memory.forget` · `mio.experience.list` · `mio.experience.confirm` · `mio.experience.reuse` · `mio.policy.check`
|
|
@@ -115,8 +116,8 @@ MCP 服务端在 5 大域共暴露 51 个工具:
|
|
|
115
116
|
### 洞察自省(4)
|
|
116
117
|
`mio.insight.status` · `mio.insight.list` · `mio.insight.mark_reported` · `mio.insight.generate`
|
|
117
118
|
|
|
118
|
-
### 创意引擎(
|
|
119
|
-
`mio.creativity.generate` · `mio.creativity.ferment` · `mio.creativity.list` · `mio.creativity.status` · `mio.idea.generate`
|
|
119
|
+
### 创意引擎(6)
|
|
120
|
+
`mio.creativity.generate` · `mio.creativity.ferment` · `mio.creativity.adopt` · `mio.creativity.list` · `mio.creativity.status` · `mio.idea.generate`
|
|
120
121
|
|
|
121
122
|
### 任务、Agent 与演化(16)
|
|
122
123
|
`mio.task.route` · `mio.task.record_outcome` · `mio.agent.register` · `mio.agent.list` · `mio.agent.report` · `mio.agent.evaluation` · `mio.host.capabilities` · `mio.evolution.status` · `mio.evolution.report` · `mio.evolution.shadow.record` · `mio.evolution.dual_write.record` · `mio.evolution.cutover.readiness` · `mio.evolution.cutover.apply` · `mio.evolution.migration.plan` · `mio.evolution.authority.plan` · `mio.phase0.report`
|
|
@@ -389,6 +390,7 @@ mio creativity list --json
|
|
|
389
390
|
Creativity engine:
|
|
390
391
|
hypotheses: 5 combos: 3 experiments: 1
|
|
391
392
|
active: 2 validated: 1 rejected: 1 draft: 1
|
|
393
|
+
adoption: 2 adopted (events=2, tags=2) — derived join, not a metric
|
|
392
394
|
|
|
393
395
|
Recent top ideas:
|
|
394
396
|
- Plugin architecture (novelty=80 feasibility=70 impact=90 score=240)
|
|
@@ -403,6 +405,32 @@ Recent top ideas:
|
|
|
403
405
|
- **空存储是正常状态。** 引擎运行之前,`status` 报告全零计数,`list` 显示 "No hypotheses match." 两者都是有效输出,不是错误。
|
|
404
406
|
- **缓存会在文件变化时失效。** 三个 JSONL 的读取按 `(size, mtime)` 校验,因此另一个进程写入后长驻的 MCP 服务端无需重启就能看到——与 `server/insight-store.js` 同一套守卫。
|
|
405
407
|
|
|
408
|
+
### 采用证据链(adopt / status / list)
|
|
409
|
+
|
|
410
|
+
假设的「采用」**不在存储里**,而是三轴分离的证据链——store 只管评审,采用是假设与外部世界的关系:
|
|
411
|
+
|
|
412
|
+
- **评审轴(`creativity-hypotheses.jsonl`)**:`draft → active → validated / rejected`,假说自身的生命周期,由 `generate` / `ferment` 推进。
|
|
413
|
+
- **声称轴(`traces.jsonl`)**:`mio.creativity.adopt`(MCP `mio.creativity.adopt`)追加 `creativity.adopt` 事件,`payload.hypothesisId` 即声称;只写事件、绝不碰 memory,校验失败零写入。
|
|
414
|
+
- **证据轴(`memory.jsonl`)**:`mio memory record ... --hypothesis-id <uuid>`(MCP 参数 `hypothesisId`)把 `hypothesis:<uuid>` 注入记录 tags;`mio memory query --tags hypothesis:<uuid>` 即按标签召回。`AGENTS.md` 规范:**采用假设做决策时 `memory.record` 必带 `hypothesisId`**——这是把决策与假设挂上钩的唯一写入口。
|
|
415
|
+
|
|
416
|
+
`status` / `list` 在**读侧**把两轴 join 成派生计数(`mio.creativity.status` 返回):
|
|
417
|
+
|
|
418
|
+
```json
|
|
419
|
+
"adoption": {
|
|
420
|
+
"adopted": 2, "claimed": 3, "evidenced": 2,
|
|
421
|
+
"metric": false,
|
|
422
|
+
"note": "derived join of creativity.adopt events and hypothesis:<id> memory tags — informational; no rate or threshold by design",
|
|
423
|
+
"sources": {
|
|
424
|
+
"events": { "file": "traces.jsonl", "durable": true },
|
|
425
|
+
"memory": { "file": "memory.jsonl", "durable": true }
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
- `claimed` / `evidenced` 是两轴各自的 **distinct id 原始计数**(可以含已不在存储中的悬空 id);`adopted` = 两集合并集与存储假设 id 的交集——只有它回答「**存下来的**假设里哪些被采用过」。原始与 join 并报,悬空证据因此可见而不是被静默吞掉。
|
|
431
|
+
- **`metric: false` 是承重字段**:这是信息性派生 join,**不是** ADR-017 评估指标——没有 adoption rate、没有阈值。`sources` 照 `routeAdoption` 的模式注明每个轴的背书文件与 durable 性,于是「0 adopted(真没人用)」与「证据文件没留存(不可测)」能区分开。
|
|
432
|
+
- `list` 每行带 `adopted: boolean`,文本模式在状态后显示 `[adopted]` 标记;JSON 输出即引擎结果,MCP 与 CLI 两侧天然一致。
|
|
433
|
+
|
|
406
434
|
### 生成与发酵(`generate` / `ferment`)
|
|
407
435
|
|
|
408
436
|
这两个子命令会调用 LLM,`mio creativity` 同样支持它们——用的是**共享的 LLM 客户端**
|
package/bin/mio.js
CHANGED
|
@@ -23,7 +23,7 @@ const { createEvolutionCutoverTools } = require('../server/evolution-cutover.js'
|
|
|
23
23
|
const { createMemoryStore } = require('../server/memory-store.js')
|
|
24
24
|
const { createExperienceStore } = require('../server/experience-store.js')
|
|
25
25
|
const { createPolicyStore } = require('../server/policy-store.js')
|
|
26
|
-
const { CreativityEngine, sourcesFromInsights } = require('../server/creativity-engine.js')
|
|
26
|
+
const { CreativityEngine, sourcesFromInsights, adoptHypothesis } = require('../server/creativity-engine.js')
|
|
27
27
|
const { buildAutoSources, topUpSources } = require('../server/creativity-sources.js')
|
|
28
28
|
const { runIdeaGenerate, groundingFrom } = require('../server/idea-generate.js')
|
|
29
29
|
const { createAgentStore } = require('../server/agent-store.js')
|
|
@@ -678,18 +678,22 @@ function pruneCommand(args, useJson) {
|
|
|
678
678
|
function rememberCommand(args, useJson) {
|
|
679
679
|
const content = args[1]
|
|
680
680
|
if (!content) {
|
|
681
|
-
console.error('Usage: mio remember "<content>" [--kind decision|context|problem|note] [--tags a,b] [--scope project|global] [--project name] [--json]')
|
|
681
|
+
console.error('Usage: mio remember "<content>" [--kind decision|context|problem|note] [--tags a,b] [--hypothesis-id id] [--scope project|global] [--project name] [--json]')
|
|
682
682
|
process.exitCode = 1
|
|
683
683
|
return
|
|
684
684
|
}
|
|
685
685
|
const flags = args.slice(2)
|
|
686
686
|
const store = cliMemoryStore()
|
|
687
|
+
// optionValue returns the next token even if it is another flag; a bare
|
|
688
|
+
// trailing `--hypothesis-id` must not swallow `--json` as a "uuid".
|
|
689
|
+
const hypothesisId = optionValue(flags, '--hypothesis-id')
|
|
687
690
|
let record
|
|
688
691
|
try {
|
|
689
692
|
record = store.recordMemory({
|
|
690
693
|
content,
|
|
691
694
|
kind: optionValue(flags, '--kind'),
|
|
692
695
|
tags: splitTagsOption(flags, '--tags'),
|
|
696
|
+
hypothesisId: hypothesisId && !hypothesisId.startsWith('--') ? hypothesisId : undefined,
|
|
693
697
|
scope: optionValue(flags, '--scope'),
|
|
694
698
|
project: optionValue(flags, '--project'),
|
|
695
699
|
source: 'cli',
|
|
@@ -2066,11 +2070,12 @@ function observerCommand(args, useJson) {
|
|
|
2066
2070
|
|
|
2067
2071
|
function creativityUsage(write = console.log) {
|
|
2068
2072
|
write(`Usage:
|
|
2069
|
-
mio creativity status Show hypothesis counts
|
|
2073
|
+
mio creativity status Show hypothesis counts, recent top ideas, adoption join
|
|
2070
2074
|
mio creativity list List hypotheses (--status active|validated|rejected|draft, --sort novelty, --limit N)
|
|
2071
2075
|
|
|
2072
2076
|
mio creativity generate Generate hypotheses by combining sources (needs 2+ --source, calls an LLM)
|
|
2073
2077
|
mio creativity ferment Review/refine active hypotheses (calls an LLM)
|
|
2078
|
+
mio creativity adopt Record an adoption event for a stored hypothesis (writes traces only)
|
|
2074
2079
|
|
|
2075
2080
|
Options:
|
|
2076
2081
|
--status name Filter list by status
|
|
@@ -2098,6 +2103,8 @@ function printCreativityStatus(result) {
|
|
|
2098
2103
|
console.log('Creativity engine:')
|
|
2099
2104
|
console.log(` hypotheses: ${result.hypotheses} combos: ${result.combos} experiments: ${result.experiments}`)
|
|
2100
2105
|
console.log(` active: ${result.active} validated: ${result.validated} rejected: ${result.rejected} draft: ${result.draft}`)
|
|
2106
|
+
const adoption = result.adoption || { adopted: 0, claimed: 0, evidenced: 0 }
|
|
2107
|
+
console.log(` adoption: ${adoption.adopted} adopted (events=${adoption.claimed} tags=${adoption.evidenced}) — derived join, not a metric`)
|
|
2101
2108
|
if (result.recentIdeas.length === 0) {
|
|
2102
2109
|
console.log('No hypotheses yet.')
|
|
2103
2110
|
return
|
|
@@ -2118,7 +2125,7 @@ function printCreativityList(items) {
|
|
|
2118
2125
|
items.forEach((h, index) => {
|
|
2119
2126
|
const when = h.createdAt ? new Date(h.createdAt).toISOString().slice(0, 19).replace('T', ' ') : ''
|
|
2120
2127
|
const labels = Array.isArray(h.sourceLabels) && h.sourceLabels.length > 0 ? h.sourceLabels.join(' + ') : '—'
|
|
2121
|
-
console.log(`${index + 1}. [${h.status}] ${h.title} (N=${h.novelty} F=${h.feasibility} I=${h.impact} score=${h.score}${h.noveltyScore !== undefined ? ` noveltyScore=${h.noveltyScore}` : ''})`)
|
|
2128
|
+
console.log(`${index + 1}. [${h.status}]${h.adopted ? ' [adopted]' : ''} ${h.title} (N=${h.novelty} F=${h.feasibility} I=${h.impact} score=${h.score}${h.noveltyScore !== undefined ? ` noveltyScore=${h.noveltyScore}` : ''})`)
|
|
2122
2129
|
console.log(` ${labels} | ${when} | ${h.id}`)
|
|
2123
2130
|
if (h.fermentCount) console.log(` fermented ${h.fermentCount}x`)
|
|
2124
2131
|
})
|
|
@@ -2296,10 +2303,45 @@ function creativityFermentCommand(args, useJson) {
|
|
|
2296
2303
|
})
|
|
2297
2304
|
}
|
|
2298
2305
|
|
|
2306
|
+
// mio.creativity.adopt from the terminal: same shared adoptHypothesis the MCP
|
|
2307
|
+
// server calls, same enforcement (id must exist; --memory-id must already
|
|
2308
|
+
// carry the hypothesis:<id> tag), event written to MIO_HOME/traces.jsonl.
|
|
2309
|
+
function creativityAdoptCommand(args, useJson) {
|
|
2310
|
+
const flags = args.slice(2)
|
|
2311
|
+
const hypothesisId = flags[0] && !flags[0].startsWith('--') ? String(flags[0]).trim() : ''
|
|
2312
|
+
if (!hypothesisId) {
|
|
2313
|
+
console.error('mio creativity adopt requires <hypothesisId>')
|
|
2314
|
+
console.error('Usage: mio creativity adopt <hypothesisId> [--memory-id id] [--task-id id] [--note text] [--json]')
|
|
2315
|
+
process.exitCode = 1
|
|
2316
|
+
return
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
let result
|
|
2320
|
+
try {
|
|
2321
|
+
result = adoptHypothesis(
|
|
2322
|
+
cliCreativityEngine(),
|
|
2323
|
+
{
|
|
2324
|
+
hypothesisId,
|
|
2325
|
+
memoryId: optionValue(flags, '--memory-id'),
|
|
2326
|
+
taskId: optionValue(flags, '--task-id'),
|
|
2327
|
+
note: optionValue(flags, '--note'),
|
|
2328
|
+
},
|
|
2329
|
+
cliTaskStore().ingestObservation,
|
|
2330
|
+
MIO_HOME
|
|
2331
|
+
)
|
|
2332
|
+
} catch (error) {
|
|
2333
|
+
console.error(error.message || error)
|
|
2334
|
+
process.exitCode = 1
|
|
2335
|
+
return
|
|
2336
|
+
}
|
|
2337
|
+
if (useJson) return jsonOrText(result, true)
|
|
2338
|
+
console.log(`Adopted hypothesis ${result.hypothesisId}`)
|
|
2339
|
+
console.log(` event: ${result.event && result.event.trace_id ? result.event.trace_id : 'creativity.adopt'}`)
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2299
2342
|
function ideaUsage() {
|
|
2300
2343
|
console.error('usage: mio idea generate --goal "one line" [--context "..."] [--constraint "..."] [--num N] [--json]')
|
|
2301
2344
|
}
|
|
2302
|
-
|
|
2303
2345
|
async function ideaGenerateCommand(args, useJson) {
|
|
2304
2346
|
const flags = args.slice(2)
|
|
2305
2347
|
const goal = (optionValue(flags, '--goal') || '').trim()
|
|
@@ -2390,6 +2432,7 @@ function creativityCommand(args, useJson) {
|
|
|
2390
2432
|
}
|
|
2391
2433
|
if (sub === 'generate') return creativityGenerateCommand(args, useJson)
|
|
2392
2434
|
if (sub === 'ferment') return creativityFermentCommand(args, useJson)
|
|
2435
|
+
if (sub === 'adopt') return creativityAdoptCommand(args, useJson)
|
|
2393
2436
|
|
|
2394
2437
|
if (!['status', 'list'].includes(sub)) {
|
|
2395
2438
|
console.error(`Unknown creativity subcommand: ${sub}`)
|
|
@@ -3492,10 +3535,11 @@ Usage:
|
|
|
3492
3535
|
mio experience list List experience reuse (--status pending|confirmed|verified)
|
|
3493
3536
|
mio experience confirm --ids a,b Confirm auto-claimed reuse (bulk supported)
|
|
3494
3537
|
mio experience reuse --source-agent A --target-agent B --experience-id X Record a reuse
|
|
3495
|
-
mio creativity status Show creativity hypothesis counts
|
|
3538
|
+
mio creativity status Show creativity hypothesis counts, recent top ideas, adoption join
|
|
3496
3539
|
mio creativity list List creativity hypotheses (--status active|validated|rejected|draft, --sort novelty, --limit N)
|
|
3497
3540
|
mio creativity generate Generate hypotheses from 2+ --source "name|content" (or --from-insights; calls an LLM)
|
|
3498
3541
|
mio creativity ferment Review and refine active hypotheses (calls an LLM)
|
|
3542
|
+
mio creativity adopt Record an adoption event for a stored hypothesis (writes traces only)
|
|
3499
3543
|
mio idea generate --goal "..." Grounded idea pipeline: auto sources, novelty gate, persisted (calls an LLM)
|
|
3500
3544
|
mio insight status Insight counts: total, reported, unreported, high-value
|
|
3501
3545
|
mio insight list List insights (--unreported, --min-score N, --detector X, --limit N)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mio-agent-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Mio Agent Runtime installer and MCP intelligence layer",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@akemi-mio/evolution-strategy": "^0.1.0",
|
|
35
35
|
"@akemi-mio/evolution-safety": "^0.1.0",
|
|
36
36
|
"@akemi-mio/evolution-scheduler": "^0.1.0",
|
|
37
|
-
"@akemi-mio/observer": "^0.1.
|
|
37
|
+
"@akemi-mio/observer": "^0.1.5",
|
|
38
38
|
"@akemi-mio/insight": "^0.1.1",
|
|
39
39
|
"@akemi-mio/creativity": "^0.1.0"
|
|
40
40
|
},
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs')
|
|
4
4
|
const path = require('path')
|
|
5
5
|
const crypto = require('crypto')
|
|
6
|
+
const { readJsonl } = require('./memory-store.js')
|
|
6
7
|
const { evaluateNovelty } = require('@akemi-mio/creativity/NoveltyScorer')
|
|
7
8
|
|
|
8
9
|
// Cache validity key for a JSONL file: a (size, mtimeMs) pair, the same
|
|
@@ -257,8 +258,55 @@ function listRow(h) {
|
|
|
257
258
|
}
|
|
258
259
|
}
|
|
259
260
|
|
|
261
|
+
// --- read-side adoption join (idea b19bada4, design discussion 1d2e22e4 D3) ---
|
|
262
|
+
// The store never gains an `adopted` status (D1/D4, see adoptHypothesis at the
|
|
263
|
+
// bottom of this file): adoption is joined at READ time from the two evidence
|
|
264
|
+
// axes the write side owns -- an append-only `creativity.adopt` trace event
|
|
265
|
+
// (payload.hypothesisId = the CLAIM, written by mio.creativity.adopt) and a
|
|
266
|
+
// `hypothesis:<id>` tag on a memory record (the EVIDENCE, injected by
|
|
267
|
+
// mio.memory.record's hypothesisId parameter). The engine sits in
|
|
268
|
+
// <dataRoot>/creativity on both hosts (MCP: path.join(dataDir, 'creativity');
|
|
269
|
+
// CLI: path.join(MIO_HOME, 'creativity')), so the two evidence files are one
|
|
270
|
+
// dirname up -- the same dataRoot convention adoptHypothesis documents.
|
|
271
|
+
// Missing files are fine: readJsonl returns [] for them.
|
|
272
|
+
//
|
|
273
|
+
// The counts are informational joins, NOT ADR-017 metrics: no rate and no
|
|
274
|
+
// threshold by design (D3 -- adoption rate was explicitly out of scope).
|
|
275
|
+
// `sources` follows the routeAdoption pattern (evaluation-store.js): names
|
|
276
|
+
// each axis' backing file and that it is durable, so a reader can tell
|
|
277
|
+
// "0 adopted" apart from "evidence not kept". claimed/evidenced are raw
|
|
278
|
+
// distinct ids (they can include ids no longer in the store); adopted is the
|
|
279
|
+
// inner join against stored hypotheses, the only count that answers "which
|
|
280
|
+
// surviving hypotheses were adopted".
|
|
281
|
+
const ADOPTION_TAG_PREFIX = 'hypothesis:'
|
|
282
|
+
|
|
283
|
+
const ADOPTION_SOURCES = Object.freeze({
|
|
284
|
+
events: { file: 'traces.jsonl', durable: true },
|
|
285
|
+
memory: { file: 'memory.jsonl', durable: true },
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
function readAdoptionEvidence(rootDir) {
|
|
289
|
+
const claimed = new Set()
|
|
290
|
+
for (const record of readJsonl(path.join(rootDir, 'traces.jsonl'))) {
|
|
291
|
+
if (!record || record.event_type !== 'creativity.adopt') continue
|
|
292
|
+
const id = record.payload && record.payload.hypothesisId
|
|
293
|
+
if (id) claimed.add(String(id))
|
|
294
|
+
}
|
|
295
|
+
const evidenced = new Set()
|
|
296
|
+
for (const record of readJsonl(path.join(rootDir, 'memory.jsonl'))) {
|
|
297
|
+
if (!record || !Array.isArray(record.tags)) continue
|
|
298
|
+
for (const tag of record.tags) {
|
|
299
|
+
if (typeof tag !== 'string' || !tag.startsWith(ADOPTION_TAG_PREFIX)) continue
|
|
300
|
+
const id = tag.slice(ADOPTION_TAG_PREFIX.length).trim()
|
|
301
|
+
if (id) evidenced.add(id)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return { claimed, evidenced }
|
|
305
|
+
}
|
|
306
|
+
|
|
260
307
|
class CreativityEngine {
|
|
261
308
|
constructor(dataDir, chatJson) {
|
|
309
|
+
this.dataDir = dataDir
|
|
262
310
|
this.store = new CreativityStore(dataDir)
|
|
263
311
|
this.chatJson = chatJson
|
|
264
312
|
this.strategyIndex = 0
|
|
@@ -510,6 +558,20 @@ Output JSON with: title, idea, expectedBenefit, risk, novelty (0-100), feasibili
|
|
|
510
558
|
return { fermented: results.length, results, errors }
|
|
511
559
|
}
|
|
512
560
|
|
|
561
|
+
// Fresh per call: both evidence files are append-only and a long-lived MCP
|
|
562
|
+
// server must see new adopt events / memory tags without a restart (same
|
|
563
|
+
// rationale as the insight-store mtime cache -- minus the cache, since this
|
|
564
|
+
// is two small file reads on a read-only report path).
|
|
565
|
+
_adoption() {
|
|
566
|
+
const { claimed, evidenced } = readAdoptionEvidence(path.dirname(this.dataDir))
|
|
567
|
+
const storedIds = new Set(this.store.getHypotheses().map((h) => h.id))
|
|
568
|
+
const adoptedIds = new Set()
|
|
569
|
+
for (const id of [...claimed, ...evidenced]) {
|
|
570
|
+
if (storedIds.has(id)) adoptedIds.add(id)
|
|
571
|
+
}
|
|
572
|
+
return { claimed, evidenced, adoptedIds }
|
|
573
|
+
}
|
|
574
|
+
|
|
513
575
|
status() {
|
|
514
576
|
const count = this.store.count()
|
|
515
577
|
const active = this.store.getHypotheses({ status: 'active' })
|
|
@@ -518,6 +580,7 @@ Output JSON with: title, idea, expectedBenefit, risk, novelty (0-100), feasibili
|
|
|
518
580
|
// draft is a real state -- ferment() reviews draft + active -- so hiding it
|
|
519
581
|
// here made `status` and `list --status draft` disagree.
|
|
520
582
|
const draft = this.store.getHypotheses({ status: 'draft' })
|
|
583
|
+
const adoption = this._adoption()
|
|
521
584
|
|
|
522
585
|
return {
|
|
523
586
|
...count,
|
|
@@ -525,6 +588,19 @@ Output JSON with: title, idea, expectedBenefit, risk, novelty (0-100), feasibili
|
|
|
525
588
|
validated: validated.length,
|
|
526
589
|
rejected: rejected.length,
|
|
527
590
|
draft: draft.length,
|
|
591
|
+
// Derived join of the two evidence axes (see readAdoptionEvidence).
|
|
592
|
+
// metric:false is load-bearing: this is NOT an ADR-017 evaluation metric,
|
|
593
|
+
// so no rate and no threshold are reported -- adoption rate was explicitly
|
|
594
|
+
// out of scope (D3). sources names the backing files, routeAdoption style.
|
|
595
|
+
adoption: {
|
|
596
|
+
adopted: adoption.adoptedIds.size,
|
|
597
|
+
claimed: adoption.claimed.size,
|
|
598
|
+
evidenced: adoption.evidenced.size,
|
|
599
|
+
metric: false,
|
|
600
|
+
note:
|
|
601
|
+
'derived join of creativity.adopt events and hypothesis:<id> memory tags — informational; no rate or threshold by design',
|
|
602
|
+
sources: ADOPTION_SOURCES,
|
|
603
|
+
},
|
|
528
604
|
recentIdeas: active.slice(-5).map(h => ({
|
|
529
605
|
id: h.id,
|
|
530
606
|
title: h.title,
|
|
@@ -544,6 +620,11 @@ Output JSON with: title, idea, expectedBenefit, risk, novelty (0-100), feasibili
|
|
|
544
620
|
// result. `limit: 0` (and only 0) opts back into "everything".
|
|
545
621
|
list(opts = {}) {
|
|
546
622
|
const limit = opts.limit === undefined ? 20 : opts.limit
|
|
623
|
+
// Same read-side join as status(): every row carries `adopted` so the two
|
|
624
|
+
// surfaces cannot disagree (the CLI/MCP entry points already share this
|
|
625
|
+
// engine). listRow() stays the pure store-row shape; decoration is here.
|
|
626
|
+
const adoption = this._adoption()
|
|
627
|
+
const decorate = (row) => ({ ...row, adopted: adoption.adoptedIds.has(row.id) })
|
|
547
628
|
if (opts.sort === 'novelty') {
|
|
548
629
|
// Relative novelty ranking (read-only, nothing written back). Pairwise
|
|
549
630
|
// Jaccard is O(n²) — bound candidates to the newest 200 (spec Risk 3).
|
|
@@ -554,9 +635,13 @@ Output JSON with: title, idea, expectedBenefit, risk, novelty (0-100), feasibili
|
|
|
554
635
|
return { ...listRow(h), noveltyScore: verdict.adjustedNovelty }
|
|
555
636
|
})
|
|
556
637
|
rows.sort((a, b) => b.noveltyScore - a.noveltyScore || (b.createdAt || 0) - (a.createdAt || 0))
|
|
557
|
-
|
|
638
|
+
const decorated = rows.map(decorate)
|
|
639
|
+
return limit === 0 ? decorated : decorated.slice(0, limit)
|
|
558
640
|
}
|
|
559
|
-
return this.store
|
|
641
|
+
return this.store
|
|
642
|
+
.getHypotheses({ status: opts.status, limit })
|
|
643
|
+
.map(listRow)
|
|
644
|
+
.map(decorate)
|
|
560
645
|
}
|
|
561
646
|
|
|
562
647
|
_buildGenerationPrompt(a, b, strategy, recentHypotheses, rejectedHypotheses) {
|
|
@@ -605,4 +690,69 @@ Output JSON with: title, idea, expectedBenefit, risk, novelty (0-100), feasibili
|
|
|
605
690
|
}
|
|
606
691
|
}
|
|
607
692
|
|
|
608
|
-
|
|
693
|
+
// Adoption evidence (idea b19bada4, design discussion 1d2e22e4 D1/D4): the
|
|
694
|
+
// store never gains an `adopted` status -- adoption is a relation between a
|
|
695
|
+
// stored hypothesis and the outside world, not an attribute of the hypothesis,
|
|
696
|
+
// so it lives as an append-only `creativity.adopt` trace event, optionally
|
|
697
|
+
// cross-checked against a memory record carrying the `hypothesis:<id>` tag.
|
|
698
|
+
// The host (MCP or CLI) passes its own ingestObservation so both entry points
|
|
699
|
+
// write byte-identical events; dataRoot is the directory holding memory.jsonl
|
|
700
|
+
// and traces.jsonl -- the engine sits in <dataRoot>/creativity on both hosts
|
|
701
|
+
// (see cliCreativityEngine in bin/mio.js). Every validation failure happens
|
|
702
|
+
// before ingest, so a rejected call never pollutes the event stream. This
|
|
703
|
+
// function writes traces only: it never touches memory.jsonl (the
|
|
704
|
+
// generate/ferment write nothing to Memory boundary extends here).
|
|
705
|
+
function adoptHypothesis(engine, args = {}, ingest, dataRoot) {
|
|
706
|
+
const hypothesisId = String(args.hypothesisId || '').trim()
|
|
707
|
+
if (!hypothesisId) {
|
|
708
|
+
throw new Error('mio.creativity.adopt requires hypothesisId: a stored hypothesis UUID')
|
|
709
|
+
}
|
|
710
|
+
if (!engine.store.getHypotheses().some((h) => h.id === hypothesisId)) {
|
|
711
|
+
throw new Error(`mio.creativity.adopt: no stored hypothesis with id "${hypothesisId}"`)
|
|
712
|
+
}
|
|
713
|
+
const memoryId = String(args.memoryId || '').trim()
|
|
714
|
+
if (memoryId) {
|
|
715
|
+
const memoryPath = path.join(dataRoot, 'memory.jsonl')
|
|
716
|
+
const records = fs.existsSync(memoryPath)
|
|
717
|
+
? fs
|
|
718
|
+
.readFileSync(memoryPath, 'utf-8')
|
|
719
|
+
.split('\n')
|
|
720
|
+
.filter(Boolean)
|
|
721
|
+
.map((line) => {
|
|
722
|
+
try { return JSON.parse(line) } catch { return null }
|
|
723
|
+
})
|
|
724
|
+
.filter(Boolean)
|
|
725
|
+
: []
|
|
726
|
+
const record = records.find((r) => r && r.id === memoryId)
|
|
727
|
+
if (!record) {
|
|
728
|
+
throw new Error(`mio.creativity.adopt: no memory record with id "${memoryId}"`)
|
|
729
|
+
}
|
|
730
|
+
const tag = `hypothesis:${hypothesisId}`
|
|
731
|
+
if (!Array.isArray(record.tags) || !record.tags.includes(tag)) {
|
|
732
|
+
throw new Error(`mio.creativity.adopt: memory record "${memoryId}" is missing tag "${tag}"`)
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (typeof ingest !== 'function') {
|
|
736
|
+
throw new Error('mio.creativity.adopt: ingest is not available on this host')
|
|
737
|
+
}
|
|
738
|
+
const taskId = String(args.taskId || '').trim()
|
|
739
|
+
const note = String(args.note || '').trim()
|
|
740
|
+
const result = ingest({
|
|
741
|
+
trace_id: `creativity-adopt:${hypothesisId}:${Date.now()}`,
|
|
742
|
+
event_type: 'creativity.adopt',
|
|
743
|
+
outcome: 'success',
|
|
744
|
+
payload: {
|
|
745
|
+
hypothesisId,
|
|
746
|
+
...(memoryId ? { memoryId } : {}),
|
|
747
|
+
...(taskId ? { taskId } : {}),
|
|
748
|
+
...(note ? { note } : {}),
|
|
749
|
+
},
|
|
750
|
+
})
|
|
751
|
+
return {
|
|
752
|
+
recorded: true,
|
|
753
|
+
hypothesisId,
|
|
754
|
+
event: (result && result.event) || result,
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
module.exports = { CreativityEngine, CreativityStore, sourcesFromInsights, isoOf, adoptHypothesis }
|
package/server/memory-store.js
CHANGED
|
@@ -518,12 +518,23 @@ function createMemoryStore(options = {}) {
|
|
|
518
518
|
const content = String(args.content || '').trim()
|
|
519
519
|
if (!content) throw new Error('memory.record requires a non-empty content')
|
|
520
520
|
const scope = normalizeScope(args.scope)
|
|
521
|
+
// Adoption evidence (idea b19bada4, task 2261a5f4): hypothesisId is pure
|
|
522
|
+
// sugar for the exact tag `mio.creativity.adopt` enforces — the record
|
|
523
|
+
// joins the creativity hypothesis through the same `hypothesis:<uuid>`
|
|
524
|
+
// tag the tags AND-filter already searches. Omitted, the record keeps
|
|
525
|
+
// the exact legacy shape: no extra field, tags byte-identical.
|
|
526
|
+
const hypothesisId = String(args.hypothesisId || '').trim()
|
|
527
|
+
const tags = normalizeTags(args.tags)
|
|
528
|
+
if (hypothesisId) {
|
|
529
|
+
const tag = `hypothesis:${hypothesisId}`
|
|
530
|
+
if (!tags.includes(tag)) tags.push(tag)
|
|
531
|
+
}
|
|
521
532
|
const record = {
|
|
522
533
|
id: createId('mem'),
|
|
523
534
|
timestamp: new Date().toISOString(),
|
|
524
535
|
kind: args.kind || 'note',
|
|
525
536
|
content,
|
|
526
|
-
tags
|
|
537
|
+
tags,
|
|
527
538
|
project: scope === 'global' ? null : args.project || projectName(),
|
|
528
539
|
scope,
|
|
529
540
|
source: args.source || agentId() || 'mcp',
|
|
@@ -20,7 +20,7 @@ const { createSubscriptionStore } = require('../subscription-store.js')
|
|
|
20
20
|
const { createDigest } = require('../digest.js')
|
|
21
21
|
const { createEvolutionReport } = require('../evolution-report.js')
|
|
22
22
|
|
|
23
|
-
const { CreativityEngine, sourcesFromInsights } = require('../creativity-engine.js')
|
|
23
|
+
const { CreativityEngine, sourcesFromInsights, adoptHypothesis } = require('../creativity-engine.js')
|
|
24
24
|
const { buildAutoSources, topUpSources } = require('../creativity-sources.js')
|
|
25
25
|
const { runIdeaGenerate, groundingFrom } = require('../idea-generate.js')
|
|
26
26
|
// The observer and insight engines are optional dependencies. Their stores own
|
|
@@ -351,6 +351,11 @@ const TOOLS = [
|
|
|
351
351
|
content: { type: 'string', description: 'Memory content.' },
|
|
352
352
|
kind: { type: 'string', description: 'Memory kind, such as decision, context, or problem.' },
|
|
353
353
|
tags: { type: 'array', items: { type: 'string' }, description: 'Optional tags.' },
|
|
354
|
+
hypothesisId: {
|
|
355
|
+
type: 'string',
|
|
356
|
+
description:
|
|
357
|
+
'Optional creativity hypothesis UUID. Injects the tag "hypothesis:<id>" so this record joins that hypothesis (the tag mio.creativity.adopt enforces); search it back via memory.query tags.',
|
|
358
|
+
},
|
|
354
359
|
project: { type: 'string', description: 'Project name. Defaults to current directory name.' },
|
|
355
360
|
scope: { type: 'string', description: 'Record scope: project (default) or global.' },
|
|
356
361
|
source: { type: 'string', description: 'Recording source. Defaults to mcp.' },
|
|
@@ -696,12 +701,12 @@ const TOOLS = [
|
|
|
696
701
|
},
|
|
697
702
|
{
|
|
698
703
|
name: 'mio.creativity.status',
|
|
699
|
-
description: 'Show creativity engine status: hypothesis counts, active/validated/rejected/draft, recent top ideas.',
|
|
704
|
+
description: 'Show creativity engine status: hypothesis counts, active/validated/rejected/draft, recent top ideas, plus the adoption evidence join — adopted/claimed/evidenced counts with sources (informational, NOT a metric; derived from creativity.adopt trace events and hypothesis:<id> memory tags).',
|
|
700
705
|
inputSchema: { type: 'object', properties: {} },
|
|
701
706
|
},
|
|
702
707
|
{
|
|
703
708
|
name: 'mio.creativity.list',
|
|
704
|
-
description: 'List creativity hypotheses with optional status filter. Newest 20 by default; pass limit: 0 for everything.',
|
|
709
|
+
description: 'List creativity hypotheses with optional status filter. Newest 20 by default; pass limit: 0 for everything. Each row carries adopted: true when a creativity.adopt event or a hypothesis:<id> memory tag exists for its id (read-side evidence join, informational).',
|
|
705
710
|
inputSchema: {
|
|
706
711
|
type: 'object',
|
|
707
712
|
properties: {
|
|
@@ -749,6 +754,21 @@ const TOOLS = [
|
|
|
749
754
|
},
|
|
750
755
|
},
|
|
751
756
|
},
|
|
757
|
+
{
|
|
758
|
+
name: 'mio.creativity.adopt',
|
|
759
|
+
description:
|
|
760
|
+
'Record that a stored creativity hypothesis was adopted: validates the hypothesis id, then appends a creativity.adopt trace event (who/when/what). Optionally enforces that the referenced memory record carries the hypothesis:<id> tag. Writes traces only — never Mio memory, never hypothesis status.',
|
|
761
|
+
inputSchema: {
|
|
762
|
+
type: 'object',
|
|
763
|
+
properties: {
|
|
764
|
+
hypothesisId: { type: 'string', description: 'UUID of a stored hypothesis (creativity-hypotheses.jsonl). Must exist or the call fails.' },
|
|
765
|
+
memoryId: { type: 'string', description: 'Optional memory record id. If given, the record must exist and carry the tag "hypothesis:<hypothesisId>" or the call fails.' },
|
|
766
|
+
taskId: { type: 'string', description: 'Optional task id this adoption feeds.' },
|
|
767
|
+
note: { type: 'string', description: 'Optional short rationale for the adoption.' },
|
|
768
|
+
},
|
|
769
|
+
required: ['hypothesisId'],
|
|
770
|
+
},
|
|
771
|
+
},
|
|
752
772
|
{
|
|
753
773
|
name: 'mio.idea.generate',
|
|
754
774
|
description:
|
|
@@ -1116,6 +1136,8 @@ async function callTool(name, args = {}) {
|
|
|
1116
1136
|
return creativityEngine.generate(generationSources(args), args.strategy)
|
|
1117
1137
|
case 'mio.creativity.ferment':
|
|
1118
1138
|
return creativityEngine.ferment(args.limit)
|
|
1139
|
+
case 'mio.creativity.adopt':
|
|
1140
|
+
return adoptHypothesis(creativityEngine, args, ingestObservation, dataDir)
|
|
1119
1141
|
case 'mio.idea.generate': {
|
|
1120
1142
|
const goal = typeof args.goal === 'string' ? args.goal.trim() : ''
|
|
1121
1143
|
if (!goal) throw new Error('mio.idea.generate requires goal: a non-empty string')
|