openclaw-plugin-people-memory 0.0.0-stage → 0.0.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/DESIGN.md ADDED
@@ -0,0 +1,182 @@
1
+ # People Memory 设计
2
+
3
+ ## 目标
4
+
5
+ 把「识人规则写在 28KB 提示词里」变成「运行时自动完成身份解析、上下文注入和受控写入」。
6
+
7
+ 三个约束:
8
+
9
+ 1. 数据边界是 agent 可见工作区,不是 agent id。
10
+ 2. 频道无关,BNCR 只是其中一个来源。
11
+ 3. SQLite 是主存储,JSON 只用于迁移和导出。
12
+
13
+ ## 完整模型(三层键)
14
+
15
+ ```mermaid
16
+ flowchart LR
17
+ C[频道 channel<br/>bncr / telegram / ...] --> P[平台 platform<br/>bncr/后端 或 频道名]
18
+ P --> A[账号 account<br/>platform:sender_id]
19
+ A -- 手动绑定 --> B[人物 person<br/>跨账号汇总]
20
+ C --> G[群 group<br/>platform:chat_id]
21
+ G --> H[群大事记 / 群观察]
22
+ ```
23
+
24
+ - 普通频道(telegram 等)没有平台层:频道名就是键,账号键 `telegram:<sender>`,群键 `telegram:<chat_id>`。
25
+ - bncr 是按平台方式接入的频道:一条接入里带多个后端(GewePlus、GolemPlus、qq、qqBot、tgBot、larkalk、wxBot)。合法账号键是 `bncr/<后端>:<sender>`;裸 `bncr:<sender>` 缺后端,是非法态,插件不新建裸行,存量裸行由周期任务清理。
26
+ - 同族只有配置声明的配对:默认 `senderFamilies=["bncr/GewePlus","bncr/GolemPlus"]`。它们是同一微信平台的两条接入,senderid 空间一致(同一 wxid 在两条接入里相同)。其它后端(qq、tgBot 等)即使同类,senderid 空间不同,无法同族归位。系统没有别名(alias)概念:GewePlus 与 GolemPlus 两条接入分开记录,不做账号互并。
27
+
28
+ ## 两个维度与同族自动归并
29
+
30
+ - 账号维度:每个账号行独立记印象/特征。不同后端的同一 chat 群行也是独立记录,不算重复。
31
+ - 人物维度:`persons` 跨账号汇总。自动归并只发生在同族:
32
+ - 同 sender 的 `bncr/GewePlus` 与 `bncr/GolemPlus` 两行 → 自动绑到同一个人物,人物吸收两侧印象/特征;
33
+ - 两侧都未绑 → 自动创建人物(命名见下)并双侧绑定迁移;
34
+ - 一侧已绑 → 新接入行出现时绑到同一个人物(仅写入路径触发;周期扫描不自动补绑,避免覆盖 owner 的解绑决定);
35
+ - 两侧绑到不同人物 → 不动数据,health 报 `person_binding_conflict`,周期任务出 merge_review 由 owner 判断;
36
+ - 任一侧 `current_role=bot` → 整组跳过。
37
+ - 跨平台同人(比如 QQ 号 + 微信号是同一人):不自动归并,只有 owner/agent 手动 `people_bind` 把多个平台账号收到一个人物下。
38
+
39
+ ## 群模型
40
+
41
+ 群独立于账号维度记大事:`group_events`(群规、里程碑、接口/功能变更)与 `group_observations`(长期观察)。每条群记录挂在具体后端下(`platform:chat_id`);事实约束是「只有同族的两个不同平台 bot 才会出现在同一个群里」,所以不同后端的同 chat 行是两条独立记录,不做跨后端去重。同一条群规则如果需要在两个后端的群行里都有,由 agent 分别 `people_note` 两次。
42
+
43
+ ## bot 与 agent 的身份
44
+
45
+ agent 在各平台以平台账号出现(例如 orion 同时有 bncr/GewePlus 行和 bncr/GolemPlus 行),这是天然的账号级绑定层:`current_role=bot` + 群 botNames 就是全部身份,不给 bot 建人物、不记 bot 印象。bot 账号命中群 botNames 或成员 likely_bot 分类时,repair 自动补 `current_role=bot`。
46
+
47
+ ## 命名规则
48
+
49
+ 显示优先级:`call_name`(本人钦定,自我介绍走 `people_name { call_name }`)> `confirmed_name`(自述名字)> 最近活跃行的平台昵称。人物级同名优先级。call_name 不被昵称同步覆盖;owner 解绑后系统不自动补绑已有人物,除非又出现新接入行。
50
+
51
+ ## 隔离与共享(按工作区)
52
+
53
+ 数据边界是 agent 可见工作区,不是 agent id。
54
+
55
+ - 默认数据目录 `<stateDir>/plugins/people/<workspace-key>/people.db`:`stateDir` 默认 `~/.openclaw`,可用 `OPENCLAW_STATE_DIR` 覆盖;`<workspace-key>` 由工作区真实路径编码。布局与 bncr 的 `<stateDir>/plugins/<id>` 一致。
56
+ - 同工作区(main 与 orion 共用同一个 workspaceDir,例如 `<workspace>`):同一人格、同一记忆、同一份识人库 `<stateDir>/plugins/people/<workspace-key>/people.db`,天然共享,不需要链接。
57
+ - 不同工作区(例如 public = `<workspace>/public`):独立库,严格隔离——不跨工作区读、不跨工作区写、不搬运人物记录。旧体系里「查不到时读另一个工作区库做辅助查询」的规则随 0.4.0 废除。
58
+ - 想留在工作区(例如沿用旧库)用 `dataDir: "{workspace}/people"` 覆盖;插件按数据目录 realpath 识别同一个库,符号链接等价于共用一个数据目录。
59
+ - 周期任务只处理本工作区的会话(home 任务只收 agent:main / agent:orion,public 任务只收 agent:public)。
60
+
61
+ ## 分层
62
+
63
+ ```mermaid
64
+ flowchart TD
65
+ A[入站消息 / agent turn] --> B[before_prompt_build]
66
+ B --> C[PeopleRuntime]
67
+ C --> D[resolveStorePaths]
68
+ D --> E[stateDir/plugins/people/workspace-key/people.db]
69
+ B --> F[PeopleService.capture]
70
+ F --> E
71
+ B --> G[PeopleService.contextCard]
72
+ G --> E
73
+ B --> H[prependContext]
74
+ I[agent tool call] --> J[people_* tools]
75
+ J --> C
76
+ K[CLI] --> C
77
+ L[旧 JSON] --> M[legacy importer once]
78
+ M --> E
79
+ ```
80
+
81
+ ```
82
+ index.ts 只做装配
83
+ src/core/ 纯数据能力,不依赖 OpenClaw
84
+ paths.ts 工作区 → 数据目录
85
+ platform.ts 频道 → 平台标签
86
+ schema.ts SQLite schema
87
+ db.ts 连接、事务、查询、写入
88
+ legacy.ts 旧 JSON 一次性导入
89
+ service.ts 捕获、上下文卡片、记忆、绑定、同族归并、校验
90
+ src/plugin/ OpenClaw 适配
91
+ config.ts 配置归一化
92
+ runtime.ts 按 realpath 缓存数据库连接
93
+ identity.ts 钩子上下文字段解析
94
+ hooks.ts inbound_claim / before_prompt_build / gateway_stop
95
+ tools.ts agent 工具
96
+ cli.ts openclaw people ...
97
+ ```
98
+
99
+ `src/core` 不 import OpenClaw,方便单测和独立复用。
100
+
101
+ ## 数据模型
102
+
103
+ schema v2 起,`accounts` 与 `persons` 都有 `call_name` 列:本人钦定的称呼,优先于 `confirmed_name` 和平台昵称,不被昵称同步覆盖。旧库打开时自动 `ALTER TABLE` 补列。
104
+
105
+ | 表 | 主键 | 用途 |
106
+ |---|---|---|
107
+ | `accounts` | `account_id` | 账号索引,`platform:sender_id` |
108
+ | `persons` | `person_id` | 跨账号人物 |
109
+ | `person_accounts` | `account_id` | 绑定桥接 |
110
+ | `impressions` | 自增 id | 原始印象,按内容哈希去重 |
111
+ | `traits` | `(owner_type, owner_id, dimension)` | 归并后的维度 |
112
+ | `groups` | `group_id` | 群索引,`(platform_key, chat_id)` 唯一 |
113
+ | `group_members` | `(group_id, account_id)` | 群成员 |
114
+ | `group_events` | 自增 id | 群大事记,按天+摘要去重 |
115
+ | `group_observations` | 自增 id | 群长期观察 |
116
+ | `dimensions` | `key` | 特征维度配置 |
117
+ | `scan_channels` | `(workspace_key, channel_key)` | 扫描进度 |
118
+ | `scan_meta` | `workspace_key` | 全量扫描时间 |
119
+ | `meta` | `key` | schema 版本、迁移标记 |
120
+
121
+ `owner_type` 只有 `account` 和 `person`。绑定后,账号级 impressions/traits 会迁移到人物级,账号行只保留身份与时间戳。
122
+
123
+ ## 写入纪律
124
+
125
+ | 写入者 | 允许写入 |
126
+ |---|---|
127
+ | 钩子 | 账号索引、显示名、最后活跃、群标题、群成员(`inbound_claim` 只缓存身份,不存正文) |
128
+ | 同族状态机 | 同族两行同 sender:自动建人物并双绑、或新行补绑到已有人物(只动绑定与迁移,不猜内容) |
129
+ | agent 工具 | impressions、traits、群事件、绑定、删除 |
130
+ | 工作区周期任务 | 先调 `people_cycle` 获取插件维护计划,再通过 `people_remember` / `people_note` / `people_scan_mark` / `people_merge` 写 |
131
+ | owner | 绑定、修复、人物合并判断 |
132
+
133
+ 所有写操作走同一个 `PeopleDatabase`,事务用 `BEGIN IMMEDIATE`,嵌套调用自动降级为 savepoint。SQLite 开 WAL、`busy_timeout=5000`,允许多进程读写。
134
+
135
+ ## 频道抽象
136
+
137
+ 平台标签:
138
+
139
+ ```
140
+ platform = template({channel}, {account})
141
+ ```
142
+
143
+ - 默认模板 `{channel}/{account}`;没有有效 account 时退化为 `{channel}`。
144
+ - `default / main / primary / 0` 视为无账号。
145
+ - `platformOverrides` 可按频道覆盖模板。
146
+ - 多后端频道(默认 bncr,配置 `multiAccountChannels`):账号键必须带后端段;裸频道写入挂起(deferred),不建裸行、不建裸群行。
147
+ - 比较时 `bncr/tgBot` 与 `bncr:tgBot` 等价,兼容旧数据。
148
+ - 同族关系来自 `senderFamilies` 配置(平台标签两两配对),与别名无关。
149
+
150
+ 群 key 保持旧格式:`platform.replace('/', ':') + ':' + chatId`。同一 chat 在不同后端下是不同群行,按行独立维护;按 chat 号查群命中多后端行时工具报歧义,用精确群 key 指定。
151
+
152
+ ## 旧数据适配
153
+
154
+ 线上 JSON 与 SPEC 有四处偏差,导入器逐一兼容:
155
+
156
+ | 偏差 | 处理 |
157
+ |---|---|
158
+ | `groups.json` 外层包 `groups` | 自动识别平铺/包装 |
159
+ | `scan-state.channels` 是 dict | 同时支持 dict 与数组 |
160
+ | `impressions[]` 是字符串 | 字符串与对象都支持 |
161
+ | 群文件新旧双命名 | 按 `platform + chat_id` 去重合并 |
162
+
163
+ 群记忆条目支持 `summary`、`event`、`title`、`content`,并拼接 `detail/description/note`。历史裸平台行(如 `GewePlus:wxid_...`)随导入原样进库,由周期任务的裸行清理归位到带后端的行。
164
+
165
+ ## 失败策略
166
+
167
+ - 钩子异常:捕获、记警告、返回 `undefined`,不影响对话。
168
+ - 旧 JSON 导入异常:捕获、记警告,数据库保持可用;不会删除旧文件。
169
+ - 工具异常:向模型返回明确中文错误,不静默成功。
170
+ - 数据库损坏:CLI `validate` 报告;修复只处理缺失人物/账号/绑定同步、裸行清理、同族自动建人物,不猜内容。人物绑定冲突不自动修,交 owner。
171
+
172
+ ## 扩展点
173
+
174
+ - 新频道:优先靠 `platformOverrides` 和上下文字段候选;仍不匹配时在 `identity.ts` 增加候选键。
175
+ - 新同族配对:`senderFamilies` 配置里加一对平台标签即可,代码不动。
176
+ - 新维度:直接写 `dimensions` 表,或在 `schema.ts` 的 `DEFAULT_DIMENSIONS` 增加默认项。
177
+ - 新工具:在 `tools.ts` 注册,manifest 的 `contracts.tools` 同步。
178
+ - 导出/备份:`people_backup` 同时产出 SQLite 与 JSON 快照。
179
+
180
+ ## 周期闭环
181
+
182
+ 定时器只负责叫醒 agent。备份、校验、安全修复、同族归并、裸行清理、健康检查和维护待办由 `people_cycle` 统一执行。agent 负责语义归并和基于证据的人物合并,不能绕过 `people_*` 直接改库。同族冲突(绑到不同人物)以 `merge_review` 形式进待办,owner 判断后才动数据。
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xmoxmo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,215 @@
1
- # Temporary Holding Version
1
+ # People Memory
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ OpenClaw 的识人插件。按 **agent 可见工作区** 隔离数据:同一个 `workspaceDir` 下的 agent 共用一套识人库,不同工作区互不可见。存储使用 SQLite,不再把 JSON 当主存储。
4
+
5
+ ## 边界
6
+
7
+ ```
8
+ <stateDir>/plugins/people/<workspace-key>/people.db ← 每个工作区一份
9
+ ```
10
+
11
+ - 默认数据目录是 `<stateDir>/plugins/people/<workspace-key>`。`stateDir` 默认 `~/.openclaw`,可用 `OPENCLAW_STATE_DIR` 覆盖;布局与 bncr 的 `<stateDir>/plugins/<id>` 一致。`<workspace-key>` 由工作区真实路径编码而来。
12
+ - 同一 `workspaceDir` 下的 agent 共用一份库;不同工作区互不可见。
13
+ - 想把数据放到别处(例如留在工作区),用 `dataDir` 或 `workspaceDataDirs` 覆盖;插件按 `realpath` 识别同一个库。
14
+
15
+ 例如留在工作区:
16
+
17
+ ```json
18
+ {
19
+ "plugins": {
20
+ "entries": {
21
+ "people": {
22
+ "config": {
23
+ "dataDir": "{workspace}/people"
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ ## 适用范围
32
+
33
+ - 插件读取的是**通用入站元信息**:发送者 id、显示昵称(`name / displayName / display_name / nickname / username`)、群标题(`title / subject / group_subject`)、是否群聊(`isGroup / type=group`)。任何能提供这些信息的频道都能用,没有平台绑定。
34
+ - 平台标签由 `channel + accountId` 按模板生成:普通频道就是频道名(如 `telegram`);BNCR 这种一条接入带多个后端的频道,accountId 是后端(`bncr/GewePlus`、`bncr/tgBot` 等)。这是标签上的差异,不影响可用性。
35
+ - BNCR 的两点只跟它的接入结构有关,不代表别的频道缺能力:
36
+ - 同族自动归并只针对 GewePlus 与 GolemPlus——这两个后端 senderid 空间一致,同一 sender 才需要归到同一个人物。别的频道没有"同一人两条接入同 id"的情况,本来就不需要归并。
37
+ - bot 识别靠群 botNames + `current_role=bot`,是通用逻辑,有群成员信息就能用。
38
+ - 频道不在 `channelContext` 里给昵称/群标题时,插件用 `inbound_claim` 从入站事件被动补录,仍能建档。
39
+
40
+ ## 安装
41
+
42
+ ```bash
43
+ cd <plugin-dir>
44
+ npm run build
45
+
46
+ openclaw plugins install --link <plugin-dir> --force --no-enable
47
+ ```
48
+
49
+ 在 `openclaw.json` 中授权对话钩子并启用:
50
+
51
+ ```json
52
+ {
53
+ "plugins": {
54
+ "entries": {
55
+ "people": {
56
+ "enabled": true,
57
+ "hooks": {
58
+ "allowConversationAccess": true
59
+ },
60
+ "config": {
61
+ "injectContext": true,
62
+ "injectGroupMemory": true,
63
+ "disabledAgents": []
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ `allowConversationAccess` 是 OpenClaw 对 `before_prompt_build` 这类对话钩子的授权项,必须放在 `hooks` 下,不能放在插件自己的 `config` 里。
72
+
73
+ **沙箱 agent 额外一步**:沙箱只能调用 `tools.sandbox.tools.alsoAllow` 里列出的工具。没加 `people_*` 时,模型会报 `Unknown tool id: people_status`。给每个沙箱 agent 加上:
74
+
75
+ ```json
76
+ {
77
+ "agents": {
78
+ "entries": {
79
+ "<agentId>": {
80
+ "tools": {
81
+ "sandbox": {
82
+ "tools": {
83
+ "alsoAllow": [
84
+ "people_status", "people_lookup", "people_remember", "people_name", "people_note",
85
+ "people_bind", "people_forget", "people_validate", "people_scan_mark", "people_bootstrap",
86
+ "people_backup", "people_health", "people_cycle", "people_search", "people_dimension",
87
+ "people_merge", "people_merge_account", "people_unbind"
88
+ ]
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ 非沙箱 agent 不需要这一步。public 已在 `openclaw.json` 中配置。
99
+
100
+ ## 工具
101
+
102
+ | 工具 | 作用 | 权限 |
103
+ |---|---|---|
104
+ | `people_status` | 当前工作区计数、扫描时间、数据库路径 | 所有 agent |
105
+ | `people_lookup` | 查自己、指定账号/人物或群记忆;`account`、`person`、`query` 都可引用,`full: true` 返回完整 traits 和印象列表 | 查他人仅 owner |
106
+ | `people_remember` | 记一条事实/印象,可写入维度(key 或中文标签均可);`person` 参数用于人物级画像 | 自己;owner 或内部周期任务可指定他人/人物 |
107
+ | `people_name` | 设置 call_name / confirmed_name / 人物名 | 自己;owner 可指定他人 |
108
+ | `people_note` | 追加群记忆 | 当前群成员 |
109
+ | `people_bind` | 账号绑定到人物,合并画像 | owner |
110
+ | `people_forget` | 删除匹配的印象/特征;`scope` 可限定只删 impressions 或 traits | 自己;owner 或内部周期任务可指定他人/人物 |
111
+ | `people_validate` | 一致性校验,可选修复 | 读取所有;修复仅 owner |
112
+ | `people_scan_mark` | 记录扫描进度 | 扫描任务 |
113
+ | `people_bootstrap` | 建库、导入旧 JSON | 所有 agent(其他工作区仅 owner) |
114
+ | `people_backup` | 生成 SQLite + JSON 备份并轮换旧备份 | 当前工作区 |
115
+ | `people_health` | 检查重复人物、过密画像、孤儿记录、缺备份 | 当前工作区 |
116
+ | `people_cycle` | 插件化维护周期:按需备份、修复、健康检查并生成待办计划 | owner / 内部周期任务 |
117
+ | `people_search` | 跨账号、人物、印象、群事件搜索 | owner / 内部维护任务 |
118
+ | `people_dimension` | 查看、添加、启停画像维度 | 查看所有;修改 owner / 内部维护任务 |
119
+ | `people_merge` | 合并重复人物 | owner / 内部维护任务,需 confirm |
120
+ | `people_merge_account` | 合并同一人物的双份账号,迁移印象、特征、群成员和绑定 | owner / 内部维护任务,需 confirm |
121
+ | `people_unbind` | 解绑账号并默认复制人物画像回账号 | owner / 内部维护任务,需 confirm |
122
+
123
+ 账号引用可写规范 id(`bncr/GewePlus:wxid_x`),也可写 `bncr:GewePlus:wxid_x`。群引用支持完整 group_id、纯数字 chat_id,以及 `GewePlus:123456789` 这类带平台前缀的写法。
124
+
125
+ ## 自动行为
126
+
127
+ 启用后,`inbound_claim` 会先被动记录发送者昵称、群标题和群聊标记(不存消息正文),解决 BNCR 这类不提供 `channelContext` 的频道拿不到昵称的问题。随后 `before_prompt_build` 在每轮对话前:
128
+
129
+ 1. 从频道上下文解析平台、发送者、会话。
130
+ 2. 首次见到账号时建档,更新 `display_name` 与 `last_interaction`。
131
+ 3. 识别群会话时更新群标题与成员。
132
+ 4. 注入一张 `<people_context>` 小卡片,包含当前对象画像和最近群记忆。
133
+
134
+ 同一账号 30 秒内只写一次,避免重试和多轮调用造成写放大。任何异常都不会中断对话,只会记一条警告日志。
135
+
136
+ 当某个画像的零散印象超过 12 条时,注入卡片会附带一条内部维护待办,提示 agent 在相关对话里归并画像。该提示只进入模型上下文,不会展示给用户。
137
+
138
+ ## 频道兼容
139
+
140
+ 平台标签由 `channel + accountId` 生成,不依赖 BNCR:
141
+
142
+ | 上下文 | 平台标签 |
143
+ |---|---|
144
+ | `provider=bncr, accountId=tgBot` | `bncr/tgBot` |
145
+ | `channel=telegram` | `telegram` |
146
+ | `channel=discord, accountId=work` | `discord/work` |
147
+
148
+ 发送者名、群标题、是否群聊都按候选字段读取,兼容 `name / displayName / display_name / nickname / username`、`title / subject / group_subject`、`isGroup / type=group` 等常见形态。特殊频道可用 `platformOverrides` 覆盖平台模板。
149
+
150
+ ## 从 JSON 迁移
151
+
152
+ 首次打开一个工作区的库时,如果目录里存在旧 JSON,会自动导入一次:
153
+
154
+ - `accounts.json`(平铺或 `accounts` 包装)
155
+ - `accounts/**/*.json`
156
+ - `persons.json`、`persons/**/*.json`
157
+ - `person_accounts.json`
158
+ - `groups.json`(平铺或 `groups` 包装)
159
+ - `groups/**/*.json`(新旧两套命名会按 `platform + chat_id` 去重)
160
+ - `scan-state.json`(`channels` 数组或 dict)
161
+ - `trait-dimensions.json`
162
+
163
+ 账号与人物上的 `call_name`(本人钦定称呼)会一并导入,优先级高于 `confirmed_name` 和平台昵称。
164
+
165
+ 导入后:
166
+
167
+ - 旧 JSON 保留为只读归档,不删除、不再写入。
168
+ - `people.db` 成为唯一主存储。
169
+ - 导入标记写在 `meta.legacy_imported_at`。
170
+
171
+ 迁移期间必须先停掉旧的 JSON 写入链路(旧扫描任务、手工脚本),否则会出现两套数据分叉。
172
+
173
+ 已经导入过、但之后补了字段或要重跑时,用强制导入(按去重键合并,不会重复写印象):
174
+
175
+ ```bash
176
+ openclaw people import --workspace <workspace> --force
177
+ ```
178
+
179
+ ## CLI
180
+
181
+ ```bash
182
+ openclaw people status --workspace <workspace>
183
+ openclaw people validate --workspace <workspace> --json
184
+ openclaw people bootstrap --workspace <workspace>/public
185
+ openclaw people import --workspace <workspace>/public --force
186
+ openclaw people validate --workspace <workspace> --fix
187
+ ```
188
+
189
+ ## 开发
190
+
191
+ ```bash
192
+ npm run build
193
+ npm test
194
+ ```
195
+
196
+ 测试覆盖:旧 JSON 导入与称呼字段、群文件去重、捕获/记忆/绑定/遗忘、符号链接共享同一库、跨频道平台解析。
197
+
198
+ ## 自我维护闭环
199
+
200
+ 每个工作区自己拥有数据库、备份和维护任务。推荐任务分工:
201
+
202
+ | 任务 | 频率 | 作用 |
203
+ |---|---|---|
204
+ | 工作区周期任务 | 每 6 小时 | 调用 `people_cycle` 执行插件维护,再补捞该工作区的会话事实 |
205
+
206
+ main 和 orion 共用一套库,由一个 home 周期任务处理 `agent:main` 和 `agent:orion` 的会话;public 使用自己的周期任务。备份间隔、校验、健康检查和待办清理由插件决定,定时器只负责叫醒 agent。
207
+
208
+ 维护任务的完整执行规则见 `SELF-MANAGEMENT.md`;运行流程、手动调用和验收命令见 `RUNBOOK.md`。
209
+
210
+ ## 隐私
211
+
212
+ - 数据库按工作区隔离(默认 `<stateDir>/plugins/people/<workspace-key>/people.db`),不同工作区互不可见。
213
+ - `people_lookup` 对非 owner 只允许查自己。
214
+ - 注入内容只给当前 agent,不进入对外回复;提示词明确要求不要原样展示。
215
+ - 如需彻底删除某人数据,用 `people_forget` 或直接删除该工作区的 `people.db`(后者会丢全部记忆,先备份)。
Binary file
@@ -0,0 +1,52 @@
1
+ # 定时任务(周期维护)
2
+
3
+ people 插件的核心维护逻辑都在插件里(`people_cycle` 负责按需备份、自动修复、校验、生成待办)。定时任务只做一件事:**按周期叫醒 agent,让它调用插件**。控制面在插件,定时器只是触发器。
4
+
5
+ ## 组成
6
+
7
+ | 文件 | 作用 |
8
+ |---|---|
9
+ | `cycle.template.md` | 通用周期维护提示词模板,含 `{{WORKSPACE}}` / `{{AGENT_IDS}}` / `{{AGENT_NAMES}}` 占位符 |
10
+ | `cycle-home.md` / `cycle-public.md` | 本机实例已用的两份具体任务(示例,含本机路径,不随公开包发布) |
11
+ | `scan-orion.md` / `scan-public.md` | 早期独立扫描任务,已停用,仅作历史参考 |
12
+
13
+ ## 注册一个周期任务
14
+
15
+ 用内置 `automations` 工具(旧名 `cron` 已不存在)。把 `cycle.template.md` 填好占位符后作为 `payload.message`:
16
+
17
+ ```text
18
+ automations(action="add", job: {
19
+ name: "people-cycle-<workspace>",
20
+ schedule: { kind: "cron", expr: "0 */6 * * *", tz: "Asia/Shanghai" },
21
+ sessionTarget: "isolated",
22
+ wakeMode: "now",
23
+ payload: {
24
+ kind: "agentTurn",
25
+ timeoutSeconds: 900,
26
+ message: "<cycle.template.md 填好占位符后的全文>"
27
+ },
28
+ delivery: { mode: "none" },
29
+ enabled: true
30
+ })
31
+ ```
32
+
33
+ 要点:
34
+
35
+ - **周期任务不设 `deleteAfterRun`**,否则跑一次就删。
36
+ - `delivery.mode = "none"`:维护任务只做内部整理,不向群播报。
37
+ - 一个工作区注册一个任务;同 workspaceDir 的多个 agent 由同一个任务覆盖(`{{AGENT_IDS}}` 里列全)。
38
+ - 触发频率建议 6 小时一次;真正的备份/修复节奏由 `people_cycle` 内部决定,调频率只影响叫醒频率。
39
+
40
+ ## 怎么确认在正常跑
41
+
42
+ ```text
43
+ automations(action="list") // 看 nextRunAtMs / lastRunStatus
44
+ automations(action="runs", jobId="<job-id>", limit: 5) // 看运行历史
45
+ ```
46
+
47
+ 再结合插件侧自检:`openclaw people status`、`openclaw people validate`、`openclaw people health`。
48
+
49
+ ## 为什么不做成"纯定时任务"
50
+
51
+ 如果维护逻辑写在定时任务提示词里,换一个 agent 就要复制一份、改一份,容易漏。现在维护逻辑收敛在 `people_cycle`,定时任务只负责叫醒,插件升级后所有工作区一起受益。
52
+
@@ -0,0 +1,51 @@
1
+ # People 周期维护任务模板
2
+
3
+ > 占位符:`{{WORKSPACE}}` 换成工作区绝对路径;`{{AGENT_IDS}}` 换成该工作区的 agent 会话前缀(逗号分隔,如 `agent:main, agent:orion`);`{{AGENT_NAMES}}` 换成对应显示名。注册方法见同目录 README.md。
4
+
5
+ 你是 {{AGENT_NAMES}} 工作区的识人周期执行员。工作区 `{{WORKSPACE}}`,本工作区共用一份 `{{WORKSPACE}}/people/people.db`。
6
+
7
+ 核心控制由 people 插件负责。禁止直接读写 `people/` 下的 JSON 或 `people.db`,禁止使用 write/edit/apply_patch,所有操作只通过 `people_*` 工具。
8
+
9
+ ## 第一步:插件维护周期
10
+
11
+ 调用 `people_cycle`。它会按需备份、自动修复安全项、校验并返回待处理计划。不要跳过这一步,也不要在它失败时继续改数据。
12
+
13
+ ## 第二步:补捞会话
14
+
15
+ 1. 用 `sessions_list` 获取会话,不传 relationship。
16
+ 2. 只处理 session_key 以 `{{AGENT_IDS}}` 开头的会话行;其它 agent 一律丢弃。
17
+ 3. 本轮最多处理 10 个有新活动的频道;每个频道 `sessions_history` limit<=10,只取最近消息。
18
+ 4. 群只在 `people_lookup` 能查到,或本窗口内确有你所在工作区 agent 参与时处理;存疑整群跳过。
19
+ 5. 跳过 bot、系统消息、纯指令、自动生成的上下文累积行。
20
+
21
+ ## 第三步:写入事实
22
+
23
+ 只抽取可验证事实:人物第一人称明确说出的所在地、身份、偏好、关系、近期计划;群的群规、里程碑、接口或功能变更。不推测,不给性格下判语,不记录玩笑或阴阳怪气。
24
+
25
+ - 人物事实:`people_remember { account: "platform:sender_id", content, dimension, source: "scan" }`
26
+ - 群事件:`people_note { group: "<chat_id>", summary, category }`;同一 chat 在多个后端各有群行时工具会报歧义,按提示改用精确群 key(`platform:chat_id`)
27
+ - 每个频道处理完:`people_scan_mark { channel: "platform:chat_id", full: false }`
28
+ - 全部结束:`people_scan_mark { channel: "workspace", full: true }`
29
+
30
+ 平台映射:`bncr:<后端>` → `bncr/<后端>`(GewePlus、GolemPlus、qq、qqBot、tgBot 等);其它频道用 provider 或 channel 作平台。GewePlus 与 GolemPlus 是同族两条接入,分开记录、互不替代,人物级归并由插件自动完成,不需要你合并账号。
31
+
32
+ ## 第四步:执行插件计划
33
+
34
+ 把 `people_cycle` 返回的 actions 当作唯一维护清单:
35
+
36
+ - **compact**:用 `people_lookup` 查看画像。ownerType 为 person(目标以 `p_` 开头)时,用 `people_remember { person: "p_...", dimension, mode: "replace" }` 归并到 traits,用 `people_forget { person: "p_...", scope: "impressions" }` 删除被替代的重复原文;账号级目标改用 account 参数。replace 只更新 traits,不再追加印象。目标是 1-3 条零散印象。
37
+ - **merge_review**:先用 `people_search` 和 `people_lookup` 核对。只有账号绑定、本人自述或稳定身份证据能证明是同一人时,才 `people_merge { source, target, confirm: true }`。证据不足就保留。
38
+ - **person_binding_conflict**(同族两条接入绑到不同人物):不自动改绑不自动合并,向 owner 报告并给出处置建议(保留哪侧 + `people_unbind` 另一侧);owner 处置后系统才会在出现新接入行时补绑。
39
+ - **observe**:只写进简报(如裸行缺后端等待带后端的消息、跨后端群行歧义),不改数据。裸行清理由插件自动完成。
40
+ - 不根据昵称相同就合并人物,不为了清 warning 删除事实。
41
+
42
+ ## 第五步:收尾
43
+
44
+ 1. 再调用 `people_cycle`,确认备份状态、health 和剩余 actions。
45
+ 2. 调用 `people_validate`,有 error 只报告,不自行改库。
46
+ 3. 记录边界:不把扫描流程、插件同步、cron 简报、工具报错等运维动作写成群记忆或人物事实。
47
+
48
+ 隔离边界:本库只对 `{{WORKSPACE}}` 工作区的 agent 可见。不向其它工作区的识人库读写或搬运任何记录;查不到的人不跨库补查,留待该人下次在本工作区出现时建档。
49
+
50
+ 输出简报:处理频道数、写入事实数、插件 cycle 的备份与修复、compaction/merge 结果、剩余 warning、下一轮建议。不要输出 NO_REPLY。
51
+
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
2
+ import { registerCli } from "./src/plugin/cli.js";
3
+ import { registerHooks } from "./src/plugin/hooks.js";
4
+ import { PeopleRuntime } from "./src/plugin/runtime.js";
5
+ import { registerTools } from "./src/plugin/tools.js";
6
+ export default definePluginEntry({
7
+ id: "people",
8
+ name: "People Memory",
9
+ description: "Workspace-scoped people and group memory backed by SQLite.",
10
+ register(api) {
11
+ const runtime = new PeopleRuntime(api);
12
+ registerTools(api, runtime);
13
+ registerHooks(api, runtime);
14
+ registerCli(api, runtime);
15
+ api.logger.info("[people] registered workspace-scoped memory tools and hooks");
16
+ },
17
+ });