mio-agent-runtime 0.13.5 → 0.14.1
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 -9
- package/bin/mio.js +130 -3
- package/package.json +4 -2
- package/scripts/link-workspace.cjs +52 -0
- package/server/creativity-engine.js +97 -62
- package/server/creativity-sources.js +186 -0
- package/server/idea-generate.js +136 -0
- package/server/mio-intelligence-mcp/index.js +85 -9
package/README.md
CHANGED
|
@@ -57,6 +57,7 @@ mio creativity status Show creativity hypothesis counts and recent top id
|
|
|
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 idea generate --goal "..." Grounded idea pipeline: auto sources + novelty gate, persisted (calls an LLM)
|
|
60
61
|
mio insight status Insight counts: total, reported, unreported, high-value (needs @akemi-mio/insight)
|
|
61
62
|
mio insight list List insights (--unreported, --min-score N, --detector X, --limit N)
|
|
62
63
|
mio insight generate Generate insights from context (--memory "kind|content"/--summary "text"; calls an LLM)
|
|
@@ -101,7 +102,7 @@ mio --version Print the version and exit (-V)
|
|
|
101
102
|
|
|
102
103
|
## MCP 工具
|
|
103
104
|
|
|
104
|
-
MCP 服务端在 5 大域共暴露
|
|
105
|
+
MCP 服务端在 5 大域共暴露 51 个工具:
|
|
105
106
|
|
|
106
107
|
### 记忆(11)
|
|
107
108
|
`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`
|
|
@@ -114,15 +115,15 @@ MCP 服务端在 5 大域共暴露 50 个工具:
|
|
|
114
115
|
### 洞察自省(4)
|
|
115
116
|
`mio.insight.status` · `mio.insight.list` · `mio.insight.mark_reported` · `mio.insight.generate`
|
|
116
117
|
|
|
117
|
-
### 创意引擎(
|
|
118
|
-
`mio.creativity.generate` · `mio.creativity.ferment` · `mio.creativity.list` · `mio.creativity.status`
|
|
118
|
+
### 创意引擎(5)
|
|
119
|
+
`mio.creativity.generate` · `mio.creativity.ferment` · `mio.creativity.list` · `mio.creativity.status` · `mio.idea.generate`
|
|
119
120
|
|
|
120
121
|
### 任务、Agent 与演化(16)
|
|
121
122
|
`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`
|
|
122
123
|
|
|
123
124
|
## 包
|
|
124
125
|
|
|
125
|
-
`mio-agent-runtime` 依赖发布在 `@akemi-mio` scope 下的 **
|
|
126
|
+
`mio-agent-runtime` 依赖发布在 `@akemi-mio` scope 下的 **10** 个包(声明于 `package.json`,均为 `0.1.0`)。它们仅在源码变更时才发布,所以普通的 CLI 发布不会重新发布这些包。
|
|
126
127
|
|
|
127
128
|
| 依赖 | 说明 |
|
|
128
129
|
|---|---|
|
|
@@ -135,6 +136,7 @@ MCP 服务端在 5 大域共暴露 50 个工具:
|
|
|
135
136
|
| `@akemi-mio/evolution-scheduler` | 演化调度器 |
|
|
136
137
|
| `@akemi-mio/observer` | ObserverService —— 多源数据采集、趋势分析、深度研究、世界模型、发酵、DAG 状态机、自演化引擎(唯一运行时依赖 `undici`,只用于走代理采集) |
|
|
137
138
|
| `@akemi-mio/insight` | InsightService —— 基于 LLM 的洞察生成、冲突/漂移/重复/停滞目标/摩擦检测器、在场服务、洞察打分(零 npm 依赖) |
|
|
139
|
+
| `@akemi-mio/creativity` | IdeaGenerator / NoveltyScorer / SourceAggregator —— `mio idea generate` 与创意引擎的生成算法(依赖 `@akemi-mio/core`) |
|
|
138
140
|
|
|
139
141
|
以下 `@akemi-mio` 包同样位于本 monorepo 中并独立发布,但**不是**本 CLI 的依赖——其源码从不 import 它们,因此未声明在 `package.json` 里:
|
|
140
142
|
|
|
@@ -146,9 +148,9 @@ MCP 服务端在 5 大域共暴露 50 个工具:
|
|
|
146
148
|
|
|
147
149
|
它们可直接被其它 Node 项目消费。`mio-agent-runtime` 自身是 CLI + MCP server 包。
|
|
148
150
|
|
|
149
|
-
###
|
|
151
|
+
### 曾经不发布的两个包(现已发布)
|
|
150
152
|
|
|
151
|
-
`@akemi-mio/core` 与 `@akemi-mio/creativity`
|
|
153
|
+
`@akemi-mio/core` 与 `@akemi-mio/creativity` 自 `mio-agent-runtime@0.14.0` 起发布到 npm(`0.1.0`)。`@akemi-mio/creativity` 是本 CLI 的直接依赖,`@akemi-mio/core` 经由它传递引入;其它 Node 项目可直接安装这两个包。
|
|
152
154
|
|
|
153
155
|
## 宿主说明
|
|
154
156
|
|
|
@@ -414,13 +416,20 @@ mio creativity ferment --limit 3
|
|
|
414
416
|
|
|
415
417
|
- `--source "名称|内容"` **至少两个**:引擎是把概念两两配对来产生新假设的,
|
|
416
418
|
一个来源在构造上就不可能产出组合(此时**不会**调用 LLM,直接返回
|
|
417
|
-
`need at least 2 sources
|
|
419
|
+
`need at least 2 sources`)。显式来源不足 2 个时,本地自动来源(memory / traces / 已存假设;MCP 侧还含 insight 与 observer 趋势)会先把列表补齐到 2——显式 `--source` 永远排在前面且不被替换;补齐后仍不足 2 个才报上面的错。
|
|
418
420
|
- **`--from-insights`(MCP 侧为 `fromInsights: true`)用已存洞察补足概念来源**:
|
|
419
421
|
读取 `mio insight list` 能看到的同一份存储,按分数取前 10 条映射成
|
|
420
422
|
`{ name, content, type: 'insight' }`,再与显式的 `--source` 合并。它只在
|
|
421
423
|
`@akemi-mio/insight` 已安装且至少存有一条洞察时有意义,否则明确报错而不是
|
|
422
424
|
静默当作「没有来源」。因为来源可由它提供,`mio.creativity.generate` 的
|
|
423
425
|
`sources` 不再是 schema 必填项。
|
|
426
|
+
- **判重粒度是(素材对 × `--strategy`)。** 同一对素材在 `explore` / `signal` /
|
|
427
|
+
`stable` 下各算一次实验——「409 就换 strategy 重试」是有效的降级而不是空转;
|
|
428
|
+
三种 strategy 都试过、且没有新素材时才真正耗尽,此时返回的 `reason` 会带上
|
|
429
|
+
strategy、来源数与候选对数(外加 `strategy` 与 `pairsAvailable: 0` 字段),
|
|
430
|
+
调用方可以据此区分「换个 strategy 有用」与「该补新素材了」。素材身份取名称
|
|
431
|
+
去空白后的**前 120 个字符**(`--source` 省略 `|` 时 name 会退化成整段描述,
|
|
432
|
+
描述尾部微调不得绕过判重);没有 `strategy` 字段的旧记录仍挡住所有 strategy。
|
|
424
433
|
- `ferment` 会复核 `active` / `draft` 假设并更新分数;`verdict=promote` 且总分 > 200 时
|
|
425
434
|
升为 `validated`,`verdict=reject` 则标记为 `rejected`。
|
|
426
435
|
- **失败的 LLM 调用会出现在结果里,不再被吞掉。** 返回值带 `errors`
|
|
@@ -428,10 +437,24 @@ mio creativity ferment --limit 3
|
|
|
428
437
|
打印 `N pair(s) failed:`。此前 `catch {}` 让「端点挂了」与「这一对确实没有
|
|
429
438
|
新意」长得一模一样——`ideas: []` / `Nothing fermented` 两种输出都无法区分。
|
|
430
439
|
|
|
440
|
+
### 目标驱动的 idea 流水线(`mio idea generate`)
|
|
441
|
+
|
|
442
|
+
一个 goal 直接产出可实验的假设(MCP 面为 `mio.idea.generate`),来源全程本地优先组装:
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
mio idea generate --goal "降低 MCP 调用延迟" --context "近三天 p95 上升" --constraint "不引入新依赖"
|
|
446
|
+
mio idea generate --goal "扩大命令覆盖" --json
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
- **来源顺序**:goal → context → constraints → 记忆 grounding(取前 3 条,`scope: all`)→ 本地自动来源(memory / traces / 已存假设,MCP 侧还含 insight 与 observer 趋势),按 `origin` 去重;凑不足 2 个时返回 `reason` 文案而不是报错。
|
|
450
|
+
- **每条 idea 都持久化**到 `<MIO_HOME>/creativity/creativity-hypotheses.jsonl`(`draft`,带 `provenance`:strategy / technique / relatedMemoryIds / 命中来源 / generatedAt),**绝不写入 Mio memory**。
|
|
451
|
+
- **novelty 门禁**:与已 `rejected` 假设近似重复的候选落盘为 `rejected` 并带 `rejectionReason`;与近期假设相似的只降分。拒绝的假设同样入库,供后续去重。
|
|
452
|
+
- `--num` 钳制在 1..3(默认 3);`--json` 返回完整结构 `{ ideas, generatedAt, groundedWith, persistedIds }`。
|
|
453
|
+
|
|
431
454
|
#### LLM 配置
|
|
432
455
|
|
|
433
|
-
只有
|
|
434
|
-
`mio insight generate`、`mio observer ferment`。它们共用
|
|
456
|
+
只有 5 条命令会调用大模型:`mio creativity generate` / `ferment`、
|
|
457
|
+
`mio idea generate`、`mio insight generate`、`mio observer ferment`。它们共用
|
|
435
458
|
`server/llm-client.js` 这一份实现,配置也共用同一套解析顺序:
|
|
436
459
|
|
|
437
460
|
```
|
package/bin/mio.js
CHANGED
|
@@ -24,6 +24,8 @@ 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
26
|
const { CreativityEngine, sourcesFromInsights } = require('../server/creativity-engine.js')
|
|
27
|
+
const { buildAutoSources, topUpSources } = require('../server/creativity-sources.js')
|
|
28
|
+
const { runIdeaGenerate, groundingFrom } = require('../server/idea-generate.js')
|
|
27
29
|
const { createAgentStore } = require('../server/agent-store.js')
|
|
28
30
|
const { createEvaluationStore } = require('../server/evaluation-store.js')
|
|
29
31
|
const { createInsightStore, isInsightAvailable } = require('../server/insight-store.js')
|
|
@@ -2065,7 +2067,7 @@ function observerCommand(args, useJson) {
|
|
|
2065
2067
|
function creativityUsage(write = console.log) {
|
|
2066
2068
|
write(`Usage:
|
|
2067
2069
|
mio creativity status Show hypothesis counts and recent top ideas
|
|
2068
|
-
mio creativity list List hypotheses (--status active|validated|rejected|draft, --limit N)
|
|
2070
|
+
mio creativity list List hypotheses (--status active|validated|rejected|draft, --sort novelty, --limit N)
|
|
2069
2071
|
|
|
2070
2072
|
mio creativity generate Generate hypotheses by combining sources (needs 2+ --source, calls an LLM)
|
|
2071
2073
|
mio creativity ferment Review/refine active hypotheses (calls an LLM)
|
|
@@ -2073,6 +2075,7 @@ function creativityUsage(write = console.log) {
|
|
|
2073
2075
|
Options:
|
|
2074
2076
|
--status name Filter list by status
|
|
2075
2077
|
--limit N Max results for list / ferment (default 20 / 5); --limit 0 lists everything
|
|
2078
|
+
--sort novelty Rank list by relative noveltyScore (a dedup signal, not a quality score)
|
|
2076
2079
|
--source "name|content" A concept source for generate; repeat 2+ times
|
|
2077
2080
|
--from-insights Seed generate's sources from stored insights (needs @akemi-mio/insight)
|
|
2078
2081
|
--strategy explore|signal|stable Generation strategy (auto-selected if omitted)
|
|
@@ -2081,6 +2084,7 @@ Options:
|
|
|
2081
2084
|
Examples:
|
|
2082
2085
|
mio creativity status
|
|
2083
2086
|
mio creativity list --status rejected --limit 10
|
|
2087
|
+
mio creativity list --sort novelty
|
|
2084
2088
|
mio creativity generate --source "auth|token rotation" --source "cache|write-through"
|
|
2085
2089
|
mio creativity generate --from-insights --source "auth|token rotation"
|
|
2086
2090
|
|
|
@@ -2114,7 +2118,7 @@ function printCreativityList(items) {
|
|
|
2114
2118
|
items.forEach((h, index) => {
|
|
2115
2119
|
const when = h.createdAt ? new Date(h.createdAt).toISOString().slice(0, 19).replace('T', ' ') : ''
|
|
2116
2120
|
const labels = Array.isArray(h.sourceLabels) && h.sourceLabels.length > 0 ? h.sourceLabels.join(' + ') : '—'
|
|
2117
|
-
console.log(`${index + 1}. [${h.status}] ${h.title} (N=${h.novelty} F=${h.feasibility} I=${h.impact} score=${h.score})`)
|
|
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}` : ''})`)
|
|
2118
2122
|
console.log(` ${labels} | ${when} | ${h.id}`)
|
|
2119
2123
|
if (h.fermentCount) console.log(` fermented ${h.fermentCount}x`)
|
|
2120
2124
|
})
|
|
@@ -2162,6 +2166,26 @@ function printCreativityErrors(errors, label) {
|
|
|
2162
2166
|
}
|
|
2163
2167
|
}
|
|
2164
2168
|
|
|
2169
|
+
// Auto sources for generate's top-up: the same local providers the MCP server
|
|
2170
|
+
// uses, assembled from the CLI's global MIO_HOME (spec Capability 2). Every
|
|
2171
|
+
// layer degrades to [] — a missing optional package or an unreadable store
|
|
2172
|
+
// must never fail `mio creativity generate`.
|
|
2173
|
+
function cliAutoSources() {
|
|
2174
|
+
let insights = []
|
|
2175
|
+
let trends = []
|
|
2176
|
+
try {
|
|
2177
|
+
if (isInsightAvailable()) insights = cliInsightStore().list({}) || []
|
|
2178
|
+
} catch (_) {}
|
|
2179
|
+
try {
|
|
2180
|
+
if (isObserverAvailable()) trends = cliObserverStore().trends({ limit: 3 }) || []
|
|
2181
|
+
} catch (_) {}
|
|
2182
|
+
try {
|
|
2183
|
+
return buildAutoSources({ dataDir: MIO_HOME, insights, trends })
|
|
2184
|
+
} catch (_) {
|
|
2185
|
+
return []
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2165
2189
|
function creativityGenerateCommand(args, useJson) {
|
|
2166
2190
|
const flags = args.slice(2)
|
|
2167
2191
|
let sources = parseSources(flags)
|
|
@@ -2191,6 +2215,15 @@ function creativityGenerateCommand(args, useJson) {
|
|
|
2191
2215
|
sources = sources.concat(seeds)
|
|
2192
2216
|
}
|
|
2193
2217
|
|
|
2218
|
+
// Auto top-up (spec Capability 2, injection point 2): explicit --source
|
|
2219
|
+
// entries win; when fewer than two remain, local memory/traces/stored
|
|
2220
|
+
// hypotheses (plus insight/observer observations) fill the gap. If the
|
|
2221
|
+
// topped-up set is still short, the errors below keep their old wording.
|
|
2222
|
+
if (sources.length < 2) {
|
|
2223
|
+
const topped = topUpSources(sources, cliAutoSources())
|
|
2224
|
+
if (topped.length >= 2) sources = topped
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2194
2227
|
if (sources.length < 2) {
|
|
2195
2228
|
if (flagPresent(flags, '--from-insights')) {
|
|
2196
2229
|
console.error(`mio creativity generate needs at least 2 concept sources; --from-insights only supplied ${sources.length}.`)
|
|
@@ -2263,6 +2296,90 @@ function creativityFermentCommand(args, useJson) {
|
|
|
2263
2296
|
})
|
|
2264
2297
|
}
|
|
2265
2298
|
|
|
2299
|
+
function ideaUsage() {
|
|
2300
|
+
console.error('usage: mio idea generate --goal "one line" [--context "..."] [--constraint "..."] [--num N] [--json]')
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
async function ideaGenerateCommand(args, useJson) {
|
|
2304
|
+
const flags = args.slice(2)
|
|
2305
|
+
const goal = (optionValue(flags, '--goal') || '').trim()
|
|
2306
|
+
if (!goal) {
|
|
2307
|
+
console.error('mio idea generate requires --goal "..."')
|
|
2308
|
+
ideaUsage()
|
|
2309
|
+
process.exitCode = 1
|
|
2310
|
+
return
|
|
2311
|
+
}
|
|
2312
|
+
const context = optionValue(flags, '--context') || ''
|
|
2313
|
+
const constraints = optionValues(flags, '--constraint')
|
|
2314
|
+
let numIdeas
|
|
2315
|
+
try {
|
|
2316
|
+
numIdeas = parseNumberOption(flags, '--num')
|
|
2317
|
+
} catch (error) {
|
|
2318
|
+
console.error(error.message || error)
|
|
2319
|
+
ideaUsage()
|
|
2320
|
+
process.exitCode = 1
|
|
2321
|
+
return
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
// Grounding from the CLI's global MIO_HOME. A failed query degrades to "no
|
|
2325
|
+
// grounding" — never a hard error (spec Capability 1: local-first, tolerant).
|
|
2326
|
+
// The core logger echoes every event to stdout; in --json mode that would
|
|
2327
|
+
// corrupt the payload jsonOrText must own, so the echo is routed to stderr
|
|
2328
|
+
// while the pipeline runs (executed deviation — the plan assumed pure JSON).
|
|
2329
|
+
const echoLog = useJson ? console.log : null
|
|
2330
|
+
if (useJson) console.log = (...params) => console.error(...params)
|
|
2331
|
+
|
|
2332
|
+
let result
|
|
2333
|
+
try {
|
|
2334
|
+
let grounding = { sources: [], relatedMemoryIds: [], groundedWith: 0 }
|
|
2335
|
+
try {
|
|
2336
|
+
const qr = await cliMemoryStore().queryMemory({ query: goal, limit: 3, scope: 'all' })
|
|
2337
|
+
grounding = groundingFrom(qr)
|
|
2338
|
+
} catch (_) {}
|
|
2339
|
+
|
|
2340
|
+
if (!useJson) warnIfLlmUnconfigured()
|
|
2341
|
+
|
|
2342
|
+
result = await runIdeaGenerate({
|
|
2343
|
+
goal,
|
|
2344
|
+
context,
|
|
2345
|
+
constraints,
|
|
2346
|
+
numIdeas,
|
|
2347
|
+
chatJson,
|
|
2348
|
+
store: cliCreativityEngine().store,
|
|
2349
|
+
groundingSources: grounding.sources,
|
|
2350
|
+
relatedMemoryIds: grounding.relatedMemoryIds,
|
|
2351
|
+
autoSources: cliAutoSources(),
|
|
2352
|
+
})
|
|
2353
|
+
} catch (error) {
|
|
2354
|
+
console.error(error.message || error)
|
|
2355
|
+
process.exitCode = 1
|
|
2356
|
+
return
|
|
2357
|
+
} finally {
|
|
2358
|
+
if (useJson) console.log = echoLog
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
if (useJson) return jsonOrText(result, true)
|
|
2362
|
+
if (result.reason) {
|
|
2363
|
+
console.log(`No ideas generated: ${result.reason}`)
|
|
2364
|
+
return
|
|
2365
|
+
}
|
|
2366
|
+
console.log(`Generated ${result.ideas.length} idea(s) (grounded with ${result.groundedWith} memories)`)
|
|
2367
|
+
result.ideas.forEach((idea) => {
|
|
2368
|
+
console.log(`- [${idea.hypothesis.status}] ${idea.title}`)
|
|
2369
|
+
console.log(` novelty=${idea.hypothesis.novelty} feasibility=${idea.hypothesis.feasibility} impact=${idea.hypothesis.impact} | ${idea.hypothesis.id}`)
|
|
2370
|
+
if (idea.provenance && idea.provenance.technique) console.log(` technique=${idea.provenance.technique}`)
|
|
2371
|
+
if (idea.hypothesis.rejectionReason) console.log(` rejected: ${idea.hypothesis.rejectionReason}`)
|
|
2372
|
+
})
|
|
2373
|
+
console.log(`Persisted ${result.persistedIds.length} idea(s)`)
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
function ideaCommand(args, useJson) {
|
|
2377
|
+
const sub = args[1]
|
|
2378
|
+
if (sub === 'generate') return ideaGenerateCommand(args, useJson)
|
|
2379
|
+
ideaUsage()
|
|
2380
|
+
process.exitCode = 1
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2266
2383
|
function creativityCommand(args, useJson) {
|
|
2267
2384
|
const sub = args[1]
|
|
2268
2385
|
|
|
@@ -2289,9 +2406,16 @@ function creativityCommand(args, useJson) {
|
|
|
2289
2406
|
if (sub === 'status') {
|
|
2290
2407
|
result = engine.status()
|
|
2291
2408
|
} else {
|
|
2409
|
+
const sort = optionValue(flags, '--sort')
|
|
2410
|
+
if (sort !== undefined && sort !== 'novelty') {
|
|
2411
|
+
console.error(`mio creativity list: unknown --sort "${sort}" (supported: novelty)`)
|
|
2412
|
+
process.exitCode = 1
|
|
2413
|
+
return
|
|
2414
|
+
}
|
|
2292
2415
|
result = engine.list({
|
|
2293
2416
|
status: optionValue(flags, '--status'),
|
|
2294
2417
|
limit: parseNumberOption(flags, '--limit'),
|
|
2418
|
+
sort,
|
|
2295
2419
|
})
|
|
2296
2420
|
}
|
|
2297
2421
|
} catch (error) {
|
|
@@ -3369,9 +3493,10 @@ Usage:
|
|
|
3369
3493
|
mio experience confirm --ids a,b Confirm auto-claimed reuse (bulk supported)
|
|
3370
3494
|
mio experience reuse --source-agent A --target-agent B --experience-id X Record a reuse
|
|
3371
3495
|
mio creativity status Show creativity hypothesis counts and recent top ideas
|
|
3372
|
-
mio creativity list List creativity hypotheses (--status active|validated|rejected|draft, --limit N)
|
|
3496
|
+
mio creativity list List creativity hypotheses (--status active|validated|rejected|draft, --sort novelty, --limit N)
|
|
3373
3497
|
mio creativity generate Generate hypotheses from 2+ --source "name|content" (or --from-insights; calls an LLM)
|
|
3374
3498
|
mio creativity ferment Review and refine active hypotheses (calls an LLM)
|
|
3499
|
+
mio idea generate --goal "..." Grounded idea pipeline: auto sources, novelty gate, persisted (calls an LLM)
|
|
3375
3500
|
mio insight status Insight counts: total, reported, unreported, high-value
|
|
3376
3501
|
mio insight list List insights (--unreported, --min-score N, --detector X, --limit N)
|
|
3377
3502
|
mio insight generate Generate insights from context (--memory "kind|content"/--summary "text"; calls an LLM)
|
|
@@ -3435,6 +3560,8 @@ async function main() {
|
|
|
3435
3560
|
return policyCommand(args, useJson)
|
|
3436
3561
|
case 'creativity':
|
|
3437
3562
|
return creativityCommand(args, useJson)
|
|
3563
|
+
case 'idea':
|
|
3564
|
+
return ideaCommand(args, useJson)
|
|
3438
3565
|
case 'insight':
|
|
3439
3566
|
return insightCommand(args, useJson)
|
|
3440
3567
|
case 'observer':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mio-agent-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "Mio Agent Runtime installer and MCP intelligence layer",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"!server/mio-intelligence-mcp/__tests__"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
+
"pretest": "node scripts/link-workspace.cjs",
|
|
20
21
|
"check": "node scripts/check-syntax.cjs",
|
|
21
22
|
"test": "node --test \"__tests__/*.test.js\" \"server/mio-intelligence-mcp/__tests__/*.test.js\"",
|
|
22
23
|
"prepack": "npm run check",
|
|
@@ -34,7 +35,8 @@
|
|
|
34
35
|
"@akemi-mio/evolution-safety": "^0.1.0",
|
|
35
36
|
"@akemi-mio/evolution-scheduler": "^0.1.0",
|
|
36
37
|
"@akemi-mio/observer": "^0.1.4",
|
|
37
|
-
"@akemi-mio/insight": "^0.1.1"
|
|
38
|
+
"@akemi-mio/insight": "^0.1.1",
|
|
39
|
+
"@akemi-mio/creativity": "^0.1.0"
|
|
38
40
|
},
|
|
39
41
|
"main": "index.js",
|
|
40
42
|
"directories": {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// Creates the node_modules junctions mio-cli needs to require the workspace
|
|
5
|
+
// packages it depends on at runtime (@akemi-mio/insight etc. already exist as
|
|
6
|
+
// junctions in a normal checkout; a fresh clone has none). Idempotent: an
|
|
7
|
+
// existing link (or directory) is left alone. Windows junctions, because this
|
|
8
|
+
// checkout lives on NTFS and junctions need no admin rights.
|
|
9
|
+
|
|
10
|
+
const fs = require('fs')
|
|
11
|
+
const path = require('path')
|
|
12
|
+
|
|
13
|
+
const REPO = path.resolve(__dirname, '..', '..', '..')
|
|
14
|
+
const links = [
|
|
15
|
+
{
|
|
16
|
+
at: path.join(REPO, 'packages', 'mio-cli', 'node_modules', '@akemi-mio', 'creativity'),
|
|
17
|
+
target: path.join(REPO, 'packages', 'creativity'),
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
at: path.join(REPO, 'packages', 'creativity', 'node_modules', '@akemi-mio', 'core'),
|
|
21
|
+
target: path.join(REPO, 'packages', 'core'),
|
|
22
|
+
},
|
|
23
|
+
// Root-level links: the new tests under tests/main/creativity/ import
|
|
24
|
+
// '@akemi-mio/creativity/*' from the repo root, and the lockfile's
|
|
25
|
+
// link:true entries are not materialized on disk (ground rule 4 forbids
|
|
26
|
+
// the `npm install` that would create them).
|
|
27
|
+
{
|
|
28
|
+
at: path.join(REPO, 'node_modules', '@akemi-mio', 'creativity'),
|
|
29
|
+
target: path.join(REPO, 'packages', 'creativity'),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
at: path.join(REPO, 'node_modules', '@akemi-mio', 'core'),
|
|
33
|
+
target: path.join(REPO, 'packages', 'core'),
|
|
34
|
+
},
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
for (const { at, target } of links) {
|
|
38
|
+
let exists = false
|
|
39
|
+
try {
|
|
40
|
+
fs.lstatSync(at)
|
|
41
|
+
exists = true
|
|
42
|
+
} catch (_) {
|
|
43
|
+
exists = false
|
|
44
|
+
}
|
|
45
|
+
if (exists) {
|
|
46
|
+
console.log(`exists ${at}`)
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
fs.mkdirSync(path.dirname(at), { recursive: true })
|
|
50
|
+
fs.symlinkSync(target, at, 'junction')
|
|
51
|
+
console.log(`linked ${at} -> ${target}`)
|
|
52
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs')
|
|
4
4
|
const path = require('path')
|
|
5
5
|
const crypto = require('crypto')
|
|
6
|
+
const { evaluateNovelty } = require('@akemi-mio/creativity/NoveltyScorer')
|
|
6
7
|
|
|
7
8
|
// Cache validity key for a JSONL file: a (size, mtimeMs) pair, the same
|
|
8
9
|
// guard server/insight-store.js uses (commit 0ba7fa8). Every getter below used
|
|
@@ -27,6 +28,13 @@ function clamp100(value, fallback = 50) {
|
|
|
27
28
|
return Math.min(100, Math.max(0, n))
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
// ISO timestamp for provenance: accepts epoch ms or date strings, returns
|
|
32
|
+
// undefined when the value is missing or unparsable.
|
|
33
|
+
function isoOf(value) {
|
|
34
|
+
const t = typeof value === 'number' ? value : Date.parse(value || '')
|
|
35
|
+
return Number.isFinite(t) ? new Date(t).toISOString() : undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
// ═══════════════════════════════════════════════
|
|
31
39
|
// JSONL Store — ideas, combos, experiments
|
|
32
40
|
// ═══════════════════════════════════════════════
|
|
@@ -154,14 +162,34 @@ function mulberry32(seed) {
|
|
|
154
162
|
}
|
|
155
163
|
}
|
|
156
164
|
|
|
157
|
-
|
|
165
|
+
// One concept-pair identity. `--source "name|content"` allows omitting the
|
|
166
|
+
// pipe, so `name` can degrade into the entire description (often >1k chars):
|
|
167
|
+
// with the raw name as identity, a tail edit mints a brand-new pair and
|
|
168
|
+
// silently bypasses dedup. Trim + first 120 chars is short enough to read in
|
|
169
|
+
// a key and long enough that genuine sources do not collide.
|
|
170
|
+
function pairSourceKey(name) {
|
|
171
|
+
return String(name == null ? '' : name).trim().slice(0, 120)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function mixConcepts(sources, exploredPairs, rng, strategy) {
|
|
158
175
|
if (sources.length < 2) return []
|
|
176
|
+
// Dedup identity is (pair, strategy): `strategy` picks the prompt and the
|
|
177
|
+
// temperature, so the documented fallback "hit 409, retry with another
|
|
178
|
+
// strategy" is a genuinely different experiment. Without the suffix every
|
|
179
|
+
// retry recomputed the same key and returned the same deterministic
|
|
180
|
+
// `all pairs already explored` — three LLM-less no-ops in a row.
|
|
181
|
+
const strat = String(strategy || '')
|
|
159
182
|
const pairs = []
|
|
160
183
|
for (let i = 0; i < sources.length; i++) {
|
|
161
184
|
for (let j = i + 1; j < sources.length; j++) {
|
|
162
|
-
const
|
|
163
|
-
const
|
|
185
|
+
const a = pairSourceKey(sources[i].name)
|
|
186
|
+
const b = pairSourceKey(sources[j].name)
|
|
187
|
+
const key = strat ? `${a}||${b}||${strat}` : `${a}||${b}`
|
|
188
|
+
const reverseKey = strat ? `${b}||${a}||${strat}` : `${b}||${a}`
|
|
164
189
|
if (exploredPairs.has(key) || exploredPairs.has(reverseKey)) continue
|
|
190
|
+
// Records written without a strategy field carry the unsuffixed key;
|
|
191
|
+
// they must keep blocking every strategy (the old semantics).
|
|
192
|
+
if (strat && (exploredPairs.has(`${a}||${b}`) || exploredPairs.has(`${b}||${a}`))) continue
|
|
165
193
|
pairs.push([sources[i], sources[j]])
|
|
166
194
|
}
|
|
167
195
|
}
|
|
@@ -173,43 +201,6 @@ function mixConcepts(sources, exploredPairs, rng) {
|
|
|
173
201
|
return pairs.slice(0, Math.min(5, pairs.length))
|
|
174
202
|
}
|
|
175
203
|
|
|
176
|
-
// ═══════════════════════════════════════════════
|
|
177
|
-
// Novelty Scorer — reject duplicates
|
|
178
|
-
// ═══════════════════════════════════════════════
|
|
179
|
-
|
|
180
|
-
function bigramSet(text) {
|
|
181
|
-
const s = new Set()
|
|
182
|
-
const lower = text.toLowerCase()
|
|
183
|
-
for (let i = 0; i < lower.length - 1; i++) {
|
|
184
|
-
s.add(lower.slice(i, i + 2))
|
|
185
|
-
}
|
|
186
|
-
return s
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function jaccard(a, b) {
|
|
190
|
-
if (a.size === 0 && b.size === 0) return 1
|
|
191
|
-
let inter = 0
|
|
192
|
-
for (const x of a) if (b.has(x)) inter++
|
|
193
|
-
return inter / (a.size + b.size - inter)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function evaluateNovelty(idea, recent, rejected) {
|
|
197
|
-
const ideaBigrams = bigramSet(idea.title + ' ' + idea.idea)
|
|
198
|
-
for (const r of recent) {
|
|
199
|
-
const sim = jaccard(ideaBigrams, bigramSet(r.title + ' ' + r.idea))
|
|
200
|
-
if (sim > 0.7) {
|
|
201
|
-
return { shouldReject: true, rejectReason: `too similar to recent: "${r.title}" (sim=${sim.toFixed(2)})`, adjustedNovelty: idea.novelty }
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
for (const r of rejected) {
|
|
205
|
-
const sim = jaccard(ideaBigrams, bigramSet(r.title + ' ' + r.idea))
|
|
206
|
-
if (sim > 0.6) {
|
|
207
|
-
return { shouldReject: true, rejectReason: `matches rejected: "${r.title}" (sim=${sim.toFixed(2)})`, adjustedNovelty: idea.novelty }
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return { shouldReject: false, adjustedNovelty: Math.min(100, idea.novelty + 5) }
|
|
211
|
-
}
|
|
212
|
-
|
|
213
204
|
// ═══════════════════════════════════════════════
|
|
214
205
|
// Creativity Engine — main orchestrator
|
|
215
206
|
// ═══════════════════════════════════════════════
|
|
@@ -234,12 +225,38 @@ function sourcesFromInsights(insights, limit = 10) {
|
|
|
234
225
|
Array.isArray(i.evidence) && i.evidence.length > 0 ? `Evidence: ${i.evidence.join('; ')}` : '',
|
|
235
226
|
].filter(Boolean).join('\n'),
|
|
236
227
|
type: 'insight',
|
|
237
|
-
|
|
228
|
+
// > 0.7: IdeaGenerator.applyTemperature keeps any weight>0.7 source unconditionally.
|
|
229
|
+
weight: 0.78,
|
|
230
|
+
origin: `insight:${i.id || i.title || 'unknown'}`,
|
|
231
|
+
...(i.id ? { originId: i.id } : {}),
|
|
232
|
+
...(isoOf(i.createdAt) ? { timestamp: isoOf(i.createdAt) } : {}),
|
|
238
233
|
}))
|
|
239
234
|
}
|
|
240
235
|
|
|
241
236
|
const STRATEGY_CYCLE = ['explore', 'signal', 'stable']
|
|
242
237
|
|
|
238
|
+
// One row shape for every list variant — the fields JSON/CLI consumers already
|
|
239
|
+
// depend on. `noveltyScore` is added only by sort=novelty (relative ranking),
|
|
240
|
+
// never confused with the quality sum `score`.
|
|
241
|
+
function listRow(h) {
|
|
242
|
+
return {
|
|
243
|
+
id: h.id,
|
|
244
|
+
title: h.title,
|
|
245
|
+
idea: h.idea,
|
|
246
|
+
status: h.status,
|
|
247
|
+
novelty: h.novelty,
|
|
248
|
+
feasibility: h.feasibility,
|
|
249
|
+
impact: h.impact,
|
|
250
|
+
...(h.logic !== undefined ? { logic: h.logic } : {}),
|
|
251
|
+
score: h.novelty + h.feasibility + h.impact,
|
|
252
|
+
sourceLabels: h.sourceLabels,
|
|
253
|
+
createdAt: h.createdAt,
|
|
254
|
+
fermentedAt: h.fermentedAt,
|
|
255
|
+
fermentCount: h.fermentCount,
|
|
256
|
+
...(h.mergedInto !== undefined ? { mergedInto: h.mergedInto } : {}),
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
243
260
|
class CreativityEngine {
|
|
244
261
|
constructor(dataDir, chatJson) {
|
|
245
262
|
this.store = new CreativityStore(dataDir)
|
|
@@ -269,14 +286,35 @@ class CreativityEngine {
|
|
|
269
286
|
const strat = strategy || STRATEGY_CYCLE[this.strategyIndex % STRATEGY_CYCLE.length]
|
|
270
287
|
this.strategyIndex++
|
|
271
288
|
|
|
272
|
-
// Mix concepts
|
|
289
|
+
// Mix concepts. Keys carry the strategy each record was written with, so
|
|
290
|
+
// explore -> signal -> stable retries each get their own attempt while a
|
|
291
|
+
// record without a strategy stays unsuffixed and blocks every strategy.
|
|
273
292
|
const recent = this.store.getHypotheses({ limit: 30 })
|
|
274
293
|
const rejected = this.store.getHypotheses({ status: 'rejected', limit: 50 })
|
|
275
|
-
const exploredPairs = new Set(
|
|
276
|
-
|
|
294
|
+
const exploredPairs = new Set(
|
|
295
|
+
recent.flatMap((h) =>
|
|
296
|
+
(h.sourceLabels || []).flatMap((l, i, a) => {
|
|
297
|
+
if (i === 0) return []
|
|
298
|
+
const x = pairSourceKey(a[i - 1])
|
|
299
|
+
const y = pairSourceKey(l)
|
|
300
|
+
return h.strategy ? [`${x}||${y}||${h.strategy}`] : [`${x}||${y}`]
|
|
301
|
+
})
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
const pairs = mixConcepts(sources, exploredPairs, this.rng.bind(this), strat)
|
|
277
305
|
|
|
278
306
|
if (pairs.length === 0) {
|
|
279
|
-
|
|
307
|
+
// The bare string made a deterministic failure look like "this strategy
|
|
308
|
+
// did not work, maybe the next one will" — callers that degrade by
|
|
309
|
+
// switching strategy burned two LLM-less retries before learning the
|
|
310
|
+
// material pool itself was exhausted.
|
|
311
|
+
const candidatePairs = (sources.length * (sources.length - 1)) / 2
|
|
312
|
+
return {
|
|
313
|
+
ideas: [],
|
|
314
|
+
reason: `all pairs already explored for strategy "${strat}" (${sources.length} sources, ${candidatePairs} candidate pairs)`,
|
|
315
|
+
strategy: strat,
|
|
316
|
+
pairsAvailable: 0,
|
|
317
|
+
}
|
|
280
318
|
}
|
|
281
319
|
|
|
282
320
|
// Generate hypotheses via LLM
|
|
@@ -506,22 +544,19 @@ Output JSON with: title, idea, expectedBenefit, risk, novelty (0-100), feasibili
|
|
|
506
544
|
// result. `limit: 0` (and only 0) opts back into "everything".
|
|
507
545
|
list(opts = {}) {
|
|
508
546
|
const limit = opts.limit === undefined ? 20 : opts.limit
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
fermentCount: h.fermentCount,
|
|
523
|
-
...(h.mergedInto !== undefined ? { mergedInto: h.mergedInto } : {}),
|
|
524
|
-
}))
|
|
547
|
+
if (opts.sort === 'novelty') {
|
|
548
|
+
// Relative novelty ranking (read-only, nothing written back). Pairwise
|
|
549
|
+
// Jaccard is O(n²) — bound candidates to the newest 200 (spec Risk 3).
|
|
550
|
+
const candidates = this.store.getHypotheses({ status: opts.status }).slice(-200)
|
|
551
|
+
const rows = candidates.map((h) => {
|
|
552
|
+
const others = candidates.filter((x) => x.id !== h.id)
|
|
553
|
+
const verdict = evaluateNovelty(h, others, [])
|
|
554
|
+
return { ...listRow(h), noveltyScore: verdict.adjustedNovelty }
|
|
555
|
+
})
|
|
556
|
+
rows.sort((a, b) => b.noveltyScore - a.noveltyScore || (b.createdAt || 0) - (a.createdAt || 0))
|
|
557
|
+
return limit === 0 ? rows : rows.slice(0, limit)
|
|
558
|
+
}
|
|
559
|
+
return this.store.getHypotheses({ status: opts.status, limit }).map(listRow)
|
|
525
560
|
}
|
|
526
561
|
|
|
527
562
|
_buildGenerationPrompt(a, b, strategy, recentHypotheses, rejectedHypotheses) {
|
|
@@ -570,4 +605,4 @@ Output JSON with: title, idea, expectedBenefit, risk, novelty (0-100), feasibili
|
|
|
570
605
|
}
|
|
571
606
|
}
|
|
572
607
|
|
|
573
|
-
module.exports = { CreativityEngine, CreativityStore, sourcesFromInsights }
|
|
608
|
+
module.exports = { CreativityEngine, CreativityStore, sourcesFromInsights, isoOf }
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Source auto-aggregation for the creativity engine (spec Capability 2).
|
|
4
|
+
//
|
|
5
|
+
// Default providers read ONLY local MIO_HOME data — zero network. Insight and
|
|
6
|
+
// observer sources arrive as plain arrays from the caller, because only the
|
|
7
|
+
// entry point knows which store is authoritative (MIO_HOME for the CLI, the
|
|
8
|
+
// MCP server's MIO_DATA_DIR) and whether its optional package resolves.
|
|
9
|
+
// SourceAggregator already tolerates a throwing provider (per-provider
|
|
10
|
+
// try/catch with a WARN log), so providers here do not need extra guards.
|
|
11
|
+
|
|
12
|
+
const fs = require('fs')
|
|
13
|
+
const path = require('path')
|
|
14
|
+
const { SourceAggregator } = require('@akemi-mio/creativity/SourceAggregator')
|
|
15
|
+
const { sourcesFromInsights, isoOf } = require('./creativity-engine.js')
|
|
16
|
+
|
|
17
|
+
function readJsonl(file) {
|
|
18
|
+
try {
|
|
19
|
+
return fs
|
|
20
|
+
.readFileSync(file, 'utf8')
|
|
21
|
+
.split('\n')
|
|
22
|
+
.filter(Boolean)
|
|
23
|
+
.map((line) => {
|
|
24
|
+
try { return JSON.parse(line) } catch { return null }
|
|
25
|
+
})
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
} catch (_) {
|
|
28
|
+
return []
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Every weight is > 0.7 on purpose: IdeaGenerator.applyTemperature keeps any
|
|
33
|
+
// source with weight > 0.7 unconditionally (temperature 0.5 middle branch), so
|
|
34
|
+
// the assembled set reaches the prompt intact — deterministic, no RNG survival.
|
|
35
|
+
const WEIGHTS = {
|
|
36
|
+
memory: 0.75,
|
|
37
|
+
traceOk: 0.74,
|
|
38
|
+
traceFail: 0.73,
|
|
39
|
+
hypothesisActive: 0.72,
|
|
40
|
+
hypothesisRejected: 0.72,
|
|
41
|
+
trend: 0.76,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function memoryProvider(dataDir) {
|
|
45
|
+
return {
|
|
46
|
+
domain: 'feedback',
|
|
47
|
+
name: 'memory',
|
|
48
|
+
collect() {
|
|
49
|
+
return readJsonl(path.join(dataDir, 'memory.jsonl'))
|
|
50
|
+
.filter((r) => r && r.archived !== true && (r.kind === 'decision' || r.kind === 'note'))
|
|
51
|
+
.slice(-6)
|
|
52
|
+
.map((r) => ({
|
|
53
|
+
name: `${r.kind}: ${String(r.content || '').slice(0, 40)}`,
|
|
54
|
+
content: String(r.content || ''),
|
|
55
|
+
type: 'feedback',
|
|
56
|
+
weight: WEIGHTS.memory,
|
|
57
|
+
origin: `memory:${r.id}`,
|
|
58
|
+
originId: r.id,
|
|
59
|
+
timestamp: isoOf(r.timestamp),
|
|
60
|
+
}))
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function traceProviders(dataDir) {
|
|
66
|
+
const events = () => readJsonl(path.join(dataDir, 'traces.jsonl')).slice(-40)
|
|
67
|
+
const fmt = (r) => `${r.event_type}${r.outcome ? `/${r.outcome}` : ''}: ${JSON.stringify(r.payload || {}).slice(0, 300)}`
|
|
68
|
+
const stamp = (r) => isoOf(r.timestamp ?? r.ts)
|
|
69
|
+
return [
|
|
70
|
+
{
|
|
71
|
+
domain: 'behavior',
|
|
72
|
+
name: 'traces-success',
|
|
73
|
+
collect() {
|
|
74
|
+
return events()
|
|
75
|
+
.filter((r) => r.event_type === 'task_outcome' && r.outcome === 'success')
|
|
76
|
+
.slice(-3)
|
|
77
|
+
.map((r) => ({
|
|
78
|
+
name: `success: ${String((r.payload && (r.payload.task || r.payload.summary)) || r.trace_id || 'task').slice(0, 40)}`,
|
|
79
|
+
content: fmt(r),
|
|
80
|
+
type: 'behavior',
|
|
81
|
+
weight: WEIGHTS.traceOk,
|
|
82
|
+
origin: `trace:${r.trace_id}`,
|
|
83
|
+
originId: r.trace_id,
|
|
84
|
+
timestamp: stamp(r),
|
|
85
|
+
}))
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
domain: 'failure',
|
|
90
|
+
name: 'traces-failure',
|
|
91
|
+
collect() {
|
|
92
|
+
return events()
|
|
93
|
+
.filter(
|
|
94
|
+
(r) =>
|
|
95
|
+
(r.event_type === 'error' || r.event_type === 'retry') ||
|
|
96
|
+
(r.event_type === 'task_outcome' && (r.outcome === 'failure' || r.outcome === 'error' || r.outcome === 'aborted')),
|
|
97
|
+
)
|
|
98
|
+
.slice(-3)
|
|
99
|
+
.map((r) => ({
|
|
100
|
+
name: `failure: ${String(r.event_type)}${r.trace_id ? ` ${String(r.trace_id).slice(0, 12)}` : ''}`,
|
|
101
|
+
content: fmt(r),
|
|
102
|
+
type: 'failure',
|
|
103
|
+
weight: WEIGHTS.traceFail,
|
|
104
|
+
origin: `trace:${r.trace_id || 'unknown'}`,
|
|
105
|
+
originId: r.trace_id,
|
|
106
|
+
timestamp: stamp(r),
|
|
107
|
+
}))
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function hypothesesProvider(dataDir) {
|
|
114
|
+
return {
|
|
115
|
+
domain: 'module',
|
|
116
|
+
name: 'hypotheses',
|
|
117
|
+
collect() {
|
|
118
|
+
return readJsonl(path.join(dataDir, 'creativity', 'creativity-hypotheses.jsonl'))
|
|
119
|
+
.filter((h) => h && (h.status === 'active' || h.status === 'rejected'))
|
|
120
|
+
.slice(-6)
|
|
121
|
+
.map((h) => ({
|
|
122
|
+
name: String(h.title || h.id).slice(0, 60),
|
|
123
|
+
content: `idea: ${h.idea}\nrisk: ${h.risk || '-'}`,
|
|
124
|
+
type: h.status === 'rejected' ? 'failure' : 'knowledge',
|
|
125
|
+
weight: h.status === 'rejected' ? WEIGHTS.hypothesisRejected : WEIGHTS.hypothesisActive,
|
|
126
|
+
origin: `hypothesis:${h.id}`,
|
|
127
|
+
originId: h.id,
|
|
128
|
+
timestamp: isoOf(h.createdAt),
|
|
129
|
+
}))
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Observer trend reports → provocation sources. The report schema has drifted
|
|
135
|
+
// across observer versions, so fields are read defensively with fallbacks.
|
|
136
|
+
function trendSources(trends) {
|
|
137
|
+
return (Array.isArray(trends) ? trends : []).slice(0, 4).map((t, i) => {
|
|
138
|
+
const title = (t && (t.title || t.name || t.date)) || `trend-${i}`
|
|
139
|
+
const body =
|
|
140
|
+
(t && t.summary) ||
|
|
141
|
+
(Array.isArray(t && t.themes) ? t.themes.join('; ') : '') ||
|
|
142
|
+
JSON.stringify(t || {}).slice(0, 400)
|
|
143
|
+
const key = (t && (t.date || t.id)) || String(i)
|
|
144
|
+
const stamp = isoOf(t && (t.generatedAt || t.date))
|
|
145
|
+
return {
|
|
146
|
+
name: String(title).slice(0, 60),
|
|
147
|
+
content: String(body),
|
|
148
|
+
type: 'provocation',
|
|
149
|
+
weight: WEIGHTS.trend,
|
|
150
|
+
origin: `observer-trends:${key}`,
|
|
151
|
+
...(t && t.id ? { originId: t.id } : {}),
|
|
152
|
+
...(stamp ? { timestamp: stamp } : {}),
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Assemble every local (+ optionally insight/observer) domain through the
|
|
158
|
+
// package SourceAggregator so dedup, per-domain caps and failure tolerance
|
|
159
|
+
// behave identically for idea.generate and the generate top-up.
|
|
160
|
+
function buildAutoSources({ dataDir, insights = [], trends = [] }) {
|
|
161
|
+
const aggregator = new SourceAggregator(4, 1)
|
|
162
|
+
aggregator.addProvider(memoryProvider(dataDir))
|
|
163
|
+
for (const provider of traceProviders(dataDir)) aggregator.addProvider(provider)
|
|
164
|
+
aggregator.addProvider(hypothesesProvider(dataDir))
|
|
165
|
+
if (Array.isArray(insights) && insights.length > 0) {
|
|
166
|
+
aggregator.addProvider({ domain: 'observation', name: 'insight', collect: () => sourcesFromInsights(insights) })
|
|
167
|
+
}
|
|
168
|
+
const trendList = Array.isArray(trends) ? trends : []
|
|
169
|
+
if (trendList.length > 0) {
|
|
170
|
+
aggregator.addProvider({ domain: 'external', name: 'observer-trends', collect: () => trendSources(trendList) })
|
|
171
|
+
}
|
|
172
|
+
return aggregator.build()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Explicit --source entries always win: auto sources only top a set up to the
|
|
176
|
+
// two entries the concept mixer needs; they never replace or reorder explicit
|
|
177
|
+
// ones (spec Capability 2, injection point 2).
|
|
178
|
+
function topUpSources(explicit, auto) {
|
|
179
|
+
const list = Array.isArray(explicit) ? explicit.filter(Boolean) : []
|
|
180
|
+
if (list.length >= 2) return list
|
|
181
|
+
const seen = new Set(list.map((s) => s.name))
|
|
182
|
+
const extra = (Array.isArray(auto) ? auto : []).filter((s) => s && !seen.has(s.name))
|
|
183
|
+
return [...list, ...extra]
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = { buildAutoSources, topUpSources, trendSources, memoryProvider, traceProviders, hypothesesProvider }
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// `mio.idea.generate` — one-shot goal-driven idea pipeline (spec Capability 1).
|
|
4
|
+
// Shared by the CLI (`mio idea generate`) and the MCP server (tool
|
|
5
|
+
// `mio.idea.generate`): each entry point only assembles entry-specific inputs
|
|
6
|
+
// (which store is authoritative, where local data lives) and renders the
|
|
7
|
+
// response this module returns.
|
|
8
|
+
//
|
|
9
|
+
// Contract:
|
|
10
|
+
// - source order: goal → context → constraints → grounding → local auto,
|
|
11
|
+
// deduplicated by `origin` (fallback name), first occurrence wins — so a
|
|
12
|
+
// memory that arrives both as a grounding hit and through the local
|
|
13
|
+
// provider contributes exactly once, the grounding copy first;
|
|
14
|
+
// - pairing strategy: cross-type `explore` only when two types exist, else
|
|
15
|
+
// the `stable` fallback (an all-knowledge set explores into ZERO combos);
|
|
16
|
+
// - every candidate passes evaluateNovelty against stored hypotheses: a
|
|
17
|
+
// near-duplicate of a REJECTED one is persisted as rejected with a reason,
|
|
18
|
+
// a merely-similar recent one is down-scored; drafts AND rejects both land
|
|
19
|
+
// in the store so later runs can dedup against them;
|
|
20
|
+
// - ideas are never written to Mio memory — CreativityStore only.
|
|
21
|
+
|
|
22
|
+
const { IdeaGenerator } = require('@akemi-mio/creativity/IdeaGenerator')
|
|
23
|
+
const { evaluateNovelty } = require('@akemi-mio/creativity/NoveltyScorer')
|
|
24
|
+
|
|
25
|
+
// All > 0.7: IdeaGenerator.applyTemperature keeps such sources unconditionally
|
|
26
|
+
// (temperature 0.5 middle branch) — deterministic prompt, no RNG survival.
|
|
27
|
+
const WEIGHTS = { goal: 0.95, context: 0.9, constraint: 0.88, grounding: 0.75 }
|
|
28
|
+
|
|
29
|
+
// `queryMemory` result → grounding sources + ids. Tolerates null/empty
|
|
30
|
+
// (a thrown query is the caller's problem to catch; a miss is not).
|
|
31
|
+
function groundingFrom(queryResult) {
|
|
32
|
+
const results = Array.isArray(queryResult && queryResult.results) ? queryResult.results : []
|
|
33
|
+
const sources = results.map((r) => ({
|
|
34
|
+
name: `${r.kind || 'memory'}: ${String(r.content || '').slice(0, 40)}`,
|
|
35
|
+
content: String(r.content || ''),
|
|
36
|
+
type: 'feedback',
|
|
37
|
+
weight: WEIGHTS.grounding,
|
|
38
|
+
origin: `memory:${r.id}`,
|
|
39
|
+
originId: r.id,
|
|
40
|
+
...(r.timestamp ? { timestamp: r.timestamp } : {}),
|
|
41
|
+
}))
|
|
42
|
+
return { sources, relatedMemoryIds: results.map((r) => r.id), groundedWith: results.length }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function namedSource(role, text, origin, weight) {
|
|
46
|
+
const body = String(text).trim()
|
|
47
|
+
return {
|
|
48
|
+
name: `${role}: ${body.slice(0, 40)}`,
|
|
49
|
+
content: body,
|
|
50
|
+
type: 'knowledge',
|
|
51
|
+
weight,
|
|
52
|
+
origin,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function runIdeaGenerate(options) {
|
|
57
|
+
const {
|
|
58
|
+
goal,
|
|
59
|
+
context = '',
|
|
60
|
+
constraints = [],
|
|
61
|
+
numIdeas,
|
|
62
|
+
chatJson,
|
|
63
|
+
store,
|
|
64
|
+
groundingSources = [],
|
|
65
|
+
relatedMemoryIds = [],
|
|
66
|
+
autoSources = [],
|
|
67
|
+
} = options || {}
|
|
68
|
+
|
|
69
|
+
const ordered = []
|
|
70
|
+
if (typeof goal === 'string' && goal.trim()) ordered.push(namedSource('goal', goal, 'goal', WEIGHTS.goal))
|
|
71
|
+
if (typeof context === 'string' && context.trim()) ordered.push(namedSource('context', context, 'context', WEIGHTS.context))
|
|
72
|
+
if (Array.isArray(constraints)) {
|
|
73
|
+
constraints.forEach((c, i) => {
|
|
74
|
+
if (typeof c === 'string' && c.trim()) ordered.push(namedSource('constraint', c, `constraint:${i}`, WEIGHTS.constraint))
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
for (const s of [...groundingSources, ...autoSources]) {
|
|
78
|
+
if (s && typeof s === 'object' && s.name) ordered.push(s)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const seen = new Set()
|
|
82
|
+
const sources = []
|
|
83
|
+
for (const s of ordered) {
|
|
84
|
+
const key = s.origin || s.name
|
|
85
|
+
if (seen.has(key)) continue
|
|
86
|
+
seen.add(key)
|
|
87
|
+
sources.push(s)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (sources.length < 2) {
|
|
91
|
+
return { reason: 'need at least 2 sources — add --context/--constraints, or run `mio remember` first' }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const pairingStrategy = new Set(sources.map((s) => s.type)).size >= 2 ? 'explore' : 'stable'
|
|
95
|
+
const requested = Number(numIdeas)
|
|
96
|
+
const maxIdeas = Number.isFinite(requested) ? Math.min(Math.max(Math.trunc(requested), 1), 3) : 3
|
|
97
|
+
|
|
98
|
+
const rotationSeed = store.getHypotheses().length
|
|
99
|
+
const generator = new IdeaGenerator(chatJson, 0.5, undefined, rotationSeed)
|
|
100
|
+
// method left undefined: HypothesisGenerator rotates techniques internally
|
|
101
|
+
// from rotationSeed; the chosen one comes back on hypothesis.technique.
|
|
102
|
+
const creativeIdeas = await generator.generateIdeas(sources, maxIdeas, pairingStrategy, [])
|
|
103
|
+
|
|
104
|
+
const generatedAt = new Date().toISOString()
|
|
105
|
+
const recent = store.getHypotheses().slice(-30)
|
|
106
|
+
const rejected = store.getHypotheses({ status: 'rejected' }).slice(-50)
|
|
107
|
+
|
|
108
|
+
const ideas = []
|
|
109
|
+
const persistedIds = []
|
|
110
|
+
for (const { hypothesis: h, experiment } of creativeIdeas) {
|
|
111
|
+
const verdict = evaluateNovelty(h, recent, rejected)
|
|
112
|
+
if (verdict.shouldReject) {
|
|
113
|
+
h.status = 'rejected'
|
|
114
|
+
h.rejectionReason = verdict.rejectReason
|
|
115
|
+
} else {
|
|
116
|
+
h.novelty = verdict.adjustedNovelty
|
|
117
|
+
}
|
|
118
|
+
const labels = new Set(Array.isArray(h.sourceLabels) ? h.sourceLabels : [])
|
|
119
|
+
h.provenance = {
|
|
120
|
+
strategy: pairingStrategy,
|
|
121
|
+
technique: h.technique,
|
|
122
|
+
relatedMemoryIds: [...relatedMemoryIds],
|
|
123
|
+
sources: sources
|
|
124
|
+
.filter((s) => labels.has(s.name))
|
|
125
|
+
.map((s) => ({ name: s.name, type: s.type, origin: s.origin, timestamp: s.timestamp })),
|
|
126
|
+
generatedAt,
|
|
127
|
+
}
|
|
128
|
+
store.addHypothesis(h)
|
|
129
|
+
persistedIds.push(h.id)
|
|
130
|
+
ideas.push({ title: h.title, hypothesis: h, experiment, novelty: h.novelty, provenance: h.provenance })
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { ideas, generatedAt, groundedWith: groundingSources.length, persistedIds }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = { runIdeaGenerate, groundingFrom }
|
|
@@ -21,6 +21,8 @@ const { createDigest } = require('../digest.js')
|
|
|
21
21
|
const { createEvolutionReport } = require('../evolution-report.js')
|
|
22
22
|
|
|
23
23
|
const { CreativityEngine, sourcesFromInsights } = require('../creativity-engine.js')
|
|
24
|
+
const { buildAutoSources, topUpSources } = require('../creativity-sources.js')
|
|
25
|
+
const { runIdeaGenerate, groundingFrom } = require('../idea-generate.js')
|
|
24
26
|
// The observer and insight engines are optional dependencies. Their stores own
|
|
25
27
|
// the require and expose isObserverAvailable() / isInsightAvailable() so tool
|
|
26
28
|
// registration can be gated on them, and both the MCP server and the CLI share
|
|
@@ -178,6 +180,16 @@ const memoryStore = createMemoryStore({
|
|
|
178
180
|
verifiedFilter: REUSE_STATUS_FILTERS.verified,
|
|
179
181
|
})
|
|
180
182
|
|
|
183
|
+
// Second memory-store instance WITHOUT the shared queryLog: idea.generate's
|
|
184
|
+
// grounding recall is tool-side context gathering, not a user query — it must
|
|
185
|
+
// not append to queries.jsonl (that log exists for recall-style queries).
|
|
186
|
+
const groundingMemoryStore = createMemoryStore({
|
|
187
|
+
dataDir,
|
|
188
|
+
projectName,
|
|
189
|
+
agentId: runtimeAgentId,
|
|
190
|
+
verifiedFilter: REUSE_STATUS_FILTERS.verified,
|
|
191
|
+
})
|
|
192
|
+
|
|
181
193
|
const digestEngine = createDigest({ home: dataDir })
|
|
182
194
|
|
|
183
195
|
const {
|
|
@@ -695,6 +707,7 @@ const TOOLS = [
|
|
|
695
707
|
properties: {
|
|
696
708
|
status: { type: 'string', description: 'Filter by status: active, validated, rejected, draft.' },
|
|
697
709
|
limit: { type: 'number', description: 'Max results. Default 20.' },
|
|
710
|
+
sort: { type: 'string', description: "Sort order: 'novelty' ranks stored hypotheses by relative noveltyScore (a dedup signal, NOT a quality score), newest 200 candidates capped. Default: newest." },
|
|
698
711
|
},
|
|
699
712
|
},
|
|
700
713
|
},
|
|
@@ -722,7 +735,7 @@ const TOOLS = [
|
|
|
722
735
|
type: 'boolean',
|
|
723
736
|
description: 'Seed concept sources from stored insights (highest score first), merged with any explicit sources. Needs @akemi-mio/insight and at least one stored insight.',
|
|
724
737
|
},
|
|
725
|
-
strategy: { type: 'string', description: 'Generation strategy: explore (cross-domain), signal (provocative), stable (conservative). Auto-selected if omitted.' },
|
|
738
|
+
strategy: { type: 'string', description: 'Generation strategy: explore (cross-domain), signal (provocative), stable (conservative). Auto-selected if omitted. Pair dedup is per strategy: the same source pair may be tried once under each strategy.' },
|
|
726
739
|
},
|
|
727
740
|
},
|
|
728
741
|
},
|
|
@@ -736,6 +749,21 @@ const TOOLS = [
|
|
|
736
749
|
},
|
|
737
750
|
},
|
|
738
751
|
},
|
|
752
|
+
{
|
|
753
|
+
name: 'mio.idea.generate',
|
|
754
|
+
description:
|
|
755
|
+
'Generate up to 3 concrete, experiment-backed hypotheses from one goal. Sources are assembled locally (goal, context/constraints, Mio memory grounding, local memory/traces/stored hypotheses) and every idea is persisted to the CreativityStore with provenance — Mio memory is never written. Returns { ideas, generatedAt, groundedWith, persistedIds }, or { reason } when fewer than two sources are available.',
|
|
756
|
+
inputSchema: {
|
|
757
|
+
type: 'object',
|
|
758
|
+
properties: {
|
|
759
|
+
goal: { type: 'string', description: 'The one-line goal the ideas should pursue (required).' },
|
|
760
|
+
context: { type: 'string', description: 'Optional free-form background.' },
|
|
761
|
+
constraints: { type: 'array', items: { type: 'string' }, description: 'Optional hard constraints.' },
|
|
762
|
+
numIdeas: { type: 'number', description: 'How many ideas to return; clamped to 1..3, default 3.' },
|
|
763
|
+
},
|
|
764
|
+
required: ['goal'],
|
|
765
|
+
},
|
|
766
|
+
},
|
|
739
767
|
|
|
740
768
|
// ═══ Observer Research Pipeline ═══
|
|
741
769
|
|
|
@@ -968,6 +996,25 @@ function observerDag(args = {}) {
|
|
|
968
996
|
return observerStore.dag(args)
|
|
969
997
|
}
|
|
970
998
|
|
|
999
|
+
// Local auto sources shared by generate's top-up and idea.generate — the same
|
|
1000
|
+
// providers the CLI assembles (spec Capability 2). Every layer degrades to []
|
|
1001
|
+
// so a missing optional package or an unreadable store never fails a tool call.
|
|
1002
|
+
function autoSources() {
|
|
1003
|
+
let insights = []
|
|
1004
|
+
let trends = []
|
|
1005
|
+
try {
|
|
1006
|
+
if (isInsightAvailable()) insights = insightStore.list({}) || []
|
|
1007
|
+
} catch (_) {}
|
|
1008
|
+
try {
|
|
1009
|
+
trends = observerStore.trends({ limit: 3 }) || []
|
|
1010
|
+
} catch (_) {}
|
|
1011
|
+
try {
|
|
1012
|
+
return buildAutoSources({ dataDir, insights, trends })
|
|
1013
|
+
} catch (_) {
|
|
1014
|
+
return []
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
971
1018
|
// `fromInsights` seeds generation from what the insight engine already noticed
|
|
972
1019
|
// instead of making the caller hand-write concept sources. Only this entry
|
|
973
1020
|
// point knows which store is authoritative, so the read happens here; the
|
|
@@ -975,15 +1022,24 @@ function observerDag(args = {}) {
|
|
|
975
1022
|
// so the CLI's `--from-insights` lands on the same shapes.
|
|
976
1023
|
function generationSources(args) {
|
|
977
1024
|
const supplied = args.sources
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1025
|
+
// Nothing to top up and no fromInsights: return as-is so the engine keeps
|
|
1026
|
+
// throwing its documented "requires sources" error for empty calls.
|
|
1027
|
+
if (!args.fromInsights && !(Array.isArray(supplied) && supplied.length)) return supplied
|
|
1028
|
+
let base = supplied
|
|
1029
|
+
if (args.fromInsights) {
|
|
1030
|
+
if (!isInsightAvailable()) {
|
|
1031
|
+
throw new Error('mio.creativity.generate fromInsights requires the optional package @akemi-mio/insight')
|
|
1032
|
+
}
|
|
1033
|
+
const seeds = sourcesFromInsights(insightStore.list({}))
|
|
1034
|
+
if (seeds.length === 0) {
|
|
1035
|
+
throw new Error('mio.creativity.generate fromInsights: no stored insights yet (run `mio insight generate` first)')
|
|
1036
|
+
}
|
|
1037
|
+
base = Array.isArray(supplied) ? supplied.concat(seeds) : seeds
|
|
985
1038
|
}
|
|
986
|
-
|
|
1039
|
+
// Explicit sources always win; local memory/traces/stored hypotheses only
|
|
1040
|
+
// top a short list up to the two entries the mixer needs (spec Capability 2,
|
|
1041
|
+
// injection point 2 — the MCP twin of `mio creativity generate`).
|
|
1042
|
+
return topUpSources(Array.isArray(base) ? base : [], autoSources())
|
|
987
1043
|
}
|
|
988
1044
|
|
|
989
1045
|
async function callTool(name, args = {}) {
|
|
@@ -1060,6 +1116,26 @@ async function callTool(name, args = {}) {
|
|
|
1060
1116
|
return creativityEngine.generate(generationSources(args), args.strategy)
|
|
1061
1117
|
case 'mio.creativity.ferment':
|
|
1062
1118
|
return creativityEngine.ferment(args.limit)
|
|
1119
|
+
case 'mio.idea.generate': {
|
|
1120
|
+
const goal = typeof args.goal === 'string' ? args.goal.trim() : ''
|
|
1121
|
+
if (!goal) throw new Error('mio.idea.generate requires goal: a non-empty string')
|
|
1122
|
+
let grounding = { sources: [], relatedMemoryIds: [], groundedWith: 0 }
|
|
1123
|
+
try {
|
|
1124
|
+
const qr = await groundingMemoryStore.queryMemory({ query: goal, limit: 3, scope: 'all' })
|
|
1125
|
+
grounding = groundingFrom(qr)
|
|
1126
|
+
} catch (_) {}
|
|
1127
|
+
return runIdeaGenerate({
|
|
1128
|
+
goal,
|
|
1129
|
+
context: typeof args.context === 'string' ? args.context : '',
|
|
1130
|
+
constraints: Array.isArray(args.constraints) ? args.constraints.filter((c) => typeof c === 'string') : [],
|
|
1131
|
+
numIdeas: args.numIdeas,
|
|
1132
|
+
chatJson,
|
|
1133
|
+
store: creativityEngine.store,
|
|
1134
|
+
groundingSources: grounding.sources,
|
|
1135
|
+
relatedMemoryIds: grounding.relatedMemoryIds,
|
|
1136
|
+
autoSources: autoSources(),
|
|
1137
|
+
})
|
|
1138
|
+
}
|
|
1063
1139
|
case 'mio.insight.status':
|
|
1064
1140
|
return insightStatus()
|
|
1065
1141
|
case 'mio.insight.list':
|