@xdxer/dingtalk-agent 0.1.4 → 0.1.5-beta.10
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 +232 -0
- package/README.en.md +115 -89
- package/README.md +111 -86
- package/dist/bin/dingtalk-agent.js +742 -152
- package/dist/bin/dingtalk-agent.js.map +1 -1
- package/dist/src/actions.js +3 -2
- package/dist/src/actions.js.map +1 -1
- package/dist/src/agent-audit.js +202 -85
- package/dist/src/agent-audit.js.map +1 -1
- package/dist/src/agent-definition.js +7 -3
- package/dist/src/agent-definition.js.map +1 -1
- package/dist/src/agent-enhance.js +51 -32
- package/dist/src/agent-enhance.js.map +1 -1
- package/dist/src/agent-platform.js +4 -4
- package/dist/src/agent-platform.js.map +1 -1
- package/dist/src/bootstrap.js +6 -2
- package/dist/src/bootstrap.js.map +1 -1
- package/dist/src/development-workspace.js +210 -34
- package/dist/src/development-workspace.js.map +1 -1
- package/dist/src/doctor.js +65 -9
- package/dist/src/doctor.js.map +1 -1
- package/dist/src/dws.js +67 -3
- package/dist/src/dws.js.map +1 -1
- package/dist/src/init.js +2 -1
- package/dist/src/init.js.map +1 -1
- package/dist/src/memory/noop-receipt.js +306 -0
- package/dist/src/memory/noop-receipt.js.map +1 -0
- package/dist/src/memory/operational.js +27 -3
- package/dist/src/memory/operational.js.map +1 -1
- package/dist/src/memory/remote-state.js +2 -1
- package/dist/src/memory/remote-state.js.map +1 -1
- package/dist/src/multica-deploy.js +692 -125
- package/dist/src/multica-deploy.js.map +1 -1
- package/dist/src/multica-provider.js +303 -25
- package/dist/src/multica-provider.js.map +1 -1
- package/dist/src/multica-runtime-vocabulary.js +110 -0
- package/dist/src/multica-runtime-vocabulary.js.map +1 -0
- package/dist/src/opencode-evals.js +6 -6
- package/dist/src/opencode-evals.js.map +1 -1
- package/dist/src/opencode-provider.js +21 -7
- package/dist/src/opencode-provider.js.map +1 -1
- package/dist/src/opencode-workspace.js +3 -3
- package/dist/src/opencode-workspace.js.map +1 -1
- package/dist/src/personal-event-evals.js +4 -2
- package/dist/src/personal-event-evals.js.map +1 -1
- package/dist/src/promotion.js +2 -1
- package/dist/src/promotion.js.map +1 -1
- package/dist/src/remote-semantic-state-live-evals.js +14 -8
- package/dist/src/remote-semantic-state-live-evals.js.map +1 -1
- package/dist/src/remote-state-evals.js +2 -2
- package/dist/src/remote-state-evals.js.map +1 -1
- package/dist/src/robot-evals.js +3 -3
- package/dist/src/robot-evals.js.map +1 -1
- package/dist/src/schedule-plan.js +380 -0
- package/dist/src/schedule-plan.js.map +1 -0
- package/dist/src/sessions.js +1 -1
- package/dist/src/sessions.js.map +1 -1
- package/dist/src/skill-manager.js +145 -13
- package/dist/src/skill-manager.js.map +1 -1
- package/dist/src/skills.js +2 -0
- package/dist/src/skills.js.map +1 -1
- package/dist/src/tui.js +369 -0
- package/dist/src/tui.js.map +1 -0
- package/dist/src/upgrade.js +113 -33
- package/dist/src/upgrade.js.map +1 -1
- package/dist/src/waits.js +2 -1
- package/dist/src/waits.js.map +1 -1
- package/dist/src/workspace.js +12 -7
- package/dist/src/workspace.js.map +1 -1
- package/docs/AGENT-IN-PRODUCTION.md +255 -0
- package/docs/ARCHITECTURE.md +366 -0
- package/docs/INSTALLATION.md +8 -8
- package/docs/PLATFORM-GUARDRAILS.md +188 -0
- package/docs/PRIOR-ART.md +126 -0
- package/docs/SELF-TEST.md +182 -0
- package/docs/architecture/agent-platform-connection-layer.svg +120 -0
- package/docs/architecture/digital-employee-composition.svg +92 -0
- package/docs/architecture/dingtalk-agent-architecture.svg +125 -0
- package/docs/assets/digital-employee-at-work.svg +77 -0
- package/docs/schemas/multica-deployment-plan.schema.json +3 -1
- package/docs/schemas/multica-deployment-receipt.schema.json +17 -3
- package/docs/schemas/multica-deployment-status.schema.json +6 -2
- package/docs/schemas/multica-workspace-inspection.schema.json +16 -0
- package/docs/schemas/multica-workspace-run-plan.schema.json +31 -0
- package/docs/schemas/multica-workspace-run.schema.json +161 -0
- package/docs/schemas/multica-workspace-status.schema.json +2 -0
- package/docs/schemas/project.schema.json +54 -3
- package/docs/schemas/workspace-scaffold.schema.json +38 -0
- package/examples/agents/README.md +45 -0
- package/examples/agents/fde-coach/AGENTS.md +2 -25
- package/examples/agents/fde-coach/agent/AGENTS.md +35 -0
- package/examples/agents/fde-coach/agent.bindings.json +10 -0
- package/examples/agents/release-manager/AGENTS.md +2 -25
- package/examples/agents/release-manager/agent/AGENTS.md +35 -0
- package/examples/agents/release-manager/agent.bindings.json +10 -0
- package/lab/agent-eval/catalog.json +5 -5
- package/lab/agent-eval/classic-failures.json +4 -4
- package/lab/agent-eval/completion-gate-regression.json +9 -9
- package/lab/agent-eval/personal-event-live.example.json +3 -3
- package/lab/agent-eval/remote-semantic-state-live.example.json +1 -1
- package/lab/agent-eval/workspace/opencode.json +2 -2
- package/lab/project-workspace/fake-multica-provider.mjs +171 -17
- package/lab/project-workspace/multica-deploy.fixture.json +2 -2
- package/lab/project-workspace/multica-readonly.fixture.json +4 -16
- package/lab/project-workspace/opencode-provider-suite.json +3 -3
- package/lab/project-workspace/project.fixture.json +2 -6
- package/lab/robot-eval/suite.json +1 -1
- package/lab/robot-eval/workspace/AGENTS.md +1 -1
- package/lab/robot-eval/workspace/opencode.json +2 -2
- package/lab/schemas/personal-event-eval.schema.json +1 -1
- package/package.json +18 -11
- package/skills/README.md +10 -8
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/SKILL.md +49 -24
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/AGENTS.template.md +1 -1
- package/skills/core/dta-agent-compose/assets/REPOSITORY.template.md +10 -0
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/agent.bindings.dingtalk-doc.template.json +2 -2
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/agent.bindings.local.template.json +2 -2
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/hosts/opencode/opencode.template.json +3 -2
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/evals/evals.json +4 -4
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/agent-definition-contract.md +7 -7
- package/skills/core/dta-agent-compose/references/drive-and-schedules.md +166 -0
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/host-loading-contract.md +11 -13
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/hosts/claude-code.md +13 -12
- package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/hosts/opencode.md +14 -13
- package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/SKILL.md +28 -3
- package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/assets/eval-catalog.template.json +1 -1
- package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/evals/evals.json +1 -1
- package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/eval-topology.md +3 -3
- package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/interactive-debug-channels.md +10 -4
- package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/SKILL.md +21 -5
- package/skills/core/dta-basic-behavior/references/event-to-behavior.md +38 -0
- package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/memory-and-evolution.md +3 -1
- package/skills/core/dta-basic-behavior/references/perception-and-gates.md +87 -0
- package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/risk-authority-and-privacy.md +12 -0
- package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/truth-and-recovery.md +4 -2
- package/skills/core/dta-people-group-memory/COMPLETENESS.md +36 -0
- package/skills/core/dta-people-group-memory/SKILL.md +69 -0
- package/skills/core/dta-people-group-memory/references/adapters.md +273 -0
- package/skills/core/dta-people-group-memory/references/assembly-guidance.md +40 -0
- package/skills/core/dta-people-group-memory/references/binding.md +110 -0
- package/skills/core/dta-people-group-memory/references/cold-start.md +70 -0
- package/skills/core/dta-people-group-memory/references/config-binding.md +89 -0
- package/skills/core/dta-people-group-memory/references/consent-and-visibility.md +83 -0
- package/skills/core/dta-people-group-memory/references/consolidation.md +162 -0
- package/skills/core/dta-people-group-memory/references/event-ingest.md +103 -0
- package/skills/core/dta-people-group-memory/references/guided-setup.md +70 -0
- package/skills/core/dta-people-group-memory/references/model.md +148 -0
- package/skills/core/dta-people-group-memory/references/storage-port.md +107 -0
- package/skills/platforms/deap/PLATFORM.md +30 -1
- package/skills/platforms/multica-dingtalk/PLATFORM.md +35 -9
- package/skills/platforms/multica-dingtalk/{dingtalk-agent-deploy-multica → dta-deploy-multica}/SKILL.md +10 -8
- package/skills/platforms/multica-dingtalk/dta-deploy-multica/references/multica-deployment-contract.md +67 -0
- package/skills/platforms/multica-dingtalk/{multica-external → dta-ops-multica}/SKILL.md +81 -11
- package/skills/platforms/multica-dingtalk/{multica-external → dta-ops-multica}/scripts/bootstrap.sh +2 -2
- package/skills/platforms/multica-dingtalk/{multica-external → dta-ops-multica}/scripts/multica_ext.py +264 -16
- package/dist/src/map.js +0 -157
- package/dist/src/map.js.map +0 -1
- package/docs/SECOND-AGENT-ACCEPTANCE.md +0 -62
- package/docs/architecture/agent-memory-topology.png +0 -0
- package/docs/architecture/agent-memory-topology.svg +0 -132
- package/docs/architecture/dingtalk-agent-blueprint.png +0 -0
- package/docs/architecture/durable-async-agent-runtime.png +0 -0
- package/docs/architecture/general-agent-kernel-topology.png +0 -0
- package/docs/architecture/provider-bound-development-workspace.png +0 -0
- package/docs/architecture/task-completion-gate.png +0 -0
- package/docs/assets/agent-delivery-lifecycle.svg +0 -103
- package/skills/core/dingtalk-basic-behavior/references/event-to-behavior.md +0 -24
- package/skills/core/dingtalk-basic-behavior/references/perception-and-gates.md +0 -28
- package/skills/platforms/deap/README.md +0 -3
- package/skills/platforms/multica-dingtalk/dingtalk-agent-boot-multica/SKILL.md +0 -40
- package/skills/platforms/multica-dingtalk/dingtalk-agent-deploy-multica/references/multica-deployment-contract.md +0 -49
- /package/examples/agents/fde-coach/{skills → agent/skills}/fde-coach/SKILL.md +0 -0
- /package/examples/agents/release-manager/{skills → agent/skills}/release-manager/SKILL.md +0 -0
- /package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/assets/role-skill.template.md +0 -0
- /package/skills/core/{dingtalk-agent-compose → dta-agent-compose}/references/storage-routing.md +0 -0
- /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/evidence-contract.md +0 -0
- /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/failure-to-case.md +0 -0
- /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/local-connector-smoke.md +0 -0
- /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/scenario-taxonomy.md +0 -0
- /package/skills/core/{dingtalk-agent-eval → dta-agent-eval}/references/storage-modes.md +0 -0
- /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/assets/memory-candidate-proposal.json +0 -0
- /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/assets/task-checkpoint.json +0 -0
- /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/action-contract.md +0 -0
- /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/runtime-modes.md +0 -0
- /package/skills/core/{dingtalk-basic-behavior → dta-basic-behavior}/references/task-lifecycle.md +0 -0
- /package/skills/platforms/multica-dingtalk/{dingtalk-agent-deploy-multica → dta-deploy-multica}/references/promotion-observation-contract.md +0 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# 适配器 —— 介质特有的坑全部收在这里
|
|
2
|
+
|
|
3
|
+
模型层与事件层不读本篇。本篇只回答:某个介质怎么实现 `storage-port.md` 的 10 个操作、它的能力位为什么是那个取值。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# adapter: `dingtalk-doc`(生产)
|
|
8
|
+
|
|
9
|
+
标 🔬 的是 2026-07-22/23 在真实生产身份上跑出来的实测结论,不是推断。
|
|
10
|
+
|
|
11
|
+
## 能力位为什么是那样
|
|
12
|
+
|
|
13
|
+
### `access.scope = leaf`
|
|
14
|
+
|
|
15
|
+
🔬 对一个已上线 Agent 的知识库做过两类探测:
|
|
16
|
+
|
|
17
|
+
| 层级 | 结果 |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `wiki space get` / `wiki member list` | `forbidden.accessDenied` |
|
|
20
|
+
| `wiki node list`(不带 folder,即列根) | `RESOURCE_NOT_FOUND` |
|
|
21
|
+
| `wiki space list --type myWikiSpace` | 返回里**没有**它 |
|
|
22
|
+
| `wiki space list --type orgWikiSpace`(翻完 76 个空间) | 返回里**没有**它 |
|
|
23
|
+
| `doc info --node <根>` | ✅ 成功 |
|
|
24
|
+
| `wiki node list --workspace <ws> --folder <根>` | ✅ 成功 |
|
|
25
|
+
| `drive permission list --node <根>` | ✅ 成功,显示 **Agent=OWNER,开发者=MANAGER** |
|
|
26
|
+
|
|
27
|
+
那个知识库是**运行时身份自己的个人库**,开发者只是节点级协作者。
|
|
28
|
+
|
|
29
|
+
⇒ **容器级探测会 100% 漏掉真正可用的落点。** 容器级失败必须映射为 `UNAVAILABLE`,**绝不能**推出 `NOT_CONFIGURED`——否则已上线场景恒判「没有可用落点」,能力装了但永远静默不启用。
|
|
30
|
+
|
|
31
|
+
### `access.probe = empirical`
|
|
32
|
+
|
|
33
|
+
🔬 `drive permission list` 只返回 `{name, role, type}`,**不返回稳定人标识**;返回的 `name` 是显示昵称(「冬翔」),与 `auth status.user_name`(「夏东翔」)**不相等**。没有任何字段能把列表项与当前身份对上。
|
|
34
|
+
🔬 `wiki member list` 明说「**ORG 类型授权不会出现在查询结果中**」,且底层不支持游标分页、`truncated=true` 时结果被截断。
|
|
35
|
+
|
|
36
|
+
⇒ 查权限表这条路是死的,只能**写一次再回读**。🔬 这一步实测约 **15s**,所以只在绑定期做一次、结果存进绑定档案。
|
|
37
|
+
|
|
38
|
+
### `read.completeness = verified-partial`
|
|
39
|
+
|
|
40
|
+
🔬 `doc read` 在 **341 行 / 31.9KB** 静默截断:返回 `success:true`,响应里**没有 truncated / hasMore / 任何分页字段**,内容被直接切掉。
|
|
41
|
+
|
|
42
|
+
⇒ 适配器必须自证完整性(带外范围探测优先,尾部哨兵次之),并在不确定时返回 `complete=false`。**模型层不知道「截断」这个词**,它只看 `complete`。
|
|
43
|
+
|
|
44
|
+
这是最危险的一位:任何随对象数线性增长的单页清单(停用账本、tombstone、已提议账本)都会越过阈值,此后读到的是「成功但不完整」,**fail-closed 会变成 fail-open**,几个月前说过「别记了」的人被重新写入,**没有任何错误信号**。⇒ 这类清单一律按主键哈希桶分片并写桶尾哨兵。
|
|
45
|
+
|
|
46
|
+
### `identity.collision = silent-rename`
|
|
47
|
+
|
|
48
|
+
🔬 同名建节点**不报错**,静默改名成 `<名>(1)`,`create` 照样返回 `success:true`。
|
|
49
|
+
🔬 2026-07-18 实测:文件夹层一拍内建出 4 份残片(4 个人);页层另有 2 人出现 `<页名>` + `<页名>(1)` 两份。页层危害更隐蔽——水位取并集时残片会让水位报「新鲜」,而人点进正本只看到旧内容。
|
|
50
|
+
|
|
51
|
+
⇒ `ensureEntity` 内部:建后**必回读父容器**核对自己那个对象的 `name`;带 `(1)` 就立刻删掉自己刚建的、改去正本;同键发现多份一律返回 `ambiguous` → HALT。
|
|
52
|
+
|
|
53
|
+
### `lookup.negative = list-only`
|
|
54
|
+
|
|
55
|
+
🔬 `wiki node search` 有分钟级索引延迟,刚建的搜不到。
|
|
56
|
+
🔬 花名册类长文档不能用来判存在(`doc read` 会截断,后半段的人查不到)。
|
|
57
|
+
|
|
58
|
+
⇒ 否定结论只能来自翻完页的 `listEntities`。
|
|
59
|
+
|
|
60
|
+
### `write.cas = none`(raw 与 view 都是)
|
|
61
|
+
|
|
62
|
+
🔬 `dws doc update` 只有 `--mode overwrite|append`,**没有任何 revision / expectVersion / CAS 参数**。
|
|
63
|
+
|
|
64
|
+
⇒ 编译层的整体重写同样没有并发保护。**不要以为编译层比原始层安全**:两个并发写者会互相覆盖,只能靠写后回读 + 最后一次为准。
|
|
65
|
+
|
|
66
|
+
### `limits`
|
|
67
|
+
|
|
68
|
+
🔬 单次写入约 1 万字符上限;长文本禁裸 `--content`,一律 `--content-file`;overwrite 先 `--dry-run` 再 `--yes`。
|
|
69
|
+
🔬 知识库描述硬顶 50 字符(建库时)。
|
|
70
|
+
|
|
71
|
+
## 物理映射
|
|
72
|
+
|
|
73
|
+
| 端口概念 | 钉钉文档上是什么 |
|
|
74
|
+
|---|---|
|
|
75
|
+
| root | 一个文件夹节点 |
|
|
76
|
+
| entity | root 下的子文件夹,**名字里含主键**(显示名只是给人看的前缀) |
|
|
77
|
+
| section (raw) | 一个或多个只增页;跨月滚动是适配器内部分区策略 |
|
|
78
|
+
| section (view) | 一个可整体重写的页 |
|
|
79
|
+
| meta 键空间 | 一个专用页,扁平 `key\|value` 行,按哈希桶分片 |
|
|
80
|
+
|
|
81
|
+
🔴 **判存在只匹配主键子串,绝不匹配显示名。** 人会改花名、群会改名(改名是常规操作),按名字判存在必然匹配不上 → 建第二份 → 正本与新档各写一份、召回读旧的沉淀写新的。
|
|
82
|
+
|
|
83
|
+
## 其它实测坑
|
|
84
|
+
|
|
85
|
+
- 富格式有损:`doc read` 会丢样式,所以**语义全压在正文文本上,不靠样式**——这也是这套设计敢用文本回读做校验的前提。
|
|
86
|
+
- 钉钉标题里别放行内代码(会被吞);代码块别嵌进有序列表项(整段丢)。
|
|
87
|
+
- 删页 `doc delete --node <id> --yes`(进回收站);删文件夹 `wiki node delete --node <id> --yes`。
|
|
88
|
+
- 🔬 个人库根 `wiki node list` 不带 `--folder` **可用**(列根成功),而属于别人的库同样调用返回 `RESOURCE_NOT_FOUND`——所以「列根能不能用」本身不是稳定判据。
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
# adapter: `local-md`(开发 / 评测)
|
|
93
|
+
|
|
94
|
+
## 落地形态
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
<root>/
|
|
98
|
+
├── people/<personKey>/ 目录名 = 主键;显示名进 front-matter
|
|
99
|
+
│ ├── interaction.md raw section,只增
|
|
100
|
+
│ ├── identity.md view section,整体重写
|
|
101
|
+
│ └── ...
|
|
102
|
+
├── conversations/<conversationKey>/
|
|
103
|
+
└── _meta/
|
|
104
|
+
├── watermark.json 键 = (subject, channel)
|
|
105
|
+
└── binding.json
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- raw section = 一个只增 `.md`
|
|
109
|
+
- view section = 写临时文件后 `rename`(原子替换,这就是 `write.cas=revision` 的来源)
|
|
110
|
+
- meta = `_meta/*.json`
|
|
111
|
+
|
|
112
|
+
## 能力位为什么全是宽松档
|
|
113
|
+
|
|
114
|
+
本地文件系统有原子 rename、有真实的存在性判断、没有静默截断、没有同名静默改名。所以 `read.completeness=exact`、`identity.collision=reject`、`lookup.negative=immediate`、`write.cas=revision`、`write.confirm=implicit`。
|
|
115
|
+
|
|
116
|
+
**这正是解耦的检验**:在这个适配器上,通用算法里的完整性校验、写后回读、判重读、翻页判存在四条分支应当**自动全灭,而 Skill 正文一字不改**。如果做不到,说明抽象没做干净。
|
|
117
|
+
|
|
118
|
+
## `--chaos` 档:契约的组成部分,不是建议
|
|
119
|
+
|
|
120
|
+
`local-md --chaos` 必须能注入:
|
|
121
|
+
|
|
122
|
+
- 强制 `complete=false`
|
|
123
|
+
- 随机静默改名(模拟 `(1)` 残片)
|
|
124
|
+
- 随机 `UNVERIFIED` 回执
|
|
125
|
+
- 索引延迟(让 `lookup.negative` 表现成 `list-only`)
|
|
126
|
+
- 判重读截断
|
|
127
|
+
|
|
128
|
+
🔴 **CI 必须让 chaos 档与真实档跑同一组用例。** 否则这套抽象只是把 bug 挪到了适配器边界之外,第一次执行仍然发生在生产环境、发生在真实同事的档案上。
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
# adapter: `aitable`(registry 类 section 的生产介质)
|
|
133
|
+
|
|
134
|
+
🔴 **不是所有 section 都该上文档。** `model.md` §三把 section 分成 `narrative`(叙事,正文)与 `registry`(注册表,一行一条按 key 查)。narrative 上文档,**registry 上 AI 表格**——因为 AI 表格恰好把文档为 registry 打的那一整套补丁变得不需要。
|
|
135
|
+
|
|
136
|
+
## 能力位为什么和 dingtalk-doc 相反
|
|
137
|
+
|
|
138
|
+
标 🔬 的是 2026-07-23 实测 `dws aitable ... --help` / record query 的结论。
|
|
139
|
+
|
|
140
|
+
| 位 | `aitable` | 为什么(实测) |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| `read.completeness` | **`exact`** | 🔬 `record query --all` 真分页,`--cursor` 空即取完、`--page-limit 0` 无限;没有 341 行静默截断墙。**→ count 对账不再需要** |
|
|
143
|
+
| `lookup.negative` | **`immediate`** | 🔬 `record query --filters <JSON>` 按字段查;查不到就是**真没有**,不像 `wiki node search` 有索引延迟。**→ "判存在只认列父目录翻完页" 不再需要** |
|
|
144
|
+
| `write.cas` | `revision` | 🔬 `record update --records`(含 recordId)精准改一行,不是整页 overwrite;按 recordId 定位**不会撞 `(1)` 残片** |
|
|
145
|
+
| `identity.collision` | `reject` | 🔬 记录按 recordId 唯一;可用主键字段做业务唯一性。**→ 建后回读核对残片 不再需要** |
|
|
146
|
+
| `maxFactBytes` | 单元格上限 | 🔬 每字段是单元格,不适合长正文(正是 narrative 不上表的原因) |
|
|
147
|
+
| `access.probe` | `declarative` | base/table 权限在 base 级可读 |
|
|
148
|
+
|
|
149
|
+
## registry 病理对照:同一份数据,上文档 vs 上表
|
|
150
|
+
|
|
151
|
+
| registry section | 上文档(现状病理) | 上 AI 表格 |
|
|
152
|
+
|---|---|---|
|
|
153
|
+
| `conversation-index`(原 `00-群索引`) | 一行一群,几百群 → **越 341 行静默截断** → 读到的是"成功但不完整"的群清单 | 一群一 record,`record query --all` 全取、按 groupType `--filters` 筛 |
|
|
154
|
+
| `watermark` 台账 | 读整页 `_parse_wm_doc` 解析、同一人多行取 max;台账变乱靠 lint 重建 | 按 `(subjectKey,channel)` 一次 query,O(1);`record update` 改一行 |
|
|
155
|
+
| `count` 对账 | —(本来就是为了绕文档截断才发明的)| record 存 N_write,query 即得,**对账退化成两个数直接比** |
|
|
156
|
+
| `tombstone` 遗忘账本 | 随人数×时间线性增长 → 越 341 行 → **fail-open:几个月前说"别记了"的人被重新写入,无报错** | 一条 tombstone 一 record,`record query --filters` 精确命中,**永不 fail-open** |
|
|
157
|
+
| `roster` | 大群成员塞一页 → 截断 | 一成员一 record,可按会话/角色查 |
|
|
158
|
+
|
|
159
|
+
**结论**:registry 上 AI 表格后,`consolidation.md` §0 的 count 对账、`storage-port.md` 的 `lookup.negative=list-only`+分桶+哨兵,这些**对 registry 不再需要**——它们是叙事介质的补丁。narrative section(identity/chronicle/digest 正文)仍留文档、仍需要它们。
|
|
160
|
+
|
|
161
|
+
## 物理映射
|
|
162
|
+
|
|
163
|
+
| registry | AI 表格落法 |
|
|
164
|
+
|---|---|
|
|
165
|
+
| person-index / conversation-index | 各一张 table,主键字段 = personKey / conversationKey |
|
|
166
|
+
| watermark / count | 一张 meta table,复合主键字段 |
|
|
167
|
+
| tombstone / proposal-ledger | 各一张 table |
|
|
168
|
+
|
|
169
|
+
绑定档案(Record)记两处落点:narrative 的文档 root + registry 的 baseId/tableId。Boot 的 seed 可以直接是 baseId(AI 表格的 base 可 `resolve-base` 按名解析,比文档节点更好找)。
|
|
170
|
+
|
|
171
|
+
## aitable 最佳实践(血泪教训,registry 适配器必须遵守)
|
|
172
|
+
|
|
173
|
+
来源:`dws` skill 的 `references/products/aitable/*` 与 FDE 引擎实测注释。**这些坑会让 registry 静默失效——比文档的截断更隐蔽,因为它们伪装成"成功的空结果"。**
|
|
174
|
+
|
|
175
|
+
🔴 **1. `--all` 会吞掉服务端错误**(FDE 2026-07-14 实测)。同一个坏查询,带不带 `--all` 是两个世界:
|
|
176
|
+
- 不带 `--all`:坏 fieldId 报 `SCAN_RECORDS_FAILED`;合法 0 行是 `data:{}`。
|
|
177
|
+
- 带 `--all`:坏 fieldId / 坏 tableId / 合法 0 行**全返回同一个信封** `{records:null,totalCount:0}`、`success:true`、无 error。
|
|
178
|
+
|
|
179
|
+
后果对 registry 是灾难:`lookup.negative=immediate` 的"查不到即真没有",一旦 fieldId 打错一个字母就变成静默"查无此人" → **会重复建档**。堵法:**读之前先用不带 `--all` 的元数据接口(`table get --table-ids`)把表和字段证一遍**,每进程各一次;证完之后 0 行才是可信的 0 行。这是 `read.completeness=exact` 在 aitable 上的**前置条件**,不是白给的。
|
|
180
|
+
|
|
181
|
+
🔴 **2. fieldId 不自带语义,必须回读表头对账**(AGENTS.md 铁律)。fieldId 写错有两种下场都静默:用在过滤条件 → 带 `--all` 时错误被吞返回 0 行;用在读列 → 那一列读成空字符串。所以每进程第一次读目标表必须 `table get` 回读表头,验证 `fieldId ↔ 预期字段名` 没漂移,绝不根据单元格值猜列。
|
|
182
|
+
|
|
183
|
+
🔴 **3. dangerous Unicode 不洗则整行写不进、无报错**(FDE 2026-07-14 实测)。钉钉表格拒收:控制符(C0/C1)、零宽字符 U+200B–200F(含 **ZWJ U+200D**)、bidi 覆写、U+2060–206F、U+FEFF。**ZWJ 是关键**——Agent 回复里 `🤦♂️` 这类组合 emoji 天天有,不洗就整行 0 行落地。写进表前必洗(洗掉 ZWJ 后 `🤦♂️`→`🤦♂`,可读性不受影响)。
|
|
184
|
+
|
|
185
|
+
🔴 **4. API 会瞬时抽风**(FDE 2026-07-15 实测):同一条 record query 连发 3 次,第 1 次 `business error: success=false`,第 2、3 次正常。record query/update 一撞就挂 → 必须退避重试(2s/4s),可重试的错才重试,真错才 die。
|
|
186
|
+
|
|
187
|
+
🔴 **5. 按 key 查,别全量拉**(registry 用好 aitable 的核心)。registry 表随实体数增长,一年后 person-index 可能上千行、watermark 更多。`--all` 全量扫是反模式——它正是文档侧"读整页花名册翻到截断"的镜像,而 aitable 上表的**全部价值就是按 key 服务端查**:
|
|
188
|
+
|
|
189
|
+
| 要干什么 | 正确姿势 | 别这么做 |
|
|
190
|
+
|---|---|---|
|
|
191
|
+
| 判某人有没有档案 / 取某人水位 | `record query --filters '{"operator":"and","operands":[{"operator":"eq","operands":["<keyFieldId>","<key>"]}]}'` —— 这就是 `lookup.negative=immediate` | `--all` 拉全表再客户端 find |
|
|
192
|
+
| 取已知的几行 | `--record-ids r1,r2,...`(单次 ≤100) | 全表扫 |
|
|
193
|
+
| 模糊找 | `--keyword` 或 `+find-record` | — |
|
|
194
|
+
| 真要枚举一个子集(如"有 backlog 的活跃人")| **先用 `--filters` 服务端筛到工作子集**,再 `--cursor` 翻页;`--field-ids` 只取需要的列省 token | `--all` 无脑全量 |
|
|
195
|
+
|
|
196
|
+
`--all` 默认 `--page-limit 50`(5000 行)就停——**超过就静默截断成"前 5000 行"**,又是一个 fail-open。真要全量必须 `--page-limit 0` 并接受量与 token 成本;但绝大多数 registry 操作是**点查**,根本不该全量。夜间维护"扫一遍活跃对象"也应 `--filters` 服务端筛(status=active 且水位落后),不是拉全表。
|
|
197
|
+
|
|
198
|
+
**6. 幂等写用 `record upsert`**:按 `recordId` 存在与否自动分流 create/update,省掉客户端按 ID 分批。registry 的水位/索引更新正是这个模式——同一 key 反复 upsert,天然幂等。单次最多 100 条。
|
|
199
|
+
|
|
200
|
+
**7. filters 语法**:最外层必须是 `and`/`or`,比较放 operands 内;`{"operator":"and","operands":[{"operator":"eq","operands":["<fieldId>","<value>"]}]}`。singleSelect 用选项 name 字符串。
|
|
201
|
+
|
|
202
|
+
**8. 返回信封三层**:数据在 `data.<字段>`(base list→`data.bases`、table get→`data.tables[0].fields`、record→`data.records`),不在顶层。取错路径得到空——按不变式三这是读失败不是没有。
|
|
203
|
+
|
|
204
|
+
## 端口层面:仍不新增能力位
|
|
205
|
+
|
|
206
|
+
aitable 用的全是已有的 10 操作 + 8 位——只是取值和 dingtalk-doc 相反。这正是**存储端口抽象成立的最强证据**:模型层对 registry 和 narrative 用同一套 `appendFacts`/`readFacts`/`metaGet`/`ensureEntity`,换介质只换能力位取值,模型正文一字不改。`read.completeness=exact` 让完整性校验分支自动全灭,`lookup.negative=immediate` 让翻页判存在分支自动全灭——**这就是 `storage-port.md` 开头那条解耦检验的现场兑现**。
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
# source adapter: 钉钉 IM / 待办 / 文档
|
|
211
|
+
|
|
212
|
+
存储适配器回答「存哪」,源适配器回答「事件从哪来、主键取哪个字段、哪些信号取不到」。模型层同样不读本节。
|
|
213
|
+
|
|
214
|
+
## 主键取哪个字段
|
|
215
|
+
|
|
216
|
+
| 模型概念 | 钉钉字段 | 说明 |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| `personKey` | `openDingTalkId` | 🔬 群成员、消息 `senderOpenDingTalkId`、通讯录三处实测一致 |
|
|
219
|
+
| `conversationKey` | `openConversationId` | 🔬 单聊也有;跨组织形态更长且含 `/` 与 `=`,**整串使用,禁止截断或当路径片段** |
|
|
220
|
+
|
|
221
|
+
🔬 `userId`(员工编号)是**另一个命名空间**:只有本组织成员有、外部人为 null,且 chat 的成员与消息返回里**不带**它;待办里的 userId 与通讯录的 userId 实测不是同一个值。只能作可空副键。
|
|
222
|
+
|
|
223
|
+
## ingest 主干
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
dws chat message list-all --start <上次水位> --end <now> --limit 50 --cursor 0 -f json
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
🔬 服务端已按会话分组并带 `singleChat`,**人与会话在入口天然分流**。
|
|
230
|
+
|
|
231
|
+
🔬 **返回体在 `result.<字段>` 下,不在顶层**(顶层是 `{arguments, errorCode, errorMsg, result, success}`)。取错路径会得到「0 条」——按不变式三这是**读失败不是没有**。
|
|
232
|
+
|
|
233
|
+
🔬 **翻页只认 `createTime`,不认 `hasMore` / `nextCursor`**:两套翻页口径并存且不可靠。群名录 `dws chat group list-all` 必须 `while hasMore` 跟 `nextCursor` 循环,**绝不用「返回条数 < limit」判结束**——`--limit` 不被服务端尊重(实测 `--limit 3` 回 2 条、`--limit 200` 回 81 条且 `hasMore=true`)。
|
|
234
|
+
|
|
235
|
+
## 会话隐私档位:白名单
|
|
236
|
+
|
|
237
|
+
🔬 2026-07-22 翻完 47 页枚举 **976 个群**,实际出现的 `groupType`:`NORMAL_GROUP` 902 / `OLD_EXTERNAL_GROUP` 33 / `UNKNOWN_TYPE` 30 / `COOPERATIVE_GROUP` 10 / `NEW_EXTERNAL_GROUP` 1。
|
|
238
|
+
|
|
239
|
+
其中 **`OLD_EXTERNAL_GROUP` 不在任何文档给出的枚举列表里**——黑名单写法会把 33 个外部群当成内部群、把外部人员上浮进人页。
|
|
240
|
+
|
|
241
|
+
⇒ 只有 `INTERNAL_GROUP` / `NORMAL_GROUP` 走正常档,**其余一切(含将来新增的未知值)一律最小披露档**。
|
|
242
|
+
|
|
243
|
+
## 治理角色
|
|
244
|
+
|
|
245
|
+
🔬 只能从 `group members` 的 `memberRoleType` 取:**1 = 群主、3 = 普通成员**;**2 推测为管理员但未实测确认**,写进档案必须标「推测」。
|
|
246
|
+
🔬 `group-role list` 返回的是「群自定义身份」(实测为 `[]`),**不是**治理角色。
|
|
247
|
+
🔬 `myRole` 词表两处不一致:`group list-all` 返回中文(`普通成员`/`群主`/`群管理员`),`group list-my-groups` 返回英文 `OWNER`。join 前必须归一化。
|
|
248
|
+
🔬 **大群首页 500 人全是普通成员**,群主/管理员不保证在前。不翻完全部页时必须写明「仅前 N 页,管理层可能未覆盖」。
|
|
249
|
+
|
|
250
|
+
## 取不到的信号(`AUTH_REQUIRED` 与 `UNSUPPORTED`)
|
|
251
|
+
|
|
252
|
+
- 🔬 **「@我」不可用**:`message search-advanced --at-me` 与 `message list-mentions` 返回里**没有 messages 字段**,翻页仍 0 条。
|
|
253
|
+
- 🔬 **按稳定人标识切片消息不可用**:`list-by-sender --sender-open-dingtalk-id` 服务端报 `senderUid is required`,只有 `--sender-user-id` 可用 ⇒ 外部人没有这条路。
|
|
254
|
+
- 🔬 **单聊无法全量枚举**:`list-all-conversations` 的 `--cursor` 是死参数、硬顶 100 条。Person 只能被时间窗自然发现。
|
|
255
|
+
- 🔬 **从单聊会话反推不出对方**:`conversation-info --user` 不给对方标识,`SINGLE_CHAT` 的 `ownerOpenDingtalkId` 实测不是对方。建档要能接受「暂挂起」而不是绑错人。
|
|
256
|
+
- 🔬 **跨组织需单独授权**:`chat data-auth cross-org` 属授权类操作。未授权时「读到 0 条」与「真的没消息」**同形**。
|
|
257
|
+
- 🔬 **PAT 行为授权门**:`chat.message:list` 受行为授权门控。托管运行时(`DINGTALK_DWS_AGENTCODE` 非空)命中时以 stderr JSON + **`exit=4`** 返回,CLI 不拉起浏览器。⇒ 源适配器必须有 `AUTH_REQUIRED` 一态,**不许归进「未配置」静默关能力,也不许诊断成「存储没写权限」**。绑定期就跑一次 `dws pat chmod --dry-run` 盘点 scope。
|
|
258
|
+
|
|
259
|
+
## 其它
|
|
260
|
+
|
|
261
|
+
- 🔬 转发消息 `forwardMessages` 的子消息 `sender` 是字符串 `"null"`,**不可归因**。
|
|
262
|
+
- 🔬 个别消息 content 是不可解的 base64 密文块;图片渲染成 `[图片消息]` 加 `mediaId=` 参数。
|
|
263
|
+
- 🔬 `todo task list` 不传 `--role-types creator,executor,participant` 会**静默漏**;且是 N+1 成本点(无服务端增量过滤),只能低频全量快照 + 本地 diff。
|
|
264
|
+
- 🔬 `drive search --creator-uids` 被静默忽略。
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
# 新增适配器时
|
|
269
|
+
|
|
270
|
+
1. 先填能力位取值表,**不许新增位**——除非它能同时改变 ≥2 个适配器的行为,且总位数不超过 8。
|
|
271
|
+
2. 把该介质的所有病理写进本篇对应小节,**不许回流到 `model.md`**。
|
|
272
|
+
3. 在 `local-md --chaos` 的用例集上跑通。
|
|
273
|
+
4. 如果发现某条病理落不进任何现有能力位,那是抽象的缺口——**先改端口,别在模型层加 if**。
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# 装配侧 —— 标准能力,装配时几乎不用做什么
|
|
2
|
+
|
|
3
|
+
这个 Skill 属于**默认套装**,`dta setup` 就装上了。装配时**不需要**判适用性、不需要选落点、不需要问用户要不要开——那些全部推迟到用户主动要求时(见 `binding.md`)。
|
|
4
|
+
|
|
5
|
+
这么设计的原因:装配跑在**开发者身份**上,而真正要写入的是**运行时身份**。装配时绑出来的落点,运行时经常写不进去;实测存在"知识库属于运行时身份、开发者只是节点级协作者"的真实拓扑,反过来也一样。**绑定必须发生在要写入的那个身份上、在真正要写的那一刻。**
|
|
6
|
+
|
|
7
|
+
## 装配时唯一要做的事:在本体里留一个 seed 位
|
|
8
|
+
|
|
9
|
+
如果这个 Agent 已经绑定过存储,把绑定档案的定位符写进本体 `AGENTS.md`,让 Boot 能一跳拿到(否则每次 Boot 要付全量兜底的代价)。**没绑定过就什么都不用写。**
|
|
10
|
+
|
|
11
|
+
```markdown
|
|
12
|
+
## 长期档案(人与会话)
|
|
13
|
+
|
|
14
|
+
我可以为长期打交道的同事和会话维护档案。**未绑定存储前不写任何东西**,也不会说“我记住了”。
|
|
15
|
+
|
|
16
|
+
- 绑定档案:[定位符] ← 绑定后由 Agent 自己回填;没绑定就删掉这一行
|
|
17
|
+
- 边界:档案是我的归纳不是原始事实,引用结论时带来源。单聊内容属于对方向我的信任
|
|
18
|
+
披露,回答第三方时降披露;会话里对某人的评价不写进任何档案。用户要求“别记了”
|
|
19
|
+
时本次立即停止使用,并按记忆纠正流程处理已落盘内容,不静默篡改原始层。
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 改造已有 Agent 时
|
|
23
|
+
|
|
24
|
+
检查两件事:
|
|
25
|
+
|
|
26
|
+
1. 本体里的 seed 是否还能一跳解析(`storage-port.md` 的 `resolve`)。解析不到就删掉这一行,让它回到未绑定态,而不是留一个死指针。
|
|
27
|
+
2. **运行时身份是否变过。** 变过就是身份漂移——绑定档案里的 `boundIdentity` 会在下次 Boot 检测到并停链。装配时发现的话,直接告诉用户档案是旧身份建的、需要重新绑定或先处理所有权。
|
|
28
|
+
|
|
29
|
+
## 与其它层的边界
|
|
30
|
+
|
|
31
|
+
按 compose 的分层规则(所有员工适用的上提、只属于一个 Agent 的长期选择进本体、绕过会出错的进 Gate):
|
|
32
|
+
|
|
33
|
+
| 内容 | 落在哪 |
|
|
34
|
+
|---|---|
|
|
35
|
+
| 记什么、隐私分级、实体与事实模型 | 本 Skill(所有 Agent 共享的标准协议) |
|
|
36
|
+
| 存储介质怎么落、介质特有的坑 | 本 Skill 的 `storage-port.md` / `adapters.md` |
|
|
37
|
+
| 这个 Agent 绑到哪、开了哪几个根 | **绑定档案**(运行时身份持有),本体里只留 seed |
|
|
38
|
+
| 身份、写权、幂等这些绕过会出事的 | Gate:绑定时探一次并落档,Boot 时比对 |
|
|
39
|
+
|
|
40
|
+
🔴 落点定位符**不进 Skill 源码**。可机械验收:`grep` 本 Skill 源码不得命中任何真实 nodeId / workspaceId / 完整文档 URL。
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# 按需绑定 —— 能力一直在,存储用到才绑
|
|
2
|
+
|
|
3
|
+
这个能力是**标准装配**的一部分,每个 Agent 都有。但它在绑定存储之前**完全惰性**:零写入、零副作用、零探测。
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
unbound ──用户主动要求──▶ binding ──绑定成功──▶ active
|
|
7
|
+
│ │ │
|
|
8
|
+
│ 零写入 │ 一次性探测+建档 │ 正常读写
|
|
9
|
+
│ 不许说"我记住了" │ 用户确认落点 │ 每 Boot 校验身份
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**为什么不在装配时就绑**:装配跑在开发者身份上,而真正要写入的是**运行时身份**——两者常常不是同一个。装配时绑出来的落点,运行时可能根本写不进去。用户主动要求时绑,绑的就是当下这个身份,一次到位。
|
|
13
|
+
|
|
14
|
+
## 一、unbound 态的纪律
|
|
15
|
+
|
|
16
|
+
- ❌ 不建任何目录、不写任何页、不做任何探测
|
|
17
|
+
- ❌ **禁止**说"我记住了""我记下了""下次我会记得"
|
|
18
|
+
- ✅ 可以说"我这次记着,但没有长期档案"
|
|
19
|
+
|
|
20
|
+
这条不是措辞洁癖:用户听到"我记住了"就会停止重复交代,等到下次发现 Agent 完全不记得,他会认为 Agent 在骗人——而且是**在他已经依赖它之后**才发现。
|
|
21
|
+
|
|
22
|
+
## 二、什么算"用户主动要求"
|
|
23
|
+
|
|
24
|
+
明确表达要长期记住的意思即可,不必逐字匹配:
|
|
25
|
+
|
|
26
|
+
- "记住这个人" / "把和他的聊天沉淀一下" / "以后记得我的偏好"
|
|
27
|
+
- "这个群的规矩你记一下"
|
|
28
|
+
- 直接问"你能记住我吗" / "你会记得我们聊过什么吗"
|
|
29
|
+
|
|
30
|
+
🔴 **不主动兜售。** 不要在没被问到时说"我可以给你建档案哦"。唯一的例外见 `consent-and-visibility.md`:确实有一条值得留的信息、且当前正事已收口时,可以作为**尾句**提议一次,用户不回答即不启用。
|
|
31
|
+
|
|
32
|
+
## 三、绑定流程
|
|
33
|
+
|
|
34
|
+
### 1. 认清身份(这是绑定的锚,不是形式)
|
|
35
|
+
|
|
36
|
+
拿到当前运行时身份及其组织归属,**写进绑定档案**。之后每次 Boot 都比对;不一致就停链报人。
|
|
37
|
+
|
|
38
|
+
### 2. 探测可写的落点(只读)
|
|
39
|
+
|
|
40
|
+
按 `storage-port.md` 的 `probe` 操作让适配器给出候选。
|
|
41
|
+
|
|
42
|
+
🔴 **能力判定必须降到最细粒度。** 容器级(知识库/空间)枚举不到,**不等于**里面的节点不可用——实测存在"容器属于另一个身份、当前身份只是节点级协作者"的真实拓扑,此时容器级探测 100% 漏掉可用落点。容器级探测**只产候选,永不产否定结论**。
|
|
43
|
+
|
|
44
|
+
### 3. 给用户选,每项都要说出代价
|
|
45
|
+
|
|
46
|
+
不要替用户默认。至少给出:复用已有位置 / 在运行时身份自己的空间自建 / 放团队共享位置 / 人与会话分置。每项必须同时说清:**谁是所有者、谁能看到、换身份要不要重新授权**。
|
|
47
|
+
|
|
48
|
+
### 4. 建档并回读
|
|
49
|
+
|
|
50
|
+
建完**必回读父容器**核对自己那个对象的名字没被静默改名(细节见 `adapters.md`)。名字不对就立刻删掉自己刚建的、改去正本。
|
|
51
|
+
|
|
52
|
+
### 5. 判写权:经验判定,不查权限表
|
|
53
|
+
|
|
54
|
+
🔴 **不要靠"在权限列表里找到自己"。** 实测该类接口可能不返回稳定人标识、返回的是显示名(与认证身份名不相等)、且某些授权类型根本不出现在结果里——这个判据会退化成常量。
|
|
55
|
+
|
|
56
|
+
改成:**写一次、回读**。读到 = `writable`;被拒 = `read-only`(只召回不沉淀,并如实告诉用户);判不了 = `write-unverified`(允许写但每次回读,回读失败即整批作废)。
|
|
57
|
+
|
|
58
|
+
🔴 **写权只在绑定时探一次,结果存进绑定档案。** 实测这一步约 15s,绝不能每次 Boot 都跑。
|
|
59
|
+
|
|
60
|
+
### 6. 盘点授权 scope
|
|
61
|
+
|
|
62
|
+
某些渠道读取受行为授权门控,缺的当场告诉用户怎么补,不要留到运行时才发现。
|
|
63
|
+
|
|
64
|
+
## 四、绑定档案
|
|
65
|
+
|
|
66
|
+
绑定结果存在**运行时身份自己持有**的一个对象里,内容至少包括:
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
boundIdentity: <运行时身份 + 组织归属> # Boot 时比对,变了就停
|
|
70
|
+
adapter: dingtalk-doc | local-md | ...
|
|
71
|
+
roots:
|
|
72
|
+
person: { locator: <适配器自解释的定位符>, enabled: true, writeState: writable }
|
|
73
|
+
conversation: { locator: <...>, enabled: true, writeState: writable }
|
|
74
|
+
coldStartMode: on-demand | operator-batch
|
|
75
|
+
boundAt: <绝对时间>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
🔴 **两个根各自独立**:可以只开人不开会话,可以放在不同位置,一个坏了另一个照跑。
|
|
79
|
+
|
|
80
|
+
🔴 **绑定档案的标识必须带 Agent 标识。** 一个运行时身份挂多个 Agent 是常规配置;不带的话 B 会覆盖 A 的绑定,A 下次 Boot 发现不符就停链,而实际根本没人动过。
|
|
81
|
+
|
|
82
|
+
🔴 **落点定位符只存在绑定档案里,不进 Skill 源码、不进档案正文。**
|
|
83
|
+
|
|
84
|
+
## 五、Boot:预算 ≤3 次往返
|
|
85
|
+
|
|
86
|
+
1. 取当前身份(比对 `boundIdentity`,不一致 → 停链报人)
|
|
87
|
+
2. 用受信配置里的 seed 一跳定位绑定档案
|
|
88
|
+
3. 读绑定档案,解出两个根与 `writeState`
|
|
89
|
+
|
|
90
|
+
**就这三步。** 两个根的存在性校验推迟到本 Session 第一次真要写它时;写权不重探(第 5 步已存)。**同一 Session 内结果缓存,后续消息零往返。**
|
|
91
|
+
|
|
92
|
+
seed 缺失时才走全量兜底,走完必须**把 seed 补回受信配置**,别让兜底变成常态路径。
|
|
93
|
+
|
|
94
|
+
## 六、身份漂移
|
|
95
|
+
|
|
96
|
+
`boundIdentity` 与当前身份不一致时:
|
|
97
|
+
|
|
98
|
+
- **停止一切写入**,不自动改绑、不搜同名位置替换
|
|
99
|
+
- 明确告诉用户:档案是用旧身份建的、旧身份是所有者、现在这个身份可能读不到也删不掉
|
|
100
|
+
- 由用户决定:重新绑定(新建一份)还是先把旧档案的所有权处理掉
|
|
101
|
+
|
|
102
|
+
🔴 这正是"稳定身份"要求的落点。它不再是装配时的准入 gate,而是**绑定时记录 + 每次 Boot 检测**——身份不稳的 Agent 会在第一次漂移时明确失败并说清楚,而不是静默写进一个没人能访问的地方。
|
|
103
|
+
|
|
104
|
+
## 七、解绑与退出
|
|
105
|
+
|
|
106
|
+
用户要关掉时:
|
|
107
|
+
|
|
108
|
+
1. 转 `paused`,**立刻停止一切写入**,本 Session 生效
|
|
109
|
+
2. 已落盘内容**不自动删除**——那是用户的数据。明确告诉他在哪、可以自己删或让你删
|
|
110
|
+
3. 要彻底移除能力需要走一次受控发布;发布前最后一次运行在落点顶部留一行「本档案由 <Agent> 维护至 <日期>,此后不再更新」
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# 冷启动 —— 从零把人和群建起来(两种模式,用户选)
|
|
2
|
+
|
|
3
|
+
装配时**必须让用户明确选一种**并写进本体 `AGENTS.md`,不要替用户默认。两种模式的收敛判据相同、代价完全不同。
|
|
4
|
+
|
|
5
|
+
## 收敛判据(两种模式共用)
|
|
6
|
+
|
|
7
|
+
🔴 **数据派生的 backlog,不是"今天刷没刷"**:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
某 subject 收敛 ⟺ watermark:(subject, channel) ≥ 该渠道最新一条源事件的 occurredAt
|
|
11
|
+
整体收敛 ⟺ backlog 为空
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**绝不用「今天跑没跑」当判据**——那是全体重刷模型:过午夜全体变旧 → 每晚全量重编 → 窗口装不下 → 永远跑不完、永远降级。
|
|
15
|
+
|
|
16
|
+
一拍 ≤N 个对象 + 软时限 + 断点续跑 + 按主键哈希桶分片,幂等,漏拍下一拍自愈。
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 模式 A:事件触发建档(默认推荐)
|
|
21
|
+
|
|
22
|
+
**只有真实发生一次"需要记住某人/某群"时才建档。** 不做任何批量扫描。
|
|
23
|
+
|
|
24
|
+
- Person:先建 `identity`(view) + `interaction`(raw)
|
|
25
|
+
- Conversation:先建 `charter`(view) + `chronicle`(raw)
|
|
26
|
+
- 同一对象无论用户采纳还是拒绝,**只提议一次**(账本进 A0 索引,按哈希桶分片)
|
|
27
|
+
|
|
28
|
+
**优点**:零启动成本;不碰无关的人和群;隐私面最小——只有真正打过交道的对象才进档案。
|
|
29
|
+
**代价**:档案密度靠时间长;第一个月 Agent "认识的人"很少;无法回答"我们团队谁在做 X"这类需要全局视野的问题。
|
|
30
|
+
|
|
31
|
+
**适合**:绝大多数场景,尤其是隐私敏感、对象数不确定、或刚上线想先看效果的。
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 模式 B:Operator 批量追平
|
|
36
|
+
|
|
37
|
+
由 operator 在**专门会话**里手动跑到 backlog 清零,**不塞进消息触发路径**(重活会占几分钟、挤掉正常回复)。
|
|
38
|
+
|
|
39
|
+
**群可以全量建索引**——这条成本低且安全:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
dws chat group list-all --limit 100 -f json # while hasMore 跟 nextCursor 翻完
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
一行一个群:群名 / `groupType` / `memberCount` / `myRole`。**不读一条消息、不翻一页成员**,只建索引不建档。真正建档仍按需触发。
|
|
46
|
+
|
|
47
|
+
**人没有等价路径**:`list-all-conversations` 的 `--cursor` 是死参数、硬顶 100 条,**单聊无法全量枚举**。所以"人"的批量追平只能:
|
|
48
|
+
1. 从已建索引的群里取成员并集(受 500 人/页与"首页全是普通成员"的限制),或
|
|
49
|
+
2. 从通讯录按部门取人(`dws contact` 系列)
|
|
50
|
+
|
|
51
|
+
两条都会建出**大量没打过交道的人的空档案**——这正是模式 B 的主要代价。
|
|
52
|
+
|
|
53
|
+
**优点**:一次建齐,能回答全局性问题("谁在做 X""这事在哪个群讨论")。
|
|
54
|
+
**代价**:
|
|
55
|
+
- 隐私面骤然放大:给没交互过的人建档,本人未必知情——**A1 内容一律不许在此模式下产生**,批量建出来的档案只能含 A3 中性事实
|
|
56
|
+
- 成本高:todo 是 N+1(255 条 = 255 次 get),大群成员翻页贵
|
|
57
|
+
- 容易踩限流与 PAT 门
|
|
58
|
+
- 建出的空档案会稀释索引,让"我认识谁"变得没有信号
|
|
59
|
+
|
|
60
|
+
**适合**:对象集合明确且有限(如固定的一个团队)、已获得组织层面授权、且确实需要全局视野的场景。
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 两种模式都必须遵守
|
|
65
|
+
|
|
66
|
+
- 建档前判存在**只认列父目录并翻完页**;`create` 后**必回读父目录**核对 `name` 不带 `(1)`。
|
|
67
|
+
- 读失败一律记欠账、这拍跳过,**宁可漏一拍、绝不建重复**。
|
|
68
|
+
- 跨组织群"读到 0 条"与"真的没消息"同形 → 记欠账,不写"没有"。
|
|
69
|
+
- 遇到 `exit=4`(PAT 门)→ 停止本轮采集并报人,**不要归进"未配置"静默关能力**。
|
|
70
|
+
- 建档后抽查两三个对象:页都在、内容对得上、索引那行更新了,才算这批真成。
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# 配置粘合 —— 用户配的存储地址,怎么到达 Skill
|
|
2
|
+
|
|
3
|
+
Skill 里零地址(它是共享的、随版本发布给所有 Agent)。那用户配的那个"固定存储地址"落在哪、运行时怎么被 Skill 读到?这一层是 dta 的既有机制,本篇把 Skill 跟它粘上。
|
|
4
|
+
|
|
5
|
+
## 一、三种配置,三个落点(别混)
|
|
6
|
+
|
|
7
|
+
| 配置 | 例子 | 该落哪 | 谁能改 | 进不进 git |
|
|
8
|
+
|---|---|---|---|---|
|
|
9
|
+
| **解析算法 + 约定键名** | "先读 seed、seed 缺失走兜底"、`AGENT-MEMORY-PLACEMENT·<Agent名>` | **Skill 源码** | 发版 | 是(Skill 仓库) |
|
|
10
|
+
| **绑定档案的地址**(一篇 adoc,正文里记 narrative root、registry baseId) | `dingtalk-doc:<绑定档案adoc节点>` | **`agent.bindings.json` 的 `memory` 字段** + 本体 `AGENTS.md` 的 seed 行 | Agent owner,走受控发布 | 是(Agent 仓库) |
|
|
11
|
+
| **这次用谁的身份 / 密钥** | DWS profile、expectedUserId | **环境变量 / 受信运行时注入** | 部署环境 | 否 |
|
|
12
|
+
|
|
13
|
+
🔴 **地址不进 Skill 源码**(共享 + 版本化,写死就绑死所有 Agent)。**身份/密钥不进 git**(部署相关、敏感)。中间那层"这个 Agent 存哪"才是用户配置,落在 **Agent 自己的 `agent.bindings.json` + `AGENTS.md`**。
|
|
14
|
+
|
|
15
|
+
## 二、dta 已有的粘合机制(不用新造)
|
|
16
|
+
|
|
17
|
+
dta `bootstrap` 有一条 `select()` 优先级链(`src/bootstrap.ts`),把配置从多个来源解析成运行时 Session context:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
命令行 flag > agent.bindings.json > 环境变量 > workspace mount > 约定默认
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
存储字段的格式就是 **`provider:reference`**,`agent-bindings.ts` 校验 provider 白名单:
|
|
24
|
+
|
|
25
|
+
```jsonc
|
|
26
|
+
// agent.bindings.json
|
|
27
|
+
{
|
|
28
|
+
"$schema": "dingtalk-agent/agent-bindings@1",
|
|
29
|
+
"memory": "dingtalk-doc:https://alidocs.dingtalk.com/i/nodes/<绑定档案adoc节点>",
|
|
30
|
+
"authority": { "profile": "<dws profile>", "expectedUserId": "<uid>" }
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
🔴 **`memory` 必须指向一篇 adoc 文档,不能指向文件夹。** `bootstrap.ts:214-218` 对 `dingtalk-doc:` 的 ref 做 `docInfo`,强制 `contentType==ALIDOC && extension==adoc`,否则抛 `dingtalk-doc 只接受在线文档 adoc`。所以用户**不能**把"画像落点文件夹的 URL"填进 `memory`——那会直接 boot 崩。正确做法:`memory` 指向一篇 **adoc 绑定档案(Placement Record)**,narrative root 文件夹、registry baseId、writeState、boundIdentity 都写在这篇档案的**正文**里;Skill 的 Boot 读这篇 adoc、从正文解出真实落点。
|
|
35
|
+
|
|
36
|
+
- `dingtalk-doc:<adoc-node-or-url>` → bootstrap 通过 DWS 拉成**隐藏只读快照**投影进 Session(`bootstrap.ts:5,220`)。
|
|
37
|
+
- 环境变量等价物:`DTA_MEMORY_STORAGE=dingtalk-doc:<node>`(`bootstrap.ts:84`)——这就是你说的"走环境变量",适合每部署不同、不想进 git 的值。
|
|
38
|
+
- `authority.profile` / `expectedUserId` 也走同一条链,对应 `DTA_DWS_PROFILE` / `DTA_EXPECTED_USER_ID`。
|
|
39
|
+
|
|
40
|
+
**所以"用户配的存储地址放哪"有权威答案**:`agent.bindings.json#memory`(或 `DTA_MEMORY_STORAGE`),格式 `dingtalk-doc:<绑定档案>`。这不是本 Skill 发明的,是 dta 的存储路由。
|
|
41
|
+
|
|
42
|
+
## 三、本 Skill 怎么接上它
|
|
43
|
+
|
|
44
|
+
本 Skill 的"绑定档案"(`binding.md` 的 Placement Record,记 narrative root + registry baseId + writeState + boundIdentity)**就是 `memory` 指向的那个对象**。链路:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
用户配 memory = dingtalk-doc:<绑定档案节点> (或 DTA_MEMORY_STORAGE 环境变量)
|
|
48
|
+
│ dta bootstrap 按 select() 解析
|
|
49
|
+
▼
|
|
50
|
+
Session context.memory = 绑定档案节点 (dingtalk-doc 拉成只读快照)
|
|
51
|
+
│ 本 Skill 的 Boot(binding.md)读它
|
|
52
|
+
▼
|
|
53
|
+
解出 narrative root + registry baseId → 校验 boundIdentity → 干活
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**seed 的双写**:绑定档案节点既写进 `agent.bindings.json#memory`(机器读、bootstrap 解析),也在 `AGENTS.md` 留一行人可读的 seed(`assembly-guidance.md`)。两者指同一个节点;前者是 dta 的粘合通道,后者是给人看的、也是本 Skill Boot 兜底的入口。
|
|
57
|
+
|
|
58
|
+
## 三·补丁、我已经有一个文件夹想当落点,到底怎么配?
|
|
59
|
+
|
|
60
|
+
用户最常见的误区:把文件夹 URL 填进 `memory`。**别这么做,会 boot 崩**(§二红字)。文件夹不是 `memory` 该指的对象,一篇 adoc 绑定档案才是。正确五步:
|
|
61
|
+
|
|
62
|
+
1. 在你的文件夹里(或任意可写位置)**建一篇 adoc 文档**当绑定档案,例如标题 `AGENT-MEMORY-PLACEMENT·<Agent名>`。
|
|
63
|
+
2. 这篇 adoc 正文写:`narrativeRoot: <文件夹节点>`、`registryBase: <aitable baseId>`、`writeState: writable`、`boundIdentity: <运行时身份+组织>`。
|
|
64
|
+
3. 建 registry 的 aitable 三表(conversation-index / person-index / watermark),把 baseId 填回上一步。
|
|
65
|
+
4. 把 `memory` 设成 `dingtalk-doc:<这篇 adoc 的节点>`(**不是文件夹节点**),profile/expectedUserId 填 authority。
|
|
66
|
+
5. 同一个 adoc 节点在 `AGENTS.md` 留一行人可读 seed。
|
|
67
|
+
|
|
68
|
+
🔴 **第 1–3 步目前没有 Agent 能自主走通的实现**(见"完成度":首次绑定环)。今天要交付,这五步是**开发者/运维手工**做(可复用生产上那套 build 脚本),做完之后单 Agent 在 doc+aitable 的读写是真实证过的。"填个文件夹即用"不成立——这是本能力当前最大的缺口,不是设计意图,是实现没到。
|
|
69
|
+
|
|
70
|
+
## 四、三种用户配置姿势(用户可选)
|
|
71
|
+
|
|
72
|
+
| 姿势 | 怎么配 | 适合 |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| **A. bindings 文件** | 在 `agent.bindings.json` 写 `memory: "dingtalk-doc:<node>"` | 标准装配,随 Agent 仓库版本化、可评审 |
|
|
75
|
+
| **B. 环境变量** | 部署时设 `DTA_MEMORY_STORAGE=dingtalk-doc:<node>` | 每部署不同、不进 git、或云端注入 |
|
|
76
|
+
| **C. 让 Agent 自建** | 都不配 → 用户首次说"记住…"时 Skill 按 `binding.md` 自建绑定档案,再把节点回填进 A 或 B | 用户不想手工找节点、交给 Agent |
|
|
77
|
+
|
|
78
|
+
🔴 **无论哪种,地址都不在 Skill 里**。A/B 是"用户已有固定地址"的两条路(正是你说的"直接指定 vs 环境变量");C 是"没有地址、让 Agent 生成再回填"。
|
|
79
|
+
|
|
80
|
+
## 五、dta 装配时该做的粘合检查(compose 侧)
|
|
81
|
+
|
|
82
|
+
装配 / 改造带本 Skill 的 Agent 时,`dta-agent-compose` 应校验这条粘合是否接通(目前是 prose,见"完成度"):
|
|
83
|
+
|
|
84
|
+
1. `agent.bindings.json#memory` 或 `DTA_MEMORY_STORAGE` 是否为合法 `dingtalk-doc:` / `aitable:` provider ref。
|
|
85
|
+
2. 该 ref 指向的节点是否 `doc info` 可回读(用**运行时身份**,不是开发者身份)。
|
|
86
|
+
3. `AGENTS.md` 的 seed 行与 `memory` 是否指同一节点(不一致 = 粘合断裂)。
|
|
87
|
+
4. registry 的 baseId 是否 `table get` 可回读、字段是否对账通过。
|
|
88
|
+
|
|
89
|
+
任一不通 → 装配结论 `partial`,明确告诉用户断在哪一环,而不是让 Agent 上线后 Boot 静默失败。
|