@yottameta/yotta-memory 0.13.1 → 0.13.2

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/USER_GUIDE.md CHANGED
@@ -129,18 +129,19 @@ statement: 本周完成发布
129
129
 
130
130
  **用户查看平台(看所有 AI 的记忆)**
131
131
 
132
- `yotta-memory view` → 浏览器打开 http://127.0.0.1:8788 → 输入主口令解锁 → 浏览 / 搜索 / 导出全部记忆(含各 AI 私密);也可在此**授权 / 吊销**某 AI 读取其私密、**重设口令**、**查看恢复钥匙**。口令只在本地内存派生,不落盘、不发远端;默认仅本机,远程需 `--host` 显式开启。
132
+ `yotta-memory view` → 浏览器打开 http://127.0.0.1:8788 → 输入主口令解锁 → 浏览 / 搜索 / 导出全部记忆(含各 AI 私密);也可在此**授权 / 吊销**某 AI 读取其私密、**重设口令**、**查看恢复钥匙**。点击「授权」后会弹窗展示只显示一次的 `agent_key`,请立即单独保存;引擎同时写临时 `keys/pending/<id>.key`,该 AI 新会话用 `key claim` 领取到自己的宿主目录,领取成功后 pending 删除。已绑定的 agent 需先「吊销」再授权,旧 key 随即校验失败。口令只在本地内存派生,不落盘、不发远端;默认仅本机,远程需 `--host` 显式开启。
133
133
 
134
134
  **AI 接入加密库**
135
135
 
136
- 1. 该 AI 声明身份(`iam` / MCP 配置 `YOTTA_AGENT_ID`)。
137
- 2. 用户在平台对其点一次「授权」→ 平台把该 AI 的 owner key 写入授权缓存(`keys/cache/<id>.key`,600 权限)。
138
- 3. 之后该 AI 正常 `remember / recall / profile / context`,读写自动加解密;未授权时写私密会提示「需在用户平台授权」,公共 FACT 不受影响。
136
+ 1. 该 AI 登记身份(`iam` / MCP 配置 `YOTTA_AGENT_ID`)。
137
+ 2. 由用户执行一次 `yotta-memory view` → 在平台点「授权」→ 生成只展示一次的 `agent_key`,写入 `keys/bindings/<id>.key.agent` 和临时 `keys/pending/<id>.key`;不再写明文 owner key cache。高级用户也可自行执行 `yotta-memory key bind <id>`;AI 只负责提醒,不代执行。
138
+ 3. 该 AI 新会话执行 `yotta-memory key status <id> --to <AI_HOME>`;有 pending 就执行 `yotta-memory key claim <id> --to <AI_HOME>`,落到 `<AI_HOME>/.yotta-memory-agent-key`。之后 MCP 配置注入 `YOTTA_MEMORY_AGENT_KEY`,CLI 用 `--agent-key-file <AI_HOME>/.yotta-memory-agent-key`。
139
+ 4. 之后该 AI 正常 `remember / recall / profile / context`,读写自动加解密;未绑定 / key 缺失时会出现 `[YTM_MIGRATION_REQUIRED]` 迁移提示,公共 FACT 不受影响。
139
140
 
140
141
  **口令管理**
141
142
 
142
143
  - 重设口令:`yotta-memory reset-password`(输入当前口令),或忘口令时 `--recovery-key <恢复钥匙>`。
143
- - 吊销某 AI:`yotta-memory key revoke <id>`(立即失效)。
144
+ - 吊销某 AI:`yotta-memory key revoke <id>`(立即失效,并清理 pending;旧 key 后续读取会校验失败)。
144
145
  - 注意:**口令即主密钥**,忘口令且丢失恢复钥匙 = 密文私密不可恢复(公共 FACT 仍在)。
145
146
 
146
147
  ## 3.6 自我学习 / 自我进化 / 自我提升(v0.8.0)
@@ -287,7 +288,7 @@ yotta-memory token new --agent 我的智能体ID
287
288
 
288
289
  - 引擎地址:`http://<本机IP>:8787/mcp`
289
290
  - 该智能体的 token:`ytm_...`
290
- - 智能体 ID(对应 X-Agent-Id 请求头)
291
+ - 智能体 ID(对应 X-Agent-Id 请求头)与该智能体的 agent_key(对应 X-Agent-Key 请求头)
291
292
 
292
293
  查本机 IP:Linux 运行 `hostname -I`(或 `ip a`);Windows 运行 `ipconfig` 找「IPv4 地址」。防火墙:Linux 若启用了 ufw,执行 `sudo ufw allow 8787/tcp`;Windows 首次监听时允许放行。否则局域网其它主机连不进来。
