@yottameta/yotta-memory 0.19.0 → 0.20.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/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/SKILL.md +2 -2
- package/bin/provider.js +1 -0
- package/bin/yotta-memory.js +328 -59
- package/package.json +1 -1
- package/references/provider-protocol.md +75 -4
- package/skill-manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## v0.20.0 (2026-09-30)
|
|
2
|
+
|
|
3
|
+
上下文分页 P2.5:先修 `memory.hook` 的 PREF 驱逐空转,再新增 `context.paging` 预算调用点。
|
|
4
|
+
|
|
5
|
+
- 修复 `memory.hook` 对画像段落驱逐空转:hook / paging 决策实际生效时,画像段落按实时可读条目渲染并套用同一套过滤;自我接入档案不进候选、不可驱逐;无 provider 时仍读 `profile.md`,基线输出逐字节不变。
|
|
6
|
+
- 修复 `profile` 只读单层目录:`private/<owner>/{prefs,bounds,commits}/YYYY/MM/` 里的分层条目现在也会进入静态画像;`context` 的实时画像路径不受影响。
|
|
7
|
+
- 新增 `context.paging`:仅在用户显式传 `--budget` 时调用;`memory.hook` 先过滤,`context.paging` 再排序 / 分页。`drop` 始终安全应用;`order` 仅在 `complete:true` 且候选未超过 500 时应用;候选集外文件只记 note、不进入输出。
|
|
8
|
+
- `context --json` 新增 `paging` 块(`status` / `provider_id` / `applied` / `dropped` / `ordered` / `truncated` / `budget` / `used` / `note`);文本仅在 `context.paging` 实际被声明或触发时增加状态行;审计沿用 `provider-audit.jsonl`,不含记忆正文。
|
|
9
|
+
- provider 子进程透传 `YOTTA_LICENSE_HOME` / `YOTTA_LICENSE_KEYS_DIR` / `YOTTA_LICENSE_BASE_URL` / `YOTTA_LICENSE_SERVER_ID`,与路线 B 授权隔离环境一致。
|
|
10
|
+
- 协议文档升级 v1.1,新增 `context.paging` 请求 / 响应 / 回落说明。
|
|
11
|
+
|
|
1
12
|
## v0.19.0 (2026-09-28)
|
|
2
13
|
- 新增扩展提供方(provider)装载点:`context` 可选调用本地 provider(capability `memory.hook`),在权限过滤后的候选集内驱逐条目;BOUND / COMMIT 与身份画像不可驱逐,预算 / 去重 / 宽限仍由引擎掌控。
|
|
3
14
|
- 未配置 / 未授权 / 超时 / 非法输出一律 fail-open 回普通记忆;`provider.json` 非法只记状态、不阻断。
|
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ Each agent has a globally unique agent ID: it is the ownership key for private m
|
|
|
107
107
|
### Profile & start-of-work context (v0.6.0 + v0.9.0 + v0.14.0)
|
|
108
108
|
|
|
109
109
|
- **profile**: aggregates `private/<owner>/` PREF / BOUND / COMMIT verbatim, grouped by type + subject + tags, written to `profile.md`; the engine infers nothing — profile conclusions are formed internally by the AI per the "memory discipline", never pasted as labels.
|
|
110
|
-
- **context**: one-shot start-of-work package — multi-agent integration rules + identity + long-term summaries first (`consolidate` output) + user profile digest + optional task-focused memory (`--focus`) + time-ordered recent corridor + deduplicated high-value backfill + boundary reminders + commitments/anchors + session loop contract; supports `--budget` for dynamic memory and `--explain` selection trace.
|
|
110
|
+
- **context**: one-shot start-of-work package — multi-agent integration rules + identity + long-term summaries first (`consolidate` output) + user profile digest + optional task-focused memory (`--focus`) + time-ordered recent corridor + deduplicated high-value backfill + boundary reminders + commitments/anchors + session loop contract; supports `--budget` for dynamic memory and `--explain` selection trace. Optional local providers can add `memory.hook` filtering and, when `--budget` is explicit, `context.paging` ordering/paging; `context --json` returns `hook` and `paging` blocks.
|
|
111
111
|
- **Memory discipline**: SKILL.md embeds a rule layer (type red lines / proactive trigger capture / know-the-user three stages / psychological grounding & alignment / bottom lines / host isolation / anti-patterns).
|
|
112
112
|
|
|
113
113
|
### Retrieval: semantic search (v0.8.0 + v0.9.0 embedding)
|
package/README.zh-CN.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
24
|
> 📖 面向用户的操作手册见 [USER_GUIDE.md](USER_GUIDE.md)。
|
|
25
|
-
> 🆕 **v0.
|
|
25
|
+
> 🆕 **v0.20.0(上下文分页 + 画像驱逐修复)**:`memory.hook` 的 PREF 驱逐空转已修复——hook / paging 决策生效时,画像段落按实时可读条目渲染并套用同一套过滤;自我接入档案不进候选、不可驱逐。新增 `context.paging`:仅在显式 `--budget` 时调用,hook 先过滤、paging 再排序与分页;`drop` 始终安全应用,`order` 仅在 `complete:true` 且未截断时应用。`context --json` 新增 `paging` 块,`--explain` 增加 `[paging]` 轨迹。配置与协议见 `references/provider-protocol.md`。
|
|
26
26
|
> 🆕 **v0.18.1(蒸馏溯源 + 日期绝对化 + 巩固标记 + 规则晋升建议 + 写入纪律)**:`distill` 新增分类型提取清单(事件 / 教训 / 待办 / 成长 / 规则边界 / 其他)+ 逐条 `[溯源: 文件#L<a>-L<b>]` + 实测质量指标 + 跳过与边界,`--json` 输出结构化报告;`consolidate` 摘要按条目 `created` 把「昨天 / 上周 / 本月 / 今年」写成绝对日期,归档副本写入巩固标记(`--undo` 剥离还原);`maintain --rules` 只读给出「重复踩坑 → 建议升级为规则」(默认阈值 3,不自动写 BOUND);记忆守则新增冲突权威顺序 6 层与写入纪律 3 条。
|
|
27
27
|
|
|
28
28
|
> 🆕 **v0.18.0(命中打点 + 容量水位 + consolidate 提案闸门 + 上下文压缩审计)**:`recall` / `explain` / `feedback --useful` / `context` 记录本地命中元数据(`hit_days` 默认保留 90 天;`hit_queries` 只存查询 8 位指纹、最多 12 槽,不存原文;随文件加密 / 备份 / 导出),`config set usage_enabled false` 或命令级 `--no-usage` 可关;只读命令与跨 owner 私密条目不写。`maintain --capacity` 只读报告容量水位、30 / 90 天活跃度、LRU / LFU 淘汰候选(冷却期 30 天 + immutable / BOUND / evergreen / pinned 豁免)与晋升建议(≥3 次命中且 ≥3 个不同查询,只出建议命令);`archive` 默认豁免冷却期与标签常青,`--force` 可显式覆盖。`consolidate` 默认等价 `--propose`(结构化报告 + `--json`),`--apply` 交互式需确认串、非交互必须 `--yes`;首次启用显示一次数据生命周期说明。`doctor` 规模体检新增 `scale_info_*` 与逐项 `ok / info / warning` 分级(`doctor.ok` 仍只看 critical)。新增 `context --audit [--from <文件|->] [--json] [--gate N]`:核对被压缩掉的内容是否已落盘,输出未落盘清单与 `remember` 建议命令,只读、不自动补写。`archive --dry-run` 只预览不改库(不动文件 / 不建事务快照 / 不写审计),无候选时不建整库快照,`archive --json` 输出结构化报告;MCP 侧只补只读 / 预演能力(`archive.dryRun`、`maintain.capacity`、`context.audit` + 内联 `auditText`、`consolidate` 只出 propose 报告),`archive --force` 与 `consolidate --apply / --undo` 仍只在命令行。上一版 v0.17.4:`rename` 消除平铺 / 分层同序号冲突(跨布局 fail-closed + 破坏性闸门 + 审计)。
|
package/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yotta-memory
|
|
3
3
|
description: 元忆 —— 有权限边界的文件式智能体记忆。文件式、零依赖、可 diff/可回滚:让任何 AI 智能体活过会话,开工 recall 恢复上下文、重要信息 remember 落盘、收工归档。类型体系 FACT(公共共享)/ PREF / BOUND / COMMIT(私密隔离)。触发:记住、别忘了、记一笔、记忆、remember、recall、跨会话、上次说到、续测、交接、归档、记忆盘、共享记忆、局域网记忆、画像、开工上下文、长期理解摘要、近期走廊、会话闭环、记忆守则、profile、context、越用越懂、语义检索、反馈、维护、蒸馏、feedback、maintain、distill、explain、自我学习、自我进化、自我提升、查看平台分页、recall 候选预过滤、任务相关记忆、--focus、--embedding、压缩遗忘、consolidate、周期摘要、自动合并、分类型衰减、回滚、备份、backup、防误删、doctor、事务快照、基线探针、恢复演练、记忆库安全扫描、scan、命中打点、容量水位、--capacity、--propose、上下文压缩审计、context --audit
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.20.0
|
|
5
5
|
license: MIT
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -36,7 +36,7 @@ license: MIT
|
|
|
36
36
|
- **可靠性基线(v0.12.0)**:`init` 对非空记忆库默认拒绝覆盖(`--attach` 接入现有库);`forget` 先移入 `.trash/` 并写审计;新增 `backup volumes / setup / status / ensure-daily / schedule / drill`(用户确认真实独立卷后默认每日自动备份)与 `backup create / list / doctor / restore`。
|
|
37
37
|
- **可靠性收口(v0.12.2)**:新增 `yotta-memory doctor` 开工检查(根目录 / 密钥库 / 索引 / 身份 / 最近备份);`maintain --apply`、`consolidate --apply`、`merge`、`archive`、`--purge` 在写入前自动创建事务快照,快照失败或严重检查异常时拒绝写入。
|
|
38
38
|
- **运行时 hook 声明(v0.13.0)**:manifest 声明 `after_milestone` / `remember_commit`;里程碑记忆必须有真实文件路径证据才标 verified,缺证据时输出 `explicit-unverified` + 一次纠偏。
|
|
39
|
-
- **扩展提供方装载点(v0.
|
|
39
|
+
- **扩展提供方装载点(v0.20.0)**:`context` 支持可选的本地扩展提供方参与「哪些记忆进上下文」:`memory.hook` 先按候选集驱逐 / 白名单过滤,`context.paging` 在显式 `--budget` 时再排序与分页。身份段落、自我接入档案、BOUND / COMMIT、预算 / 去重 / 宽限仍由引擎掌控;未配置 / 未授权 / 超时 / 非法输出一律走普通记忆。`context --json` 输出 `schema` / `hook` / `paging` / `text`,`--explain` 增加 `[paging]` 轨迹。配置与协议见 `references/provider-protocol.md`。
|
|
40
40
|
|
|
41
41
|
- **蒸馏溯源与分类型提取(v0.18.1)**:`distill` 输出新增「分类型提取清单(事件 / 教训 / 待办 / 成长 / 规则边界 / 其他)+ 质量指标 + 跳过与边界」;每条提取项带 `[溯源: 文件#L<a>-L<b>]`,报告只给实测压缩比 / 条目覆盖 / 溯源覆盖 / 要素提取率,不承诺压缩倍数或语义保真。`distill --json` 输出结构化报告;帮助补 `--owner` / `--out`。
|
|
42
42
|
- **日期绝对化与巩固标记(v0.18.1)**:`consolidate` 生成摘要正文时,按条目 `created`(缺则 `updated`)把「昨天 / 上周 / 本月 / 今年」等写成绝对日期(如 `昨天(2026-01-09)`);「最近 / 前几天 / 刚才」等模糊词不归一。归档副本末尾追加一行 `<!-- yotta-memory: consolidated to … -->` 巩固标记;`--undo` 剥离标记并还原原始内容;标记失败不影响批次,只在报告与审计里计数。
|
package/bin/provider.js
CHANGED
|
@@ -94,6 +94,7 @@ function minimalEnv() {
|
|
|
94
94
|
const keep = [
|
|
95
95
|
'PATH', 'Path', 'PATHEXT', 'SystemRoot', 'windir', 'COMSPEC', 'ComSpec',
|
|
96
96
|
'TEMP', 'TMP', 'HOME', 'USERPROFILE', 'LANG', 'LC_ALL',
|
|
97
|
+
'YOTTA_LICENSE_HOME', 'YOTTA_LICENSE_KEYS_DIR', 'YOTTA_LICENSE_BASE_URL', 'YOTTA_LICENSE_SERVER_ID',
|
|
97
98
|
];
|
|
98
99
|
const env = {};
|
|
99
100
|
for (const key of keep) {
|
package/bin/yotta-memory.js
CHANGED
|
@@ -25,7 +25,7 @@ const net = require('net');
|
|
|
25
25
|
const child_process = require('child_process');
|
|
26
26
|
const { AsyncLocalStorage } = require('async_hooks');
|
|
27
27
|
|
|
28
|
-
const VERSION = '0.
|
|
28
|
+
const VERSION = '0.20.0';
|
|
29
29
|
const CLI_VALUE_OPTS = new Set(['--type', '--limit', '--days', '--out', '--owner', '--agent', '--agent-id', '--agent-key', '--agent-key-file', '--plugin-data', '--threshold', '--scope', '--host', '--port', '--dir', '--name', '--user', '--relationship', '--source', '--weight', '--budget', '--password', '--new-password', '--recovery-key', '--recovery-key-out', '--reason', '--merge', '--model', '--subject', '--embedding', '--focus', '--embedding-timeout', '--min-age', '--min-idle', '--max-utility', '--min-group', '--period', '--to', '--id', '--time', '--tools', '--mcp-config', '--skill-dir', '--year', '--evalset', '--k', '--seed', '--bootstrap', '--gate', '--against', '--template', '--path', '--from']);
|
|
30
30
|
const CLI_FLAG_OPTS = new Set(['--project', '--all', '--unsafe', '--no-auth', '--stdio', '--onstart', '--from-current', '--restart', '--force', '--attach', '--allow-same-volume', '--verify', '--no-hint', '--encrypt', '--no-encrypt', '--password-stdin', '--json', '--manual', '--skip-schedule', '--useful', '--useless', '--undo', '--dry-run', '--apply', '--purge', '--dedup', '--batches', '--propose', '--audit', '--capacity', '--rules', '--explain', '--semantic', '--runtime', '--ablate', '--timing', '--baseline', '--probe', '--quarantine', '--restore', '--no-usage', '--yes', '--keep-memories', '--keep-identity']);
|
|
31
31
|
|
|
@@ -7460,30 +7460,55 @@ function cmdWhoami(opts) {
|
|
|
7460
7460
|
|
|
7461
7461
|
// ---- 用户画像聚合(v0.6.0,零推断:只归组呈现原文,结论由承载 AI 依据「记忆守则」内部形成)----
|
|
7462
7462
|
function profileGroups(root, owner) {
|
|
7463
|
-
const
|
|
7464
|
-
const map = {};
|
|
7463
|
+
const entries = [];
|
|
7465
7464
|
for (const t of PRIVATE_LEAF) {
|
|
7466
7465
|
const dir = path.join(root, PRIVATE_DIR, owner, t);
|
|
7467
7466
|
if (!fs.existsSync(dir)) continue;
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
if (!fs.statSync(fp).isFile()) continue;
|
|
7467
|
+
const files = [];
|
|
7468
|
+
walkEntryFiles(dir, files);
|
|
7469
|
+
for (const fp of files) {
|
|
7472
7470
|
let e;
|
|
7473
7471
|
try { e = readEntry(fp, root); } catch (err) { continue; }
|
|
7474
|
-
|
|
7475
|
-
if (!map[key]) {
|
|
7476
|
-
map[key] = { type: e.type, subject: e.subject || '', tags: e.tags || [], items: [] };
|
|
7477
|
-
groups.push(map[key]);
|
|
7478
|
-
}
|
|
7479
|
-
map[key].items.push({
|
|
7480
|
-
file: e.file, statement: e.statement, confidence: e.confidence,
|
|
7481
|
-
last_accessed: e.last_accessed, updated: e.updated,
|
|
7482
|
-
});
|
|
7472
|
+
entries.push(e);
|
|
7483
7473
|
}
|
|
7484
7474
|
}
|
|
7475
|
+
return profileGroupsFromEntries(entries);
|
|
7476
|
+
}
|
|
7477
|
+
function profileGroupsFromEntries(entries) {
|
|
7478
|
+
const groups = [];
|
|
7479
|
+
const map = {};
|
|
7480
|
+
for (const e of (entries || [])) {
|
|
7481
|
+
const key = e.type + '\u0000' + (e.subject || '') + '\u0000' + (e.tags || []).join(',');
|
|
7482
|
+
if (!map[key]) {
|
|
7483
|
+
map[key] = { type: e.type, subject: e.subject || '', tags: e.tags || [], items: [] };
|
|
7484
|
+
groups.push(map[key]);
|
|
7485
|
+
}
|
|
7486
|
+
map[key].items.push({
|
|
7487
|
+
file: e.file, statement: e.statement, confidence: e.confidence,
|
|
7488
|
+
last_accessed: e.last_accessed, updated: e.updated,
|
|
7489
|
+
});
|
|
7490
|
+
}
|
|
7485
7491
|
return groups;
|
|
7486
7492
|
}
|
|
7493
|
+
function renderProfileText(owner, groups) {
|
|
7494
|
+
const lines = [];
|
|
7495
|
+
lines.push('# 用户画像(' + owner + ')');
|
|
7496
|
+
lines.push('');
|
|
7497
|
+
lines.push('> 生成: yotta-memory profile | 引擎零推断:以下为私密记忆原文的结构化归组,画像结论由承载 AI 依据「记忆守则」在内部形成,不当面贴标签。');
|
|
7498
|
+
lines.push('> 刷新: ' + today());
|
|
7499
|
+
lines.push('');
|
|
7500
|
+
for (const g of (groups || [])) {
|
|
7501
|
+
lines.push('## ' + g.type + ' · ' + (g.subject || '(无主题)') + (g.tags.length ? ' [tags: ' + g.tags.join(', ') + ']' : ''));
|
|
7502
|
+
lines.push('');
|
|
7503
|
+
for (const it of g.items) {
|
|
7504
|
+
const acc = it.last_accessed ? '最近访问 ' + it.last_accessed : '未访问';
|
|
7505
|
+
lines.push('- ' + it.statement + '(confidence ' + it.confidence + ' · ' + acc + ')');
|
|
7506
|
+
lines.push(' - ' + it.file);
|
|
7507
|
+
}
|
|
7508
|
+
lines.push('');
|
|
7509
|
+
}
|
|
7510
|
+
return lines.join('\n');
|
|
7511
|
+
}
|
|
7487
7512
|
function profileCore(opts) {
|
|
7488
7513
|
opts = opts || {};
|
|
7489
7514
|
const root = userRoot();
|
|
@@ -7511,23 +7536,7 @@ function profileCore(opts) {
|
|
|
7511
7536
|
}
|
|
7512
7537
|
const groups = profileGroups(root, owner);
|
|
7513
7538
|
if (!groups.length) return { error: false, text: '该智能体(' + owner + ')暂无画像:private/' + owner + '/ 下无 PREF / BOUND / COMMIT 条目。' };
|
|
7514
|
-
const
|
|
7515
|
-
lines.push('# 用户画像(' + owner + ')');
|
|
7516
|
-
lines.push('');
|
|
7517
|
-
lines.push('> 生成: yotta-memory profile | 引擎零推断:以下为私密记忆原文的结构化归组,画像结论由承载 AI 依据「记忆守则」在内部形成,不当面贴标签。');
|
|
7518
|
-
lines.push('> 刷新: ' + today());
|
|
7519
|
-
lines.push('');
|
|
7520
|
-
for (const g of groups) {
|
|
7521
|
-
lines.push('## ' + g.type + ' · ' + (g.subject || '(无主题)') + (g.tags.length ? ' [tags: ' + g.tags.join(', ') + ']' : ''));
|
|
7522
|
-
lines.push('');
|
|
7523
|
-
for (const it of g.items) {
|
|
7524
|
-
const acc = it.last_accessed ? '最近访问 ' + it.last_accessed : '未访问';
|
|
7525
|
-
lines.push('- ' + it.statement + '(confidence ' + it.confidence + ' · ' + acc + ')');
|
|
7526
|
-
lines.push(' - ' + it.file);
|
|
7527
|
-
}
|
|
7528
|
-
lines.push('');
|
|
7529
|
-
}
|
|
7530
|
-
const text = lines.join('\n');
|
|
7539
|
+
const text = renderProfileText(owner, groups);
|
|
7531
7540
|
const outFile = writeProfileText(root, owner, text + '\n');
|
|
7532
7541
|
return { error: false, text: text + '\n\n[已写入] ' + outFile };
|
|
7533
7542
|
}
|
|
@@ -7566,6 +7575,25 @@ function compareContextRecent(a, b) {
|
|
|
7566
7575
|
const MEMORY_HOOK_MAX_CANDIDATES = 500;
|
|
7567
7576
|
const MEMORY_HOOK_STATEMENT_LIMIT = 2000;
|
|
7568
7577
|
|
|
7578
|
+
function contextEntryLine(entry) {
|
|
7579
|
+
return '- [' + entry.type + '] ' + entry.subject + ': ' + entry.statement;
|
|
7580
|
+
}
|
|
7581
|
+
|
|
7582
|
+
function isSelfProfileEntry(entry, owner) {
|
|
7583
|
+
if (!entry || entry.type !== 'PREF' || entry.subject !== SELF_PROFILE_SUBJECT) return false;
|
|
7584
|
+
if (owner && entry.owner && entry.owner !== owner) return false;
|
|
7585
|
+
const file = String(entry.file || '').replace(/\\/g, '/');
|
|
7586
|
+
return owner ? file.indexOf('private/' + owner + '/prefs/') === 0 : false;
|
|
7587
|
+
}
|
|
7588
|
+
|
|
7589
|
+
function contextPageableEntries(readableEntries, owner) {
|
|
7590
|
+
return (readableEntries || [])
|
|
7591
|
+
.filter(function (entry) {
|
|
7592
|
+
return entry.type !== 'BOUND' && entry.type !== 'COMMIT' && !isSelfProfileEntry(entry, owner);
|
|
7593
|
+
})
|
|
7594
|
+
.sort(compareContextRecent);
|
|
7595
|
+
}
|
|
7596
|
+
|
|
7569
7597
|
function hookBlock(status, providerId, note) {
|
|
7570
7598
|
return {
|
|
7571
7599
|
status: status || 'not_installed',
|
|
@@ -7590,6 +7618,130 @@ function hookStatusText(hook) {
|
|
|
7590
7618
|
return hook.status;
|
|
7591
7619
|
}
|
|
7592
7620
|
|
|
7621
|
+
function pagingBlock(status, providerId, note) {
|
|
7622
|
+
return {
|
|
7623
|
+
status: status || 'not_installed',
|
|
7624
|
+
provider_id: providerId || '',
|
|
7625
|
+
applied: false,
|
|
7626
|
+
dropped: [],
|
|
7627
|
+
ordered: [],
|
|
7628
|
+
truncated: false,
|
|
7629
|
+
budget: 0,
|
|
7630
|
+
used: 0,
|
|
7631
|
+
note: note || '',
|
|
7632
|
+
};
|
|
7633
|
+
}
|
|
7634
|
+
|
|
7635
|
+
function pagingStatusText(paging) {
|
|
7636
|
+
if (!paging) return '';
|
|
7637
|
+
if (paging.status === 'active') {
|
|
7638
|
+
const who = paging.provider_id ? '提供方 ' + paging.provider_id : '提供方';
|
|
7639
|
+
return '已应用(' + who + ';丢弃 ' + paging.dropped.length + ' / 排序 ' + paging.ordered.length + ')';
|
|
7640
|
+
}
|
|
7641
|
+
if (paging.status === 'license_required') return '需授权(该能力需要授权后使用;普通记忆不受影响)';
|
|
7642
|
+
if (paging.status === 'timeout') return '未生效(提供方超时,已回落普通记忆)';
|
|
7643
|
+
if (paging.status === 'invalid_output') return '未生效(提供方输出无效,已回落普通记忆)';
|
|
7644
|
+
if (paging.status === 'error') return '未生效(提供方异常,已回落普通记忆)';
|
|
7645
|
+
return '';
|
|
7646
|
+
}
|
|
7647
|
+
|
|
7648
|
+
function pagingCandidatePayload(entry, baselineSection) {
|
|
7649
|
+
return {
|
|
7650
|
+
file: entry.file,
|
|
7651
|
+
type: entry.type,
|
|
7652
|
+
subject: entry.subject,
|
|
7653
|
+
statement: String(entry.statement || '').slice(0, MEMORY_HOOK_STATEMENT_LIMIT),
|
|
7654
|
+
created: entry.created || '',
|
|
7655
|
+
updated: entry.updated || '',
|
|
7656
|
+
chars: contextEntryLine(entry).length,
|
|
7657
|
+
baseline_section: baselineSection || 'high_value',
|
|
7658
|
+
};
|
|
7659
|
+
}
|
|
7660
|
+
|
|
7661
|
+
function applyContextPaging(pageable, opts, meta) {
|
|
7662
|
+
opts = opts || {};
|
|
7663
|
+
meta = meta || {};
|
|
7664
|
+
const budget = parseInt(opts.budget || 0, 10) || 0;
|
|
7665
|
+
if (budget <= 0) {
|
|
7666
|
+
return {
|
|
7667
|
+
block: pagingBlock('not_requested', '', '未传 --budget,未调用 context.paging'),
|
|
7668
|
+
drop: new Set(),
|
|
7669
|
+
order: null,
|
|
7670
|
+
applied: false,
|
|
7671
|
+
};
|
|
7672
|
+
}
|
|
7673
|
+
const block = pagingBlock('not_installed', '', '');
|
|
7674
|
+
block.budget = budget;
|
|
7675
|
+
const truncated = pageable.length > MEMORY_HOOK_MAX_CANDIDATES;
|
|
7676
|
+
block.truncated = truncated;
|
|
7677
|
+
const candidates = pageable.slice(0, MEMORY_HOOK_MAX_CANDIDATES);
|
|
7678
|
+
const payload = {
|
|
7679
|
+
agent: opts.owner || '',
|
|
7680
|
+
budget: budget,
|
|
7681
|
+
focus: opts.focus || '',
|
|
7682
|
+
truncated: truncated,
|
|
7683
|
+
protected: Array.isArray(meta.protected) ? meta.protected.slice() : [],
|
|
7684
|
+
sections: meta.sections || {},
|
|
7685
|
+
candidates: candidates.map(function (entry) {
|
|
7686
|
+
return pagingCandidatePayload(entry, (meta.baselineSections || {})[entry.file]);
|
|
7687
|
+
}),
|
|
7688
|
+
};
|
|
7689
|
+
let run;
|
|
7690
|
+
try {
|
|
7691
|
+
run = require('./provider').runCapability('context.paging', payload);
|
|
7692
|
+
} catch (e) {
|
|
7693
|
+
block.status = 'error';
|
|
7694
|
+
block.note = '预算分页失败:' + e.message;
|
|
7695
|
+
return { block: block, drop: new Set(), order: null, applied: false };
|
|
7696
|
+
}
|
|
7697
|
+
block.status = run.status;
|
|
7698
|
+
block.provider_id = run.provider_id || '';
|
|
7699
|
+
block.note = run.note || run.message || '';
|
|
7700
|
+
if (run.status !== 'active' || !run.data || typeof run.data !== 'object') {
|
|
7701
|
+
return { block: block, drop: new Set(), order: null, applied: false };
|
|
7702
|
+
}
|
|
7703
|
+
const sent = new Set(payload.candidates.map(function (item) { return item.file; }));
|
|
7704
|
+
const rejected = [];
|
|
7705
|
+
const dropped = [];
|
|
7706
|
+
for (const item of (Array.isArray(run.data.drop) ? run.data.drop : [])) {
|
|
7707
|
+
const file = String(item || '');
|
|
7708
|
+
if (!file) continue;
|
|
7709
|
+
if (!sent.has(file)) {
|
|
7710
|
+
if (rejected.indexOf(file) === -1) rejected.push(file);
|
|
7711
|
+
continue;
|
|
7712
|
+
}
|
|
7713
|
+
if (dropped.indexOf(file) === -1) dropped.push(file);
|
|
7714
|
+
}
|
|
7715
|
+
let order = null;
|
|
7716
|
+
if (Array.isArray(run.data.order)) {
|
|
7717
|
+
if (run.data.complete === true && !truncated) {
|
|
7718
|
+
const picked = [];
|
|
7719
|
+
for (const item of run.data.order) {
|
|
7720
|
+
const file = String(item || '');
|
|
7721
|
+
if (!file) continue;
|
|
7722
|
+
if (!sent.has(file)) {
|
|
7723
|
+
if (rejected.indexOf(file) === -1) rejected.push(file);
|
|
7724
|
+
continue;
|
|
7725
|
+
}
|
|
7726
|
+
if (picked.indexOf(file) === -1) picked.push(file);
|
|
7727
|
+
}
|
|
7728
|
+
order = picked;
|
|
7729
|
+
} else if (truncated) {
|
|
7730
|
+
block.note = block.note || '候选超过 ' + MEMORY_HOOK_MAX_CANDIDATES + ',order 未应用(保护)';
|
|
7731
|
+
} else {
|
|
7732
|
+
block.note = block.note || 'provider 未声明 complete=true,order 未应用(保护)';
|
|
7733
|
+
}
|
|
7734
|
+
}
|
|
7735
|
+
if (rejected.length) {
|
|
7736
|
+
const text = '候选集外文件已忽略:' + rejected.join(', ');
|
|
7737
|
+
block.note = block.note ? block.note + ';' + text : text;
|
|
7738
|
+
}
|
|
7739
|
+
block.dropped = dropped;
|
|
7740
|
+
block.ordered = order || [];
|
|
7741
|
+
block.applied = dropped.length > 0 || (order !== null && order.length > 0);
|
|
7742
|
+
return { block: block, drop: new Set(dropped), order: order, applied: block.applied };
|
|
7743
|
+
}
|
|
7744
|
+
|
|
7593
7745
|
/**
|
|
7594
7746
|
* memory.hook 装载点:只把「可驱逐」条目(非 BOUND / COMMIT)交给 provider。
|
|
7595
7747
|
* provider 只能返回本次候选集内的 evict(驱逐清单);complete=true 时可选 selected(白名单)。
|
|
@@ -7598,9 +7750,7 @@ function hookStatusText(hook) {
|
|
|
7598
7750
|
*/
|
|
7599
7751
|
function applyMemoryHook(readableEntries, opts) {
|
|
7600
7752
|
const block = hookBlock('not_installed', '', '');
|
|
7601
|
-
const pageable = readableEntries
|
|
7602
|
-
.filter(function (entry) { return entry.type !== 'BOUND' && entry.type !== 'COMMIT'; })
|
|
7603
|
-
.sort(compareContextRecent);
|
|
7753
|
+
const pageable = contextPageableEntries(readableEntries, opts.owner || '');
|
|
7604
7754
|
const truncated = pageable.length > MEMORY_HOOK_MAX_CANDIDATES;
|
|
7605
7755
|
const payload = {
|
|
7606
7756
|
agent: opts.owner || '',
|
|
@@ -7665,6 +7815,25 @@ function applyMemoryHook(readableEntries, opts) {
|
|
|
7665
7815
|
return { block: block, evict: new Set(evicted), selected: selected };
|
|
7666
7816
|
}
|
|
7667
7817
|
|
|
7818
|
+
function replaceContextProfileSection(lines, profileText, fence) {
|
|
7819
|
+
const start = lines.indexOf('## 2. 用户画像摘要');
|
|
7820
|
+
if (start === -1) return false;
|
|
7821
|
+
const marker = lines.indexOf('## 2.5 长期理解摘要');
|
|
7822
|
+
const end = marker === -1 ? lines.length : marker;
|
|
7823
|
+
if (end < start) return false;
|
|
7824
|
+
const block = ['## 2. 用户画像摘要', ''];
|
|
7825
|
+
if (profileText) {
|
|
7826
|
+
block.push(fence + 'markdown');
|
|
7827
|
+
block.push(String(profileText).trim());
|
|
7828
|
+
block.push(fence);
|
|
7829
|
+
} else {
|
|
7830
|
+
block.push('(该身份暂无画像;可运行 yotta-memory profile 生成)');
|
|
7831
|
+
}
|
|
7832
|
+
block.push('');
|
|
7833
|
+
lines.splice.apply(lines, [start, end - start].concat(block));
|
|
7834
|
+
return true;
|
|
7835
|
+
}
|
|
7836
|
+
|
|
7668
7837
|
function contextCore(opts) {
|
|
7669
7838
|
opts = opts || {};
|
|
7670
7839
|
const roots = memoryRoots();
|
|
@@ -7698,6 +7867,7 @@ function contextCore(opts) {
|
|
|
7698
7867
|
const lines = [];
|
|
7699
7868
|
const shownFiles = new Set();
|
|
7700
7869
|
const usedFiles = new Set();
|
|
7870
|
+
let usedDynamicChars = 0;
|
|
7701
7871
|
const FENCE = String.fromCharCode(96, 96, 96);
|
|
7702
7872
|
function usedChars() { return lines.reduce(function (s, x) { return s + String(x).length + 1; }, 0); }
|
|
7703
7873
|
function appendContextEntry(entry, reason, options) {
|
|
@@ -7707,7 +7877,7 @@ function contextCore(opts) {
|
|
|
7707
7877
|
trace.push('[dropped] ' + entry.file + ' reason: duplicate');
|
|
7708
7878
|
return false;
|
|
7709
7879
|
}
|
|
7710
|
-
const line =
|
|
7880
|
+
const line = contextEntryLine(entry);
|
|
7711
7881
|
if (options.budget !== false && budget > 0 && usedChars() + line.length > budget) {
|
|
7712
7882
|
trace.push('[dropped] ' + entry.file + ' reason: budget_exceeded');
|
|
7713
7883
|
return false;
|
|
@@ -7715,6 +7885,7 @@ function contextCore(opts) {
|
|
|
7715
7885
|
lines.push(line);
|
|
7716
7886
|
shownFiles.add(entry.file);
|
|
7717
7887
|
usedFiles.add(entry.file);
|
|
7888
|
+
usedDynamicChars += line.length;
|
|
7718
7889
|
trace.push('[included] ' + entry.file + ' reason: ' + reason);
|
|
7719
7890
|
return true;
|
|
7720
7891
|
}
|
|
@@ -7784,19 +7955,124 @@ function contextCore(opts) {
|
|
|
7784
7955
|
if (memHook.evict.has(entry.file)) return false;
|
|
7785
7956
|
return true;
|
|
7786
7957
|
}
|
|
7958
|
+
let focusedEntries = [];
|
|
7959
|
+
if (focus) {
|
|
7960
|
+
const focused = recallCore(focus, {
|
|
7961
|
+
limit: limit,
|
|
7962
|
+
owner: owner,
|
|
7963
|
+
unsafe: unsafe,
|
|
7964
|
+
selfAgent: selfAgent,
|
|
7965
|
+
years: years,
|
|
7966
|
+
embedding: embeddingCommand,
|
|
7967
|
+
embeddingTimeout: embeddingTimeout,
|
|
7968
|
+
noUsage: true
|
|
7969
|
+
});
|
|
7970
|
+
focusedEntries = focused.entries || [];
|
|
7971
|
+
}
|
|
7972
|
+
const pageable = contextPageableEntries(readableEntries, owner).filter(hookAllows);
|
|
7973
|
+
const summaryLimit = Math.min(3, Math.max(0, limit));
|
|
7974
|
+
const summaryCandidates = pageable.filter(isConsolidatedSummary).slice(0, summaryLimit);
|
|
7975
|
+
const summaryFiles = new Set(summaryCandidates.map(function (entry) { return entry.file; }));
|
|
7976
|
+
const pageableFiles = new Set(pageable.map(function (entry) { return entry.file; }));
|
|
7977
|
+
const focusCandidates = focusedEntries.filter(function (entry) {
|
|
7978
|
+
return hookAllows(entry) && pageableFiles.has(entry.file);
|
|
7979
|
+
});
|
|
7980
|
+
const focusFiles = new Set(focusCandidates.map(function (entry) { return entry.file; }));
|
|
7981
|
+
const corridorCandidates = pageable
|
|
7982
|
+
.filter(function (entry) {
|
|
7983
|
+
return !isConsolidatedSummary(entry) && !summaryFiles.has(entry.file) && !focusFiles.has(entry.file);
|
|
7984
|
+
})
|
|
7985
|
+
.slice(0, limit);
|
|
7986
|
+
const corridorFiles = new Set(corridorCandidates.map(function (entry) { return entry.file; }));
|
|
7987
|
+
const highValueCandidates = pageable
|
|
7988
|
+
.filter(function (entry) {
|
|
7989
|
+
return !isConsolidatedSummary(entry) && !summaryFiles.has(entry.file) && !focusFiles.has(entry.file) && !corridorFiles.has(entry.file);
|
|
7990
|
+
})
|
|
7991
|
+
.map(function (entry) { return { e: entry, s: 0.5 * importanceScore(entry) + 0.5 * utilityScore(entry) }; })
|
|
7992
|
+
.sort(function (a, b) { return b.s - a.s; })
|
|
7993
|
+
.slice(0, limit)
|
|
7994
|
+
.map(function (item) { return item.e; });
|
|
7995
|
+
const baselineSections = {};
|
|
7996
|
+
function markBaseline(entries, section) {
|
|
7997
|
+
for (const entry of entries) baselineSections[entry.file] = section;
|
|
7998
|
+
}
|
|
7999
|
+
markBaseline(summaryCandidates, 'summary');
|
|
8000
|
+
markBaseline(focusCandidates, 'focus');
|
|
8001
|
+
markBaseline(corridorCandidates, 'corridor');
|
|
8002
|
+
markBaseline(highValueCandidates, 'high_value');
|
|
8003
|
+
const paging = applyContextPaging(pageable, {
|
|
8004
|
+
owner: owner,
|
|
8005
|
+
budget: budget,
|
|
8006
|
+
focus: focus,
|
|
8007
|
+
limit: limit,
|
|
8008
|
+
}, {
|
|
8009
|
+
protected: readableEntries
|
|
8010
|
+
.filter(function (entry) { return isSelfProfileEntry(entry, owner); })
|
|
8011
|
+
.map(function (entry) { return entry.file; }),
|
|
8012
|
+
sections: {
|
|
8013
|
+
summary: summaryCandidates.map(function (entry) { return entry.file; }),
|
|
8014
|
+
focus: focusCandidates.map(function (entry) { return entry.file; }),
|
|
8015
|
+
corridor: corridorCandidates.map(function (entry) { return entry.file; }),
|
|
8016
|
+
high_value: highValueCandidates.map(function (entry) { return entry.file; }),
|
|
8017
|
+
},
|
|
8018
|
+
baselineSections: baselineSections,
|
|
8019
|
+
});
|
|
8020
|
+
const pagingState = paging.block;
|
|
8021
|
+
const pagingOrder = new Map();
|
|
8022
|
+
for (let i = 0; i < pagingState.ordered.length; i++) pagingOrder.set(pagingState.ordered[i], i);
|
|
8023
|
+
function pagingAllows(entry) {
|
|
8024
|
+
return !entry || !paging.drop.has(entry.file);
|
|
8025
|
+
}
|
|
8026
|
+
function decisionAllows(entry) {
|
|
8027
|
+
return hookAllows(entry) && pagingAllows(entry);
|
|
8028
|
+
}
|
|
8029
|
+
function pagingOrderIndex(entry) {
|
|
8030
|
+
if (!entry || !pagingOrder.has(entry.file)) return Number.MAX_SAFE_INTEGER;
|
|
8031
|
+
return pagingOrder.get(entry.file);
|
|
8032
|
+
}
|
|
8033
|
+
function compareWithPaging(a, b, fallback) {
|
|
8034
|
+
const byOrder = pagingOrderIndex(a) - pagingOrderIndex(b);
|
|
8035
|
+
if (byOrder !== 0) return byOrder;
|
|
8036
|
+
return fallback(a, b);
|
|
8037
|
+
}
|
|
8038
|
+
if ((hookState.applied || pagingState.applied) && owner) {
|
|
8039
|
+
const liveGroups = profileGroupsFromEntries(
|
|
8040
|
+
readableEntries
|
|
8041
|
+
.filter(function (entry) { return entry.type === 'PREF' || entry.type === 'BOUND' || entry.type === 'COMMIT'; })
|
|
8042
|
+
.filter(decisionAllows)
|
|
8043
|
+
);
|
|
8044
|
+
replaceContextProfileSection(lines, renderProfileText(owner, liveGroups), FENCE);
|
|
8045
|
+
trace.push('[profile] source: live reason: ' + (hookState.applied ? 'memory.hook' : 'context.paging'));
|
|
8046
|
+
}
|
|
8047
|
+
const statusLines = [];
|
|
7787
8048
|
if (hookState.status !== 'not_installed') {
|
|
7788
|
-
|
|
7789
|
-
lines.splice(anchor === -1 ? 2 : anchor, 0, '- 扩展装载(memory.hook): ' + hookStatusText(hookState), '');
|
|
8049
|
+
statusLines.push('- 扩展装载(memory.hook): ' + hookStatusText(hookState));
|
|
7790
8050
|
trace.push('[hook] status: ' + hookState.status
|
|
7791
8051
|
+ ' provider: ' + (hookState.provider_id || '-')
|
|
7792
8052
|
+ ' evicted: ' + hookState.evicted.length
|
|
7793
8053
|
+ ' dropped: ' + hookState.dropped.length);
|
|
7794
8054
|
}
|
|
8055
|
+
const pagingText = pagingStatusText(pagingState);
|
|
8056
|
+
if (pagingText) {
|
|
8057
|
+
statusLines.push('- 预算分页(context.paging): ' + pagingText);
|
|
8058
|
+
trace.push('[paging] status: ' + pagingState.status
|
|
8059
|
+
+ ' provider: ' + (pagingState.provider_id || '-')
|
|
8060
|
+
+ ' dropped: ' + pagingState.dropped.length
|
|
8061
|
+
+ ' ordered: ' + pagingState.ordered.length
|
|
8062
|
+
+ ' truncated: ' + (pagingState.truncated ? 'true' : 'false'));
|
|
8063
|
+
}
|
|
8064
|
+
if (statusLines.length) {
|
|
8065
|
+
const anchor = lines.indexOf('## 1. 身份');
|
|
8066
|
+
statusLines.push('');
|
|
8067
|
+
lines.splice.apply(lines, [anchor === -1 ? 2 : anchor, 0].concat(statusLines));
|
|
8068
|
+
}
|
|
7795
8069
|
|
|
7796
8070
|
lines.push('## 2.5 长期理解摘要');
|
|
7797
8071
|
lines.push('');
|
|
7798
|
-
const summaries = readableEntries
|
|
7799
|
-
|
|
8072
|
+
const summaries = readableEntries
|
|
8073
|
+
.filter(isConsolidatedSummary)
|
|
8074
|
+
.filter(decisionAllows)
|
|
8075
|
+
.sort(function (a, b) { return compareWithPaging(a, b, compareContextRecent); });
|
|
7800
8076
|
if (!summaries.length) lines.push('(暂无周期摘要;可用 yotta-memory consolidate --apply 生成)');
|
|
7801
8077
|
for (const e of summaries.slice(0, summaryLimit)) {
|
|
7802
8078
|
appendContextEntry(e, 'summary_priority', { budget: false });
|
|
@@ -7806,19 +8082,11 @@ function contextCore(opts) {
|
|
|
7806
8082
|
if (focus) {
|
|
7807
8083
|
lines.push('## 2.6 任务相关记忆(--focus)');
|
|
7808
8084
|
lines.push('');
|
|
7809
|
-
const focused = recallCore(focus, {
|
|
7810
|
-
limit: limit,
|
|
7811
|
-
owner: owner,
|
|
7812
|
-
unsafe: unsafe,
|
|
7813
|
-
selfAgent: selfAgent,
|
|
7814
|
-
years: years,
|
|
7815
|
-
embedding: embeddingCommand,
|
|
7816
|
-
embeddingTimeout: embeddingTimeout,
|
|
7817
|
-
noUsage: true
|
|
7818
|
-
});
|
|
7819
|
-
const focusedEntries = focused.entries || [];
|
|
7820
8085
|
if (!focusedEntries.length) lines.push('(无匹配记忆)');
|
|
7821
|
-
|
|
8086
|
+
const focusedVisible = focusedEntries
|
|
8087
|
+
.filter(decisionAllows)
|
|
8088
|
+
.sort(function (a, b) { return compareWithPaging(a, b, function () { return 0; }); });
|
|
8089
|
+
for (const e of focusedVisible) {
|
|
7822
8090
|
appendContextEntry(e, 'focus_match score: ' + round3(e.score));
|
|
7823
8091
|
}
|
|
7824
8092
|
lines.push('');
|
|
@@ -7828,8 +8096,8 @@ function contextCore(opts) {
|
|
|
7828
8096
|
lines.push('');
|
|
7829
8097
|
const corridor = readableEntries
|
|
7830
8098
|
.filter(function (e) { return !isConsolidatedSummary(e) && e.type !== 'BOUND' && e.type !== 'COMMIT'; })
|
|
7831
|
-
.
|
|
7832
|
-
.
|
|
8099
|
+
.filter(decisionAllows)
|
|
8100
|
+
.sort(function (a, b) { return compareWithPaging(a, b, compareContextRecent); })
|
|
7833
8101
|
.filter(function (e) { return !shownFiles.has(e.file); })
|
|
7834
8102
|
.slice(0, limit);
|
|
7835
8103
|
if (!corridor.length) lines.push('(暂无近期记忆)');
|
|
@@ -7839,9 +8107,9 @@ function contextCore(opts) {
|
|
|
7839
8107
|
lines.push('## 4. 近期高价值记忆(补位)');
|
|
7840
8108
|
lines.push('');
|
|
7841
8109
|
const highValue = readableEntries
|
|
7842
|
-
.filter(function (e) { return !isConsolidatedSummary(e) && e.type !== 'BOUND' && e.type !== 'COMMIT' && !shownFiles.has(e.file) &&
|
|
8110
|
+
.filter(function (e) { return !isConsolidatedSummary(e) && e.type !== 'BOUND' && e.type !== 'COMMIT' && !shownFiles.has(e.file) && decisionAllows(e); })
|
|
7843
8111
|
.map(function (e) { return { e: e, s: 0.5 * importanceScore(e) + 0.5 * utilityScore(e) }; })
|
|
7844
|
-
.sort(function (a, b) { return b.s -
|
|
8112
|
+
.sort(function (a, b) { return compareWithPaging(a.e, b.e, function (x, y) { return y.s - x.s; }); })
|
|
7845
8113
|
.slice(0, limit);
|
|
7846
8114
|
if (!highValue.length) lines.push('(暂无补位条目)');
|
|
7847
8115
|
for (const h of highValue) appendContextEntry(h.e, 'high_value_backfill');
|
|
@@ -7899,13 +8167,14 @@ function contextCore(opts) {
|
|
|
7899
8167
|
for (const message of reliability.warnings) lines.push('- [警告] ' + message);
|
|
7900
8168
|
if (!reliability.ok) lines.push('- 破坏性写入已锁定:先运行 yotta-memory doctor 修复严重问题。');
|
|
7901
8169
|
}
|
|
7902
|
-
|
|
8170
|
+
pagingState.used = usedDynamicChars;
|
|
8171
|
+
return { error: false, exitCode: 0, text: lines.join('\n'), trace: trace, hook: hookState, paging: pagingState };
|
|
7903
8172
|
}
|
|
7904
8173
|
function cmdContext(opts) {
|
|
7905
8174
|
const r = contextCore(opts);
|
|
7906
8175
|
if (opts.json) {
|
|
7907
8176
|
if (r.report) console.log(JSON.stringify(r.report, null, 2));
|
|
7908
|
-
else console.log(JSON.stringify({ schema: 1, hook: r.hook || null, text: r.text }, null, 2));
|
|
8177
|
+
else console.log(JSON.stringify({ schema: 1, hook: r.hook || null, paging: r.paging || null, text: r.text }, null, 2));
|
|
7909
8178
|
} else {
|
|
7910
8179
|
console.log(r.text);
|
|
7911
8180
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yottameta/yotta-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Yuanyi (元忆) — boundary-aware, file-based memory for AI agents. File-based, zero-dependency, diff/rollback-able; FACT/PREF/BOUND/COMMIT types (public shared / private isolated), user-level + project-level storage; v0.12 reliability baseline (init guard, trash-based deletion, independent-volume backup/list/doctor/restore, start-of-work doctor, transactional snapshots before destructive writes), v0.10 consolidation (periodic summaries with provenance, near-duplicate auto-merge, per-type decay, batch audit + rollback), v0.9 recall quality + context focus + optional local embedding plugin, plus v0.8 semantic search, feedback loop, self-organization and distillation.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# 扩展提供方协议 v1(元忆 · capability `memory.hook`)
|
|
1
|
+
# 扩展提供方协议 v1.1(元忆 · capability `memory.hook` / `context.paging`)
|
|
2
2
|
|
|
3
3
|
元忆可以可选地调用一个由用户显式配置的**本地扩展提供方(provider)**,由它参与「哪些记忆进上下文」。
|
|
4
4
|
未配置提供方时,`context` 的行为与输出与之前完全一致;任何失败都回落到普通记忆,不阻断命令。
|
|
5
5
|
|
|
6
|
+
本版协议包含两个 capability:
|
|
7
|
+
|
|
8
|
+
- `memory.hook`:在装配前过滤候选集(`evict` / `selected`)。
|
|
9
|
+
- `context.paging`:在用户显式传 `--budget` 时,对已通过 `memory.hook` 的候选集做排序与分页(`drop` / `order`)。
|
|
10
|
+
|
|
6
11
|
## 1. 配置
|
|
7
12
|
|
|
8
13
|
配置文件:`<YOTTA_PROVIDER_HOME>/provider.json`,默认 `~/.yottameta/provider.json`。
|
|
@@ -15,7 +20,7 @@
|
|
|
15
20
|
{
|
|
16
21
|
"id": "local-provider",
|
|
17
22
|
"version": "0.1.0",
|
|
18
|
-
"capabilities": ["memory.hook"],
|
|
23
|
+
"capabilities": ["memory.hook", "context.paging"],
|
|
19
24
|
"command": ["node", "C:/path/to/provider.js"],
|
|
20
25
|
"timeout_ms": 600
|
|
21
26
|
}
|
|
@@ -69,7 +74,7 @@
|
|
|
69
74
|
|
|
70
75
|
- **`evict`(推荐)**:要从上下文里驱逐的条目清单。每个 `file` 必须出现在本次 `candidates` 中;候选集外的 file 会被丢弃并记入 `dropped`。未见过的条目默认保留 —— 候选很多时也安全。
|
|
71
76
|
- **`selected`(可选白名单)**:仅在 `complete: true` 且本次未 `truncated` 时接受。每个 `file` 必须 ∈ `candidates`;未列入的候选会被驱逐。
|
|
72
|
-
- 元忆侧仍强制执行:BOUND / COMMIT
|
|
77
|
+
- 元忆侧仍强制执行:BOUND / COMMIT 与自我接入档案不可驱逐;`## 1. 身份` 段落不受影响,画像中的其它条目随条目一并参与驱逐;预算、去重、宽限、章节顺序由引擎决定。
|
|
73
78
|
|
|
74
79
|
需要授权或不可用时:
|
|
75
80
|
|
|
@@ -91,7 +96,73 @@
|
|
|
91
96
|
`context --json` 的 `hook` 块给出 `status` / `provider_id` / `applied` / `evicted` / `dropped` / `note`;
|
|
92
97
|
`context --explain` 的 trace 里追加一行 `[hook] ...`。
|
|
93
98
|
|
|
94
|
-
## 5.
|
|
99
|
+
## 5. `context.paging`(v1.1 新增)
|
|
100
|
+
|
|
101
|
+
### 5.1 调用时机
|
|
102
|
+
|
|
103
|
+
- 仅在用户显式执行 `context` 且传了 `--budget`(大于 0)时调用;不传预算不调用,状态为 `not_requested`。
|
|
104
|
+
- 调用顺序:`memory.hook` 先过滤,`context.paging` 再排序 / 分页。
|
|
105
|
+
- 候选集与 `memory.hook` 同源:权限过滤后、去掉 BOUND / COMMIT / 自我接入档案;最多 500 条。
|
|
106
|
+
- `context.paging` 不能覆盖引擎硬约束:身份段落、自我接入档案、BOUND / COMMIT 不参与;最终字符预算仍由元忆引擎强制。
|
|
107
|
+
|
|
108
|
+
### 5.2 请求
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"schema": 1,
|
|
113
|
+
"capability": "context.paging",
|
|
114
|
+
"request_id": "<uuid>",
|
|
115
|
+
"payload": {
|
|
116
|
+
"agent": "codex",
|
|
117
|
+
"budget": 4000,
|
|
118
|
+
"focus": "",
|
|
119
|
+
"truncated": false,
|
|
120
|
+
"protected": ["private/codex/prefs/2026-08-25-0001.md"],
|
|
121
|
+
"sections": {
|
|
122
|
+
"summary": ["<file>"],
|
|
123
|
+
"focus": ["<file>"],
|
|
124
|
+
"corridor": ["<file>"],
|
|
125
|
+
"high_value": ["<file>"]
|
|
126
|
+
},
|
|
127
|
+
"candidates": [
|
|
128
|
+
{
|
|
129
|
+
"file": "facts/2026/09/2026-09-29-0001.md",
|
|
130
|
+
"type": "FACT",
|
|
131
|
+
"subject": "示例主题",
|
|
132
|
+
"statement": "示例内容(最多 2000 字)",
|
|
133
|
+
"created": "2026-09-29",
|
|
134
|
+
"updated": "2026-09-29",
|
|
135
|
+
"chars": 128,
|
|
136
|
+
"baseline_section": "corridor"
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
- `sections` 是引擎基线的分段落位,供 provider 参考,不构成约束。
|
|
144
|
+
- `protected` 列出引擎硬保护、不可驱逐的文件(如自我接入档案)。
|
|
145
|
+
|
|
146
|
+
### 5.3 响应
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{ "ok": true, "capability": "context.paging", "data": {
|
|
150
|
+
"drop": ["<file>"],
|
|
151
|
+
"order": ["<file>", "<file>"],
|
|
152
|
+
"complete": true
|
|
153
|
+
} }
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
- `drop`:明确分页出去的条目,任意子集,**始终应用**;候选集外文件只记 note,不进入输出。
|
|
157
|
+
- `order`:期望的进入顺序,**仅在 `complete: true` 且本次未 `truncated` 时应用**;未列入的候选按引擎基线顺序补尾,不丢内容。
|
|
158
|
+
- 候选超过 500(`truncated=true`)时只接受 `drop`,`order` 不应用并写入 note。
|
|
159
|
+
|
|
160
|
+
### 5.4 状态与回落
|
|
161
|
+
|
|
162
|
+
`context --json` 的 `paging` 块给出 `status` / `provider_id` / `applied` / `dropped` / `ordered` / `truncated` / `budget` / `used` / `note`;
|
|
163
|
+
`context --explain` 的 trace 里追加一行 `[paging] ...`。未配置 / 未声明该 capability 时为 `not_installed`;未传 `--budget` 时为 `not_requested`;授权失败 / 超时 / 非法输出 / 异常均回落普通上下文,不影响既有记忆。
|
|
164
|
+
|
|
165
|
+
## 6. 审计与边界
|
|
95
166
|
|
|
96
167
|
- 每次实际调用写一行 `<YOTTA_PROVIDER_HOME>/provider-audit.jsonl`:`ts` / `capability` / `provider_id` / `status` / `duration_ms` / `bytes_out`。
|
|
97
168
|
- 审计**不记录**记忆正文、查询原文或任何 payload 内容。
|