mio-agent-runtime 0.14.0 → 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 CHANGED
@@ -423,6 +423,13 @@ mio creativity ferment --limit 3
423
423
  `@akemi-mio/insight` 已安装且至少存有一条洞察时有意义,否则明确报错而不是
424
424
  静默当作「没有来源」。因为来源可由它提供,`mio.creativity.generate` 的
425
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。
426
433
  - `ferment` 会复核 `active` / `draft` 假设并更新分数;`verdict=promote` 且总分 > 200 时
427
434
  升为 `validated`,`verdict=reject` 则标记为 `rejected`。
428
435
  - **失败的 LLM 调用会出现在结果里,不再被吞掉。** 返回值带 `errors`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mio-agent-runtime",
3
- "version": "0.14.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",
@@ -162,14 +162,34 @@ function mulberry32(seed) {
162
162
  }
163
163
  }
164
164
 
165
- function mixConcepts(sources, exploredPairs, rng) {
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) {
166
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 || '')
167
182
  const pairs = []
168
183
  for (let i = 0; i < sources.length; i++) {
169
184
  for (let j = i + 1; j < sources.length; j++) {
170
- const key = `${sources[i].name}||${sources[j].name}`
171
- const reverseKey = `${sources[j].name}||${sources[i].name}`
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}`
172
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
173
193
  pairs.push([sources[i], sources[j]])
174
194
  }
175
195
  }
@@ -266,14 +286,35 @@ class CreativityEngine {
266
286
  const strat = strategy || STRATEGY_CYCLE[this.strategyIndex % STRATEGY_CYCLE.length]
267
287
  this.strategyIndex++
268
288
 
269
- // 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.
270
292
  const recent = this.store.getHypotheses({ limit: 30 })
271
293
  const rejected = this.store.getHypotheses({ status: 'rejected', limit: 50 })
272
- const exploredPairs = new Set(recent.flatMap(h => (h.sourceLabels || []).map((l, i, a) => i > 0 ? `${a[i-1]}||${l}` : '')).filter(Boolean))
273
- const pairs = mixConcepts(sources, exploredPairs, this.rng.bind(this))
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)
274
305
 
275
306
  if (pairs.length === 0) {
276
- return { ideas: [], reason: 'all pairs already explored' }
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
+ }
277
318
  }
278
319
 
279
320
  // Generate hypotheses via LLM
@@ -735,7 +735,7 @@ const TOOLS = [
735
735
  type: 'boolean',
736
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.',
737
737
  },
738
- 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.' },
739
739
  },
740
740
  },
741
741
  },