293
294
 
@@ -311,7 +312,14 @@ yotta-memory remember FACT 主题 内容 # 智能体落盘
311
312
 
312
313
  智能体装上技能后(`SKILL.md`)会自动学会这套工作流,无需任何 MCP 配置。
313
314
 
314
- **方式二:stdio MCP(零常驻进程,智能体按需拉起 CLI)。** 在智能体 MCP 配置里加:
315
+ **方式二:stdio MCP(零常驻进程,智能体按需拉起 CLI)。** 先让 AI 领取 agent_key:
316
+
317
+ ```bash
318
+ yotta-memory key status <本智能体ID> --to <AI_HOME>
319
+ yotta-memory key claim <本智能体ID> --to <AI_HOME>
320
+ ```
321
+
322
+ 领取成功后宿主目录出现 `<AI_HOME>/.yotta-memory-agent-key`;再由 MCP 宿主读取该文件并注入环境变量。然后在智能体 MCP 配置里加:
315
323
 
316
324
  ```json
317
325
  {
@@ -319,13 +327,17 @@ yotta-memory remember FACT 主题 内容 # 智能体落盘
319
327
  "yotta-memory": {
320
328
  "command": "yotta-memory",
321
329
  "args": ["serve", "--stdio"],
322
- "env": { "YOTTA_AGENT_ID": "<该智能体唯一ID>" }
330
+ "env": {
331
+ "YOTTA_AGENT_ID": "<该智能体唯一ID>",
332
+ "YOTTA_MEMORY_AGENT_KEY": "<AI_HOME>/.yotta-memory-agent-key 的内容",
333
+ "YOTTA_MEMORY_TRUST_ENV_AGENT": "1"
334
+ }
323
335
  }
324
336
  }
325
337
  }
326
338
  ```
327
339
 
328
- 本机接入不需要 token,也不需要启动 HTTP 服务;但**必须在配置里声明唯一的 `YOTTA_AGENT_ID`**(见下),否则写私密记忆会被拒。
340
+ 本机接入不需要网络 token,也不需要启动 HTTP 服务;但**必须同时声明唯一的 `YOTTA_AGENT_ID` 与 `YOTTA_MEMORY_AGENT_KEY`,并设置 `YOTTA_MEMORY_TRUST_ENV_AGENT=1`**,否则私密读写会被拒。
329
341
 
330
342
  **本机智能体装好技能后如何获取记忆存放位置?** 按优先级:`YOTTA_MEMORY_HOME` 环境变量 > `config set memory_home` 持久化的 `~/.yottamemory/config.json` > 默认 `~/.yottamemory`。AI 开工执行 `yotta-memory config get` 查看当前生效位置;记忆库移动后执行一次 `config set memory_home <新目录>` 即可。
331
343
 
@@ -333,13 +345,13 @@ yotta-memory remember FACT 主题 内容 # 智能体落盘
333
345
 
334
346
  1. 开工先 `yotta-memory whoami` 确认「我是谁」。
335
347
  2. 未登记 → 向用户确认一个**全局唯一** ID(建议 `<主机名>-<角色>`,别用 `dashu` / `codex` 这类易撞名),执行 `yotta-memory iam <id>`:引擎**强制唯一性**(被其它主机 / 来源占用会拒绝),并自动落一条「自我接入档案」PREF(owner=自己)。
336
- 3. 本机多个 AI 智能体共用引擎时,**每个都要在它自己的 MCP 配置里声明唯一 `YOTTA_AGENT_ID`**(CLI 直连则每次带 `--agent <id>`),各自 `whoami` 各回各的、互不撞。
348
+ 3. 本机多个 AI 智能体共用引擎时,**每个都要在它自己的 MCP 配置里声明唯一 `YOTTA_AGENT_ID` + 自己的 `YOTTA_MEMORY_AGENT_KEY`**;CLI 直连每次带 `--agent <id> --agent-key <key>` 或 `--agent-key-file <文件>`。owner ID 单独存在时不构成认证。
337
349
  4. **禁止**从记忆里读到别人的 ID 就当自己的(比如看到「Kali 智能体 ID 为 dashu」就把自己当 dashu);不确定先 `whoami` 再问用户,**禁止猜**。
338
350
  5. **不设则 owner 为空**:写私密记忆会被引擎拒绝(公共 FACT 不受影响),避免私密隔离退化。
339
351
 
340
352
  ### 5.2 局域网其它主机的 AI 智能体
341
353
 
342
- **第 1 步:向记忆引擎主机获取**:引擎 IP、端口(默认 8787)、本智能体的 token、智能体 ID。
354
+ **第 1 步:向记忆引擎主机获取**:引擎 IP、端口(默认 8787)、本智能体的 token 与智能体 ID。若还没有 agent_key,由用户在引擎主机执行 `yotta-memory view` 授权;同机 / 共享文件系统时 AI 用 `key status` / `key claim` 领取到 `<AI_HOME>/.yotta-memory-agent-key`,不共享文件系统时由用户通过密码管理器或安全文件传输放到目标宿主目录。
343
355
 
344
356
  **第 2 步:配置 MCP**(可以让 AI 按 `SKILL.md` 引导自动完成;也可以手动在你的智能体 MCP 配置里加这段):
345
357
 
@@ -350,7 +362,8 @@ yotta-memory remember FACT 主题 内容 # 智能体落盘
350
362
  "url": "http://<引擎主机IP>:8787/mcp",
351
363
  "headers": {
352
364
  "Authorization": "Bearer <TOKEN>",
353
- "X-Agent-Id": "<本智能体ID>"
365
+ "X-Agent-Id": "<本智能体ID>",
366
+ "X-Agent-Key": "<本智能体宿主 key 文件中的 agent_key>"
354
367
  }
355
368
  }
356
369
  }
@@ -378,7 +391,7 @@ yotta-memory remember FACT 主题 内容 # 智能体落盘
378
391
  | `yotta-memory reindex` | 重建索引 |
379
392
  | `yotta-memory export [--out 文件.json]` / `import <文件.json>` | 导出 / 导入 |
380
393
  | `yotta-memory config set <键> <值>` / `config get` | 记忆库位置与引擎参数(`memory_home` / `embedding_cmd` / `embedding_timeout` / `maintain_archived_utility` / `maintain_decay_halflife_<TYPE>` / `consolidate_*` 等)|
381
- | `yotta-memory whoami` | 查看当前智能体身份与登记状态(读 `YOTTA_AGENT_ID` / `X-Agent-Id`,不猜不默认)|
394
+ | `yotta-memory whoami --agent <id>` | 查看当前显式身份与登记状态;环境身份仅在 MCP 信任标记下有效 |
382
395
  | `yotta-memory iam <id> [--name <显示名>] [--user <用户名>] [--relationship <关系>] [--force]` | 登记本智能体唯一身份并自动落自我档案(`agents.json`,ID 必须唯一;可选扩展显示名 / 用户 / 关系)|
383
396
  | `yotta-memory token new --agent <id> [--force]` / `token list` / `token revoke --agent <id>` | 访问 token(同 ID 已被其它来源占用需 `--force` 覆盖)|
384
397
  | `yotta-memory serve [--port 8787] [--stdio] [--no-auth]` | 启动记忆引擎(--no-auth 关闭鉴权,仅限可信内网)|
@@ -427,7 +440,7 @@ yotta-memory remember FACT 主题 内容 # 智能体落盘
427
440
  **确实需要读取其它智能体的私密记忆时(三种授权方式,满足任一即可):**
428
441
 
429
442
  1. 显式授权 `grants.json`:在记忆库根目录写 `{"<你的agentID>": ["<对方agentID>"]}`;
430
- 2. identity=user:以 `--agent user` / `--owner user` / 环境变量 `YOTTA_AGENT_ID=user` 读取;
443
+ 2. identity=user:以 `--agent user` / `--owner user` 读取,调用方仍需持有匹配的 agent_key;
431
444
  3. 显式放行 `--unsafe`:用户明确同意时使用。
432
445
 
433
446
  **协作纪律**:FACT 写入公共区共享;PREF / BOUND / COMMIT 只写自己的私密区;不主动读取其它智能体的私密记忆。
@@ -0,0 +1,71 @@
1
+ <!doctype html><html lang="zh"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>元忆 · 用户查看平台</title><style>
2
+ body{font-family:system-ui,-apple-system,"Microsoft YaHei",sans-serif;max-width:1000px;margin:24px auto;padding:0 16px;color:#1f2328;background:#fafafa}
3
+ h1{font-size:22px} .card{background:#fff;border:1px solid #e2e2e2;border-radius:10px;padding:16px 18px;margin:14px 0;box-shadow:0 1px 2px rgba(0,0,0,.04)}
4
+ button{background:#2563eb;color:#fff;border:0;border-radius:6px;padding:7px 14px;cursor:pointer;margin:2px;font-size:14px}
5
+ button.danger{background:#dc2626} button.ghost{background:#e5e7eb;color:#1f2328}
6
+ input,select{padding:8px;border:1px solid #c9c9c9;border-radius:6px;margin:2px;font-size:14px;box-sizing:border-box}
7
+ table{border-collapse:collapse;width:100%;font-size:13px} td,th{border:1px solid #ececec;padding:6px 8px;text-align:left;vertical-align:top}
8
+ .owner{display:inline-flex;align-items:center;gap:6px;border:1px solid #ddd;border-radius:8px;padding:5px 10px;margin:4px 6px 4px 0;background:#f6f8fa}
9
+ .entry{border-bottom:1px solid #eee;padding:8px 0} .meta{color:#8a8a8a;font-size:12px}
10
+ .err{color:#dc2626;margin-top:8px} .ok{color:#16a34a;margin-top:8px}
11
+ #app{display:none} code{background:#f0f0f0;padding:1px 5px;border-radius:4px;font-size:12px}
12
+ </style></head><body>
13
+ <h1>元忆 · 用户查看平台 <span id="ver" style="font-size:14px;color:#888"></span></h1>
14
+ <div id="lock" class="card">
15
+ <p><b>输入主口令解锁</b>(口令只在本地内存派生,不落盘、不发送远端)。忘口令可在 CLI 用恢复钥匙重设:<code>yotta-memory reset-password --recovery-key &lt;钥匙&gt;</code></p>
16
+ <input type="password" id="pw" placeholder="主口令" style="width:260px">
17
+ <button onclick="unlock()">解锁</button>
18
+ <div class="err" id="lockerr"></div>
19
+ </div>
20
+ <div id="app">
21
+ <div class="card">
22
+ <b>AI 列表</b>(✅=已授权可读自己私密,🔒=未授权)
23
+ <div class="meta" style="margin-top:6px">「授权」由你(用户)操作:确认后生成只显示一次的 agent_key,请立即单独保存;服务端同时写临时待领取文件 <code>keys/pending/&lt;agent_id&gt;.key</code>,供该 AI 新会话领取,领取成功后自动删除。</div>
24
+ <div id="owners" style="margin-top:8px"></div>
25
+ </div>
26
+ <div class="card">
27
+ <b>记忆</b>
28
+ <input id="q" placeholder="搜索关键词" style="width:220px" onkeydown="if(event.key==='Enter'){off=0;load()}">
29
+ <button onclick="off=0;load()">搜索</button>
30
+ <button class="ghost" onclick="doExport()">导出 JSON</button>
31
+ <button class="ghost" onclick="showRk()">显示恢复钥匙</button>
32
+ <span id="rkout" style="font-size:12px;color:#888;margin-left:8px"></span>
33
+ <div id="meta" style="margin-top:10px;font-size:12px;color:#666"></div>
34
+ <div id="entries" style="margin-top:6px"></div>
35
+ <div id="pager" style="margin-top:10px">
36
+ <button class="ghost" id="prevb" onclick="prevPage()">上一页</button>
37
+ <span id="pageinfo" style="font-size:12px;color:#888;margin:0 8px"></span>
38
+ <button class="ghost" id="nextb" onclick="nextPage()">下一页</button>
39
+ </div>
40
+ </div>
41
+ <div class="card">
42
+ <b>重设口令</b><br>
43
+ <input type="password" id="cur" placeholder="当前口令">
44
+ <input type="password" id="np1" placeholder="新口令">
45
+ <input type="password" id="np2" placeholder="确认新口令">
46
+ <button onclick="resetPw()">重设</button>
47
+ <span id="pwout"></span>
48
+ </div>
49
+ </div>
50
+ <script>
51
+ function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c];});}
52
+ async function api(p,b){try{const r=await fetch(p,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(b||{})});return await r.json();}catch(e){return{error:String(e)};}}
53
+ async function boot(){const s=await api('/api/status');document.getElementById('ver').textContent='v'+(s.version||'');if(s.unlocked){showApp();}}
54
+ function showApp(){document.getElementById('lock').style.display='none';document.getElementById('app').style.display='block';loadOwners();load();}
55
+ async function unlock(){const d=await api('/api/unlock',{password:document.getElementById('pw').value});if(d.error){document.getElementById('lockerr').textContent=d.error;return;}showApp();}
56
+ async function loadOwners(){const d=await api('/api/owners');const box=document.getElementById('owners');box.innerHTML='';if(!d.owners||!d.owners.length){box.innerHTML='(无 owner)';return;}
57
+ for(const o of d.owners){const c=document.createElement('span');c.className='owner';c.innerHTML=esc(o.owner)+(o.authorized?' ✅':' 🔒')+' <button class="ghost" data-a="'+esc(o.owner)+'">授权</button><button class="danger" data-r="'+esc(o.owner)+'">吊销</button>';box.appendChild(c);}
58
+ box.querySelectorAll('[data-a]').forEach(function(b){b.onclick=function(){var owner=b.getAttribute('data-a');if(!confirm('确认由你为用户授权 '+owner+' 读取其私密记忆?授权后将生成只显示一次的 agent_key,请立即保存;同时写入待领取文件供该 AI 新会话领取。AI 不应代为执行该授权操作。'))return;b.disabled=true;api('/api/authorize',{owner:owner}).then(function(d){b.disabled=false;if(!d||d.error){alert((d&&d.error)||'授权失败');loadOwners();return;}if(d.agentKey){showKey(d.agentKey);}loadOwners();});};});
59
+ box.querySelectorAll('[data-r]').forEach(function(b){b.onclick=function(){if(!confirm('确认吊销 '+b.getAttribute('data-r')+' 的 agent_key?吊销后该智能体立即失去私密读写能力。'))return;api('/api/revoke',{owner:b.getAttribute('data-r')}).then(function(){loadOwners();});};});
60
+ function showKey(k){var ov=document.createElement('div');ov.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index:99';var box=document.createElement('div');box.className='card';box.style.cssText='max-width:640px;word-break:break-all';var t=document.createElement('div');t.innerHTML='<b>agent_key(只显示一次)</b>';var hint=document.createElement('div');hint.className='meta';hint.textContent='请用户立即单独保存。AI 新会话执行 yotta-memory key claim <agent_id> --to <AI_HOME> 领取,写入 <AI_HOME>/.yotta-memory-agent-key 后删除待领取文件。若 key 丢失,可吊销后重新授权;旧 key 会立即校验失败。';var ta=document.createElement('textarea');ta.readOnly=true;ta.value=k;ta.style.cssText='width:100%;height:72px;margin-top:8px;font-family:monospace;font-size:12px';var close=document.createElement('button');close.textContent='我已保存,关闭';close.onclick=function(){ov.remove();};box.appendChild(t);box.appendChild(hint);box.appendChild(ta);box.appendChild(close);ov.appendChild(box);document.body.appendChild(ov);ta.focus();ta.select();}
61
+ }
62
+ let off=0,PS=50;
63
+ async function load(){const d=await api('/api/entries',{query:document.getElementById('q').value,offset:off,limit:PS});const meta=document.getElementById('meta');const pg=document.getElementById('pageinfo');if(meta)meta.textContent='共 '+d.count+' 条';const lim=d.limit||PS;const totalPg=Math.max(1,Math.ceil(d.count/lim));const curPg=Math.floor((d.offset||0)/lim)+1;if(pg)pg.textContent='第 '+curPg+' / '+totalPg+' 页';const box=document.getElementById('entries');box.innerHTML='';if(d.entries)for(const e of d.entries){const div=document.createElement('div');div.className='entry';div.innerHTML='<b>['+esc(e.type)+'] '+esc(e.subject)+'</b><div>'+esc(e.statement)+'</div><div class="meta">'+esc(e.file)+' · owner='+esc(e.owner||'-')+' · '+esc(e.updated||e.created||'')+'</div>';box.appendChild(div);}const pb=document.getElementById('prevb'),nb=document.getElementById('nextb');if(pb)pb.disabled=(d.offset||0)<=0;if(nb)nb.disabled=!d.hasMore;}
64
+ function prevPage(){if(off>=PS){off-=PS;load();}}
65
+ function nextPage(){off+=PS;load();}
66
+ async function doExport(){const d=await api('/api/export');if(d.error){alert(d.error);return;}const blob=new Blob([JSON.stringify(d,null,2)],{type:'application/json'});const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='yottamemory-view-export.json';a.click();}
67
+ async function showRk(){const d=await api('/api/recovery-key');document.getElementById('rkout').textContent=d.recoveryKey?('恢复钥匙: '+d.recoveryKey):(d.error||'');}
68
+ async function resetPw(){const np1=document.getElementById('np1').value,np2=document.getElementById('np2').value;if(np1!==np2){document.getElementById('pwout').innerHTML='<span class="err">两次新口令不一致</span>';return;}
69
+ const d=await api('/api/reset-password',{currentPassword:document.getElementById('cur').value,newPassword:np1});document.getElementById('pwout').innerHTML=d.error?('<span class="err">'+esc(d.error)+'</span>'):('<span class="ok">'+esc(d.text||'ok')+'</span>');}
70
+ boot();
71
+ </script></body></html>