better-dsh 0.0.0 → 0.2.3

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.
Files changed (142) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +294 -4
  3. package/control-prompt.md +37 -0
  4. package/cordis.patch.yml +53 -0
  5. package/docs/00_adr/0001-bridge-tool-layer-not-service-layer.md +14 -0
  6. package/docs/00_adr/0002-masking-is-presentation-only.md +15 -0
  7. package/docs/10_plans/A2A-messaging-channel-test-archive.md +256 -0
  8. package/docs/10_plans/code-mode-vs-rlm-ipython-comparison.md +137 -0
  9. package/docs/10_plans/dashr-blueprint-review.md +201 -0
  10. package/docs/10_plans/dashr-blueprint.md +561 -0
  11. package/docs/10_plans/dashr-compaction-window-and-archive.md +307 -0
  12. package/docs/10_plans/dashr-profile-layer-feasibility.md +367 -0
  13. package/docs/10_plans/dashr-sandbox-escalation-semantics-gap.md +171 -0
  14. package/docs/10_plans/dashr-security-sandbox-analysis.md +187 -0
  15. package/docs/10_plans/dashr-surface-invariant-and-omp-imports.md +97 -0
  16. package/docs/10_plans/ipython-kernel-interactive-interface-test-report.md +152 -0
  17. package/docs/10_plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft.md +146 -0
  18. package/docs/10_plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft_v3.md +50 -0
  19. package/docs/10_plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft_v4.md +79 -0
  20. package/docs/10_plans/kernel-refactoring/Dash-vs-PrimeAgent-systemprompt-toolcatalog-comparison.md +138 -0
  21. package/docs/10_plans/kernel-refactoring/RLM-system-prompt-injection-gap-report.md +161 -0
  22. package/docs/10_plans/kernel-refactoring/V0.1.5-development-plan.md +109 -0
  23. package/docs/10_plans/kernel-refactoring/actinoable-surface-to-llm-in-agent-runtime_dsh.md +50 -0
  24. package/docs/10_plans/kernel-refactoring/actinoable-surface-to-llm-in-agent-runtime_prime.md +113 -0
  25. package/docs/10_plans/recallable-compaction.md +147 -0
  26. package/docs/10_plans/spike-tag-repro.mjs +102 -0
  27. package/docs/10_plans/upstream-analysis.md +128 -0
  28. package/docs/50_test-reports/REPL-/345/267/245/345/205/267/350/260/203/347/224/250-/346/210/252/346/226/255/350/257/212/346/226/255.md +110 -0
  29. package/docs/50_test-reports/kernel-provisioning.md +44 -0
  30. package/docs/50_test-reports/repl-kernel-provisioning-test-report.md +87 -0
  31. package/docs/50_test-reports/upstream-dsh-0.1.2-alpha.5-local-test-report.md +81 -0
  32. package/docs/50_test-reports/upstream-dsh-0.1.2-alpha.5-report.md +93 -0
  33. package/docs/50_test-reports/v0.1.8-improved-/345/256/236/346/265/213/346/212/245/345/221/212.md +142 -0
  34. package/docs/50_test-reports/v0.1.8-/345/256/236/346/265/213/346/212/245/345/221/212.md +193 -0
  35. package/docs/50_test-reports/v0.1.8b-/345/256/236/346/265/213/346/212/245/345/221/212.md +96 -0
  36. package/docs/50_test-reports/v0.1.8c-/345/256/236/346/265/213/346/212/245/345/221/212.md +127 -0
  37. package/docs/50_test-reports/v0.1.8d-/345/256/236/346/265/213/346/212/245/345/221/212.md +150 -0
  38. package/docs/50_test-reports/v0.1.8d_artifacts/README.md +138 -0
  39. package/docs/50_test-reports/v0.1.8d_artifacts/code-mode-repl-only.observation.md +74 -0
  40. package/docs/50_test-reports/v0.1.8d_artifacts/dsh-session-session-4a293388-9ae1-474b-87a0-9e17bb556d94.jsonl +3890 -0
  41. package/docs/50_test-reports/v0.1.8d_artifacts/dsh-session-session-4a293388-9ae1-474b-87a0-9e17bb556d94.w-sample-0435.jsonl +544 -0
  42. package/docs/50_test-reports/v0.1.8d_artifacts/functions.json +592 -0
  43. package/docs/50_test-reports/v0.1.8d_artifacts/skills-catalog.snapshot.md +30 -0
  44. package/docs/50_test-reports/v0.1.8d_artifacts/tools-sdk.output-schemas.json +1236 -0
  45. package/docs/50_test-reports/v0.1.8d_artifacts/tools-sdk.python.txt +592 -0
  46. package/docs/50_test-reports/v0.1.8d_artifacts/tools-sdk.typescript.txt +516 -0
  47. package/docs/50_test-reports/v0.1.8d_artifacts/wire-vs-transcription.diff.md +54 -0
  48. package/docs/50_test-reports/v0.1.8e-/345/256/236/346/265/213/346/212/245/345/221/212.md +224 -0
  49. package/docs/50_test-reports/v0.1.9a-/345/256/236/346/265/213/346/212/245/345/221/212.md +168 -0
  50. package/docs/50_test-reports/v0.2.0b-/345/256/236/346/265/213/346/212/245/345/221/212.md +123 -0
  51. package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/Cargo.lock +7 -0
  52. package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/Cargo.toml +6 -0
  53. package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/src/bin/messy.rs +8 -0
  54. package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/src/main.rs +4 -0
  55. package/docs/50_test-reports/v0.2.0b_artifacts/hashline-probe.md +5 -0
  56. package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/Cargo.lock +7 -0
  57. package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/Cargo.toml +7 -0
  58. package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/build.rs +4 -0
  59. package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/src/main.rs +13 -0
  60. package/docs/50_test-reports/v0.2.1-/345/256/236/346/265/213/346/212/245/345/221/212.md +110 -0
  61. package/docs/50_test-reports/v0.2.1b-/345/256/236/346/265/213/346/212/245/345/221/212.md +86 -0
  62. package/docs/50_test-reports/v0.2.1c-/345/256/236/346/265/213/346/212/245/345/221/212.md +66 -0
  63. package/docs/50_test-reports/v0.2.1d-/345/256/236/346/265/213/346/212/245/345/221/212.md +67 -0
  64. package/docs/50_test-reports/v0.2.1e-P1-/345/256/236/346/265/213/346/212/245/345/221/212.md +136 -0
  65. package/docs/50_test-reports/v0.2.1ef-dev-audit-report.md +73 -0
  66. package/docs/50_test-reports/v0.2.1f-plugin-shipped-ui-patches/345/256/236/346/265/213/346/212/245/345/221/212.md +102 -0
  67. package/docs/60_exploration-and-research/cordis-research.md +350 -0
  68. package/docs/60_exploration-and-research/dsh-web-profile-package-map.md +186 -0
  69. package/docs/60_exploration-and-research/dsh-web-ui-slot-system-research.md +310 -0
  70. package/docs/60_exploration-and-research/dsh-webui-strip-boundary-research.md +300 -0
  71. package/docs/60_exploration-and-research/ios-chat-app-bridge-research.md +324 -0
  72. package/docs/60_exploration-and-research/web-frontend-composability-research.md +191 -0
  73. package/docs/REPL-/345/267/245/345/205/267/350/260/203/347/224/250-/346/210/252/346/226/255/350/257/212/346/226/255.md +110 -0
  74. package/docs/adr/0001-bridge-tool-layer-not-service-layer.md +14 -0
  75. package/docs/adr/0002-masking-is-presentation-only.md +15 -0
  76. package/docs/distro-blueprint.md +81 -0
  77. package/docs/dsh-webUI-with-rlm-mode.png +0 -0
  78. package/docs/plans/A2A-messaging-channel-test-archive.md +256 -0
  79. package/docs/plans/code-mode-vs-rlm-ipython-comparison.md +137 -0
  80. package/docs/plans/dashr-blueprint-review.md +201 -0
  81. package/docs/plans/dashr-blueprint.md +561 -0
  82. package/docs/plans/dashr-compaction-window-and-archive.md +307 -0
  83. package/docs/plans/dashr-profile-layer-feasibility.md +367 -0
  84. package/docs/plans/dashr-sandbox-escalation-semantics-gap.md +171 -0
  85. package/docs/plans/dashr-security-sandbox-analysis.md +187 -0
  86. package/docs/plans/dashr-surface-invariant-and-omp-imports.md +97 -0
  87. package/docs/plans/ipython-kernel-interactive-interface-test-report.md +152 -0
  88. package/docs/plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft.md +146 -0
  89. package/docs/plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft_v3.md +50 -0
  90. package/docs/plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft_v4.md +79 -0
  91. package/docs/plans/kernel-refactoring/Dash-vs-PrimeAgent-systemprompt-toolcatalog-comparison.md +138 -0
  92. package/docs/plans/kernel-refactoring/RLM-system-prompt-injection-gap-report.md +161 -0
  93. package/docs/plans/kernel-refactoring/V0.1.5-development-plan.md +109 -0
  94. package/docs/plans/kernel-refactoring/actinoable-surface-to-llm-in-agent-runtime_dsh.md +50 -0
  95. package/docs/plans/kernel-refactoring/actinoable-surface-to-llm-in-agent-runtime_prime.md +113 -0
  96. package/docs/plans/recallable-compaction.md +147 -0
  97. package/docs/plans/spike-tag-repro.mjs +102 -0
  98. package/docs/plans/upstream-analysis.md +128 -0
  99. package/docs/repositioning-and-rebranding.md +102 -0
  100. package/docs/v0.1.8-improved-/345/256/236/346/265/213/346/212/245/345/221/212.md +142 -0
  101. package/docs/v0.1.8-/345/256/236/346/265/213/346/212/245/345/221/212.md +193 -0
  102. package/docs/v0.1.8b-/345/256/236/346/265/213/346/212/245/345/221/212.md +96 -0
  103. package/docs/v0.1.8c-/345/256/236/346/265/213/346/212/245/345/221/212.md +127 -0
  104. package/docs/v0.1.8d-/345/256/236/346/265/213/346/212/245/345/221/212.md +150 -0
  105. package/docs/v0.1.8d_artifacts/README.md +138 -0
  106. package/docs/v0.1.8d_artifacts/code-mode-repl-only.observation.md +74 -0
  107. package/docs/v0.1.8d_artifacts/dsh-session-session-4a293388-9ae1-474b-87a0-9e17bb556d94.jsonl +3890 -0
  108. package/docs/v0.1.8d_artifacts/dsh-session-session-4a293388-9ae1-474b-87a0-9e17bb556d94.w-sample-0435.jsonl +544 -0
  109. package/docs/v0.1.8d_artifacts/functions.json +592 -0
  110. package/docs/v0.1.8d_artifacts/skills-catalog.snapshot.md +30 -0
  111. package/docs/v0.1.8d_artifacts/tools-sdk.output-schemas.json +1236 -0
  112. package/docs/v0.1.8d_artifacts/tools-sdk.python.txt +592 -0
  113. package/docs/v0.1.8d_artifacts/tools-sdk.typescript.txt +516 -0
  114. package/docs/v0.1.8d_artifacts/wire-vs-transcription.diff.md +54 -0
  115. package/docs/v0.1.8e-/345/256/236/346/265/213/346/212/245/345/221/212.md +224 -0
  116. package/docs/v0.1.9a-/345/256/236/346/265/213/346/212/245/345/221/212.md +168 -0
  117. package/docs/v0.2.0b-/345/256/236/346/265/213/346/212/245/345/221/212.md +123 -0
  118. package/docs/v0.2.0b_artifacts/f2probe/Cargo.lock +7 -0
  119. package/docs/v0.2.0b_artifacts/f2probe/Cargo.toml +6 -0
  120. package/docs/v0.2.0b_artifacts/f2probe/src/bin/messy.rs +8 -0
  121. package/docs/v0.2.0b_artifacts/f2probe/src/main.rs +4 -0
  122. package/docs/v0.2.0b_artifacts/hashline-probe.md +5 -0
  123. package/docs/v0.2.0b_artifacts/slowprobe/Cargo.lock +7 -0
  124. package/docs/v0.2.0b_artifacts/slowprobe/Cargo.toml +7 -0
  125. package/docs/v0.2.0b_artifacts/slowprobe/build.rs +4 -0
  126. package/docs/v0.2.0b_artifacts/slowprobe/src/main.rs +13 -0
  127. package/docs/v0.2.1-/345/256/236/346/265/213/346/212/245/345/221/212.md +110 -0
  128. package/docs/v0.2.1b-/345/256/236/346/265/213/346/212/245/345/221/212.md +86 -0
  129. package/docs/v0.2.1c-/345/256/236/346/265/213/346/212/245/345/221/212.md +66 -0
  130. package/lib/client/index.js +473 -0
  131. package/lib/index.d.ts +736 -0
  132. package/lib/index.js +11518 -0
  133. package/lib/kernel-env-hxaihi9C.js +195 -0
  134. package/lib/kernel-env.d.ts +80 -0
  135. package/lib/kernel-env.js +3 -0
  136. package/lib/py-sdk-BCaOGYz7.d.ts +125 -0
  137. package/lib/py-sdk-CbgYiX8O.js +691 -0
  138. package/lib/py-sdk.d.ts +2 -0
  139. package/lib/py-sdk.js +3 -0
  140. package/package.json +325 -4
  141. package/scripts/kernel-provision.mjs +35 -0
  142. package/index.js +0 -3
@@ -0,0 +1,113 @@
1
+ # Self Survey of `dsh` (DeepSeek Harness Agent) and Prime Agent - Actionable Surface to LLM in Agent runtime
2
+
3
+
4
+ - Actionables: tools, toolset(s), skills, bash, executable code scripts (e.g. python ...)
5
+ - Surface to LLM: the text/prompts exposed to LLM during agent runtime
6
+
7
+
8
+
9
+
10
+ ## Prime Agent(第一人称:Prime Agent 运行时自述)
11
+
12
+ ### 地基 — 我"调用工具"的 wire 形态(function calling)
13
+
14
+ - 和 dsh 是同一个 function-calling JSON:`Function: { name, arguments }` —— 只有两个 element,`name` 平级、无 `tools.` 之类前缀命名空间。
15
+ - **但我的平级工具枚举清单里只有一个名字:`ipython`。** 所以 wire 层固定是:`name` 只有 `"ipython"` 一种取值;`arguments` 只有一个必填键 `code`(字符串)。没有别的字段。
16
+
17
+ - 我发出的真实 wire 原文(从我自己这个 session 的日志里抄的,一字未改):
18
+
19
+ ```json
20
+ {"type": "toolCall", "id": "call_00_wf7zeFy6KRIXEaeC5zZS3896",
21
+ "name": "ipython",
22
+ "arguments": {"code": "%%bash\nls -la /home/u1/workspaces/dashr/dev/kernel-refactoring/"}}
23
+ ```
24
+
25
+ - 客户端拿 `name: "ipython"` 定位唯一执行器,把 `arguments.code` 整段交给**常驻 IPython kernel** 执行,把我 print / 显示出来的 stdout 文本塞回 toolResult(原文照抄,stderr 与 kernel 状态另列):
26
+
27
+ ```json
28
+ {"role": "toolResult", "toolCallId": "call_00_wf7zeFy6KRIXEaeC5zZS3896",
29
+ "toolName": "ipython",
30
+ "content": [{"type": "text", "text": "<cell 的 stdout>"}],
31
+ "details": {"durationMs": 472, "status": "ok", "stdout": "...", "stderr": "", "kernelRestarted": false},
32
+ "isError": false}
33
+ ```
34
+
35
+ - **`await` 不在 wire 层,在 `code` 层**——`await` 是 Python 语法,发生在我写进 `arguments.code` 的那段程序里面。完整的语法链就四步:
36
+
37
+ 1. 我要做一个动作 → 想好一段 **Python 程序**(一句话也是一个程序);
38
+ 2. 把它写进 `arguments.code`(可以是 `await 技能函数(...)`、`%%bash` cell、`rlm(...)`、或任意 Python);
39
+ 3. 发出唯一形态的 JSON:`Function: { name: "ipython", arguments: { code: "<那个程序>" } }`;
40
+ 4. kernel 跑完,stdout 原样作为 toolResult 回到我的上下文,我再决定下一轮。
41
+
42
+ - wire 示例(全部是同一个工具名,差别只在 `code` 里):
43
+
44
+ ```json
45
+ // 例子1:调用联网技能 —— await 发生在 code 里
46
+ Function: { name: "ipython", arguments: {"code": "await websearch('prime agent')"} }
47
+
48
+ // 例子2:shell —— %%bash 必须是 code 的第一行
49
+ Function: { name: "ipython", arguments: {"code": "%%bash\nls -la"} }
50
+
51
+ // 例子3:子代理 admission —— 返回的是句柄,不是答案
52
+ Function: { name: "ipython", arguments: {"code": "h = await rlm('sub-task', name='worker')"} }
53
+ ```
54
+
55
+ ### Actionables — 全部经 `ipython` 的 `code` 触达(没有第二层工具枚举)
56
+
57
+ wire 层是平的、只有一个名字;dsh 那 29 个平级工具对应到我这边的全部"动作清单",都是我写进 `code` 里的 Python 对象:
58
+
59
+ - **内核全局预导入的 9 个 python skill 模块**,调用形态统一是 `await <模块>.<函数>(...)`,返回值可绑定变量、可组合:
60
+ `agent_message`(send / list_agents)、`agent_observe`、`attach_image`、`compact`、`edit`、`goal`、`refine`(run)、`rlm_heartbeat`、`websearch`
61
+ - **内核全局 `rlm`**:`await rlm('sub-task')`(非阻塞 admission,返回 rlm_child_id/name/session_dir/model,永不返回答案)、`rlm.list_subagents()`、`rlm.delete_subagent()`、`rlm.find_models()`、`rlm.get_harness_state()`、`rlm.harness.*`(memory / prompt_note / skill / subagent-spec 四类 CRUD,默认 session 本地,`global_=True` 才跨会话)
62
+ - **`%%bash` cell**(必须是 `code` 的第一行)——shell、项目命令、CLI 全走这里;**没有独立的 bash 工具名**
63
+ - **任意 Python**:stdlib 读/写/搜索文件、编排进程、`uv pip install` 装包、`%cd` / `os.environ` 管内核状态
64
+ - **每个 skill 的 shell CLI**:`<skill> ...`(在 %%bash 里跑)
65
+ - **markdown skills**(项目 `.agents/skills` 向上收集 + 用户级 + 捆绑 dist):不是 callable 绑定,用 ipython 读它们的 SKILL.md 学 API
66
+ - **没有的东西**:read / write / glob / grep / todo_write / subagent / send_message……这些名字在我的 wire 枚举里**不存在**;对应能力 = code 里的 Python 或 skill 函数。这条不是靠运行时报错兜底,是 schema 面根本不暴露。
67
+
68
+ ### Surface to LLM — 我暴露给模型的文本
69
+
70
+ - 系统提示正文(每次请求动态组装,顺序固定):code-first 人设开篇 → IPYTHON_CONTROL_PROMPT(长驻 notebook、%%bash 纪律、状态持久、await 调用、"do not invent non-native wrappers" 等反模式明令)→ RLM-native call contract → delegation doctrine(子代理 admission/消息/观察拓扑)→ continual harness 状态菜单(prompt/memory/skill/subagent 计数)→ 项目 AGENTS.md 链(Project Context)→ 会话变量:cwd、conversation log 路径、recursive agent depth、预装 Python 包清单
71
+ - 工具 schema:只有 `ipython` 一个(`name` + `parameters:{code: string}`,description 写在 schema 里,正文不重复列工具目录)
72
+ - skills catalog:`<available_skills>` XML 块拼在正文里(当前 session 26 条:name / type / python_import / description / location,措辞是 kernel 形态)
73
+ - SKILL.md 本体:不注入;我用 ipython 读文件学 API
74
+ - 对话历史 + 压缩(compact skill;单条 cell 输出截断上限默认 2000 行 / 50KB)
75
+ - kernel 事件通知:`<ipython_kernel_reset>` 标记、interrupted cell 的 wait/kill 选择提示
76
+
77
+
78
+ ### 组合性 — 预绑定的名字是「Python 类型化的内置工具」,可当一等对象组合(本 session 已实测)
79
+
80
+ - 术语(建议 Dash prompt 也采用):**Python 类型化的内置工具(typed Python built-in tools)**。它们不是 wire 层的 function call,而是 kernel 全局命名空间里的 typed Python 对象:
81
+ - `edit` = 可调用模块,成员 `run(path, old_str, new_str) -> str` 是 async 函数,抛 `FileNotFoundError` / `ValueError`
82
+ - `rlm` = 可调用模块(`rlm(...)` 即 spawn),成员 run / find_models / list_subagents / delete_subagent / harness / get_harness_state
83
+ - 实现层:`edit` 是进程内 async Python 函数(`Path.read_text/write_text` 直接 syscall),零 shell 零 subprocess
84
+ - 实测组合模式(全部真实执行):
85
+
86
+ | 组合模式 | 写法 | 结果 |
87
+ |---|---|---|
88
+ | 条件 | `if "x" in Path(p).read_text(): await edit(...)` | ✅ |
89
+ | 异常 | `except ValueError` / `except FileNotFoundError` | ✅ 类型化异常 |
90
+ | 重试 | for + try/except,失败换更宽 old_str | ✅ |
91
+ | 批量 / 并行 | for 收集返回值 / `asyncio.gather(*[edit(...)])` | ✅ |
92
+ | 对象化 | 存 dict、当参数传、自写 `safe_edit()` wrapper(加重试/降级策略) | ✅ |
93
+
94
+ - 对照:平级 function call(DSH/Hermes)里组合只能发生在模型脑内、跨多轮往返、非确定;这里组合发生在确定性 Python 代码里,一个 cell 一轮往返。
95
+ - **反面证据(诚实记录)**:本 session 的 system prompt 关于组合只有一句 "composed into program logic just like any other call",无例子、未说明是 typed 对象 → 本 session 模型未自发组合过一次(写文件全用裸 Python)。结论:**不写清楚,模型就不会。**(此为 prompt 缺口,补丁见下节)
96
+
97
+ ### 状态管理 — cell 化之后的「可回查状态机」(本 session 已实测)
98
+
99
+ 三种后台句柄,全部跨 cell 存活于 kernel 命名空间、可回查、可控制:
100
+
101
+ | 句柄 | 创建 | 回查状态 | 控制 |
102
+ |---|---|---|---|
103
+ | asyncio Task | `t = asyncio.create_task(coro())` | `t.done()` / `t.result()` | `t.cancel()` |
104
+ | 子进程(Hermes job/PID 等价物) | `p = subprocess.Popen(...)` | `p.poll()` / `p.pid` | `p.kill()` / `p.wait()` |
105
+ | rlm 子代理 | `h = await rlm('sub-task')` | `rlm.list_subagents()` → `status` 字段 | `rlm.delete_subagent(h)` |
106
+
107
+ - `await` 是"等"的语法;`asyncio.create_task` / Popen 是"不等"的句柄 —— 本 session 的 prompt 只教了 await,没教 async 句柄,这是第二个 prompt 缺口。
108
+ - rlm 实测闭环:admission 立即返回 `RLMSpawnHandle(rlm_child_id, session_dir, model)` → `list_subagents()` 查 `status='completed'` → `delete_subagent` 回收 → 子代理回复经 agent_message **事件驱动送达**(实测:delete 之后消息仍到达,投递队列与句柄注册表解耦)。
109
+ - 长命令(`%%bash` curl 等):包进 create_task/Popen,句柄落 kernel 命名空间,下一 cell 回查;kernel 死则句柄丢(进程还活,句柄没了)。
110
+
111
+ ### 建议 prompt 补丁(Prime upstream `IPYTHON_CONTROL_PROMPT` 与 Dash preset 共用)
112
+
113
+ > Pre-imported skill modules and the global `rlm` are typed Python objects, not wire-level tools: each exposes documented async functions and raises typed exceptions (e.g. `edit` raises `FileNotFoundError`/`ValueError`). Treat them as first-class values: compose them with conditionals, `try/except` on their typed errors, loops, `asyncio.gather` for parallelism, custom wrapper functions for retry/degradation policies, or storage in data structures. For background work, keep pollable handles in the kernel namespace: `asyncio.create_task` → `done()/result()/cancel()`; `subprocess.Popen` → `pid/poll()/kill()`; `rlm()` admission → `rlm.list_subagents()/delete_subagent()`, with results arriving asynchronously via `agent_message`. Prefer composing multiple tool calls inside one cell over issuing them one call per cell.
@@ -0,0 +1,147 @@
1
+ # Recallable Compaction(可回溯压缩)— 设计备忘
2
+
3
+ > 状态:设计备忘(**未实现**)· 2026-08-20 · 用户方向 + API 面 spike 验证通过
4
+ > 归属:`dashr/dev/`(gitignored 本地 scratch,不进公开仓库)
5
+ > 上游文档:`dev/dashr-compaction-window-and-archive.md`(Feature 2 压缩损失补偿的总纲;本文档是其「回溯通道」的落地设计,并回收其 §3 开放问题中已解决项)
6
+
7
+ ## 0. 背景:Context as variable 的四方向与本 gap
8
+
9
+ dashr 参考 Prime Agent 的三个核心特点(RLM 模式 / IPython REPL tool calling / Context as variable)中,前两个已完全实现;第三个目前只实现一半。完全体的四个方向:
10
+
11
+ | 方向 | 状态 |
12
+ |---|---|
13
+ | 1. 事件流过滤 | ✅ 已实现(`recencyWindowTokens`,Feature 1,v0.1.1+) |
14
+ | 2. 运行时变量存取 | ✅ 已实现(kernel 变量自由存取) |
15
+ | 3. **上下文压缩与回溯** | ❌ **未实现 ← 本文档** |
16
+ | 4. 广义上下文组件化 | ✅ 已实现(Continual Harness / `refine()`) |
17
+
18
+ 方向 3 的缺口:压缩后旧上下文被摘要 shadow,运行时模型无法直接读取原文(原文虽在 session 持久化文件中,但模型经资源工具查询繁琐且难定位)。Feature 2 原设计(强制归档 + 摘要尾部标记)解决「可恢复」,本文档补上「模型可寻址的回溯通道」与全部 API 面验证。
19
+
20
+ ## 1. Spike 结论(2026-08-20,Dash agent 实证 + 本机复验)
21
+
22
+ > 委托方式:Dash HTTP API(session `session-a2a61011`,`cwd=/home/u1/workspaces/dashr`,rlm-mode preset)。证据脚本 `dev/spike-tag-repro.mjs` 已随文档保存,本机复跑 exit 0、输出吻合。
23
+
24
+ | # | 事实 | 对设计的影响 |
25
+ |---|---|---|
26
+ | 1 | surface 是 **append-only + 深冻结** 的不可变投影;唯一写入口 `Session.append`;**不存在原地 patch 节点文本的 API** | 不能 mutate,只能 replace |
27
+ | 2 | 「改写单个节点」的官方正解 = 再 append 一个 `replace`,`start === end === 目标 seq`,新节点 shadow 旧节点 —— 这正是上游 compaction 改写历史所用的同一机制 | 挂 tag 的通道 ✓ |
28
+ | 3 | 新摘要节点 seq = **`result.summarySeq + 1`**(contractual adjacency;`commitCompactionBody` 源码确认:`compaction/summary` 事件后紧邻 `user/message` 替换节点,`= endSeq - 1`) | 摘要节点可精确定位 ✓ |
29
+ | 4 | 被 shadow 的原文**事后仍可读**:`session.events[shadowedSeqs]` 完整保留(append-only log 不删除,shadow 只是从 surface 投影隐去) | **归档不必在压缩前捕获** —— 顺序约束消失 |
30
+ | 5 | `user/message` 重写无限制(`assertToolResultRewrite` 只约束 `tool/result`) | 摘要文本可自由追加 tag 块 |
31
+ | 6 | 替换节点保留原 `source`(含 `compactionId`)时,下游 checkpoint 识别(`isCompactCheckpointSource`)不破 | tag 挂载无损 ✓ |
32
+
33
+ **关键修正**:原方案「归档先于压缩」(压缩前取到将被 shadow 的区间 → 归档 → 才允许压缩)不再需要。事实 #3+#4 意味着顺序可以是**先压缩、后归档、再挂 tag**——每一步只依赖已提交的事实,**tag 永不悬挂,无需任何补偿逻辑**。
34
+
35
+ ## 2. 设计:三步流程
36
+
37
+ 在 `compactIfNeeded`(与 `compactNow` 共用)的 `compactRegion` 返回之后:
38
+
39
+ 1. **读原文**(事后,从 append-only log):`result.shadowedSeqs` → `session.deriveEventMessage(session.events[seq])` → 分类 → 序列化。
40
+ 2. **写归档存储**:介质二选一(§2.2,待用户拍板)。
41
+ 3. **挂 tag**:单节点 `replace`(`start === end === result.summarySeq + 1`),把 tag 文本块追加到摘要内容尾部。
42
+
43
+ 归档失败时降级为现状行为(照常压缩、仅无 tag),**不阻塞压缩**(沿用旧 doc 原则)。
44
+
45
+ ### 2.1 分类(沿用旧 doc §2.2,纯机械非语义)
46
+
47
+ | bucket | 来源 | 说明 |
48
+ |---|---|---|
49
+ | `user_requests` | user 消息 | 用户指令原文 |
50
+ | `assistant_responses` | assistant 消息 | 正文 verbatim;thinking 块剥除与否见开放项 |
51
+ | `tool_results` | toolResult 消息 | 含 cell stdout |
52
+ | `file_ops` | 机械提取 | read/modified 列表 |
53
+
54
+ ### 2.2 归档介质(开放决策,二选一)
55
+
56
+ **方案 K — kernel 变量 + dill snapshot**(旧 doc 原方案):
57
+ - 复用 State Storage,实现量最小。
58
+ - 代价:(a) trunk 与用户工作变量抢同一个 `capBytes` 预算,超限时**整个 snapshot 被 skip**(连用户变量一起丢);(b) dill 跨 Python 版本脆弱;(c) 原文常驻 kernel 进程 RAM,随压缩次数线性涨。
59
+
60
+ **方案 F — 独立 trunk 文件 + kernel 索引 + `recall()` 读文件**(推荐):
61
+ - trunk 本体 = 纯文本 JSON(带 role/seq),落 `kernel-snapshots/<sessionId>/trunks/<compactionId>.json`;kernel 里只放索引 dict(tag → 路径);`recall(tag)` helper 直接读文件。
62
+ - 好处:独立预算天然成立(不吃 snapshot cap);写入即时持久(无 snapshot 的 1500ms debounce 窗口);恢复 = 回填索引或重扫目录;绕开 dill 版本问题。
63
+ - 代价:recall 读文件比读内存慢几毫秒,对 LLM 无感。
64
+ - State Storage 职责因此更清晰:snapshot 管用户工作变量,trunk store 管压缩原文。
65
+
66
+ ### 2.3 tag 与 recall 协议
67
+
68
+ - tag id:`ctx-<compactionId>`(或自增计数)。
69
+ - tag 文本块(挂摘要尾部,格式示例):
70
+
71
+ ```
72
+ [原文回溯: ctx-12]
73
+ 本轮压缩的原始上下文已归档,需要细节时用 recall('ctx-12') 读取。
74
+ ```
75
+
76
+ - `recall(tag)` helper(py-sdk 注入):保留命名空间藏 trunk 数据(**不暴露裸全局**,防模型 cell 误覆盖/`del`);只读访问;超长输出截断提示;缺失报明确错误。
77
+ - Control Prompt 增加约定段:摘要条目带 `ctx-N` 标签时,需要全文就 `recall('ctx-N')`。
78
+ - token 经济学:摘要照常在上下文(tag 一行文本 ≈ 0 成本);模型只在需要时付出一次读取成本;归档不查询则不进上下文。
79
+
80
+ ### 2.4 预算与淘汰(开放数字)
81
+
82
+ 归档随压缩次数线性增长,必须设上限,否则省下的上下文 token 从 RAM/磁盘侧门还回去。候选:最近 N 档 / 总 token 预算 / LRU。淘汰是安全的——session log 里原文永远在(§2.5)。
83
+
84
+ ### 2.5 自愈(compactionId 稳定性)
85
+
86
+ tag 携带 `compactionId + shadowedSeqs`;revive 时发现归档缺失,可从 session log 重放重建。本质:**session log 就是原文的持久层,归档只是模型可寻址的快路径缓存**。
87
+
88
+ ### 2.6 两条触发路径(共用挂点)
89
+
90
+ - auto pressure:`RecencyAwareCompactionEngine.compactIfNeeded`
91
+ - manual:`/compact` → `compactNow`
92
+ - 挂点都在 `compactRegion` 返回后,路径无关。
93
+
94
+ ## 3. 代码骨架(compactRegion 返回后)
95
+
96
+ ```ts
97
+ const result = await this.compactRegion(range.start, range.end, agent, signal)
98
+ if (result === null) return null
99
+
100
+ const session = agent.session
101
+ const summaryNodeSeq = result.summarySeq + 1 // contractual adjacency
102
+
103
+ // 1) 事后读原文(append-only log 完整保留)
104
+ const original = result.shadowedSeqs
105
+ .map(seq => session.deriveEventMessage(session.events[seq]))
106
+ .filter((m): m is Message => m !== null)
107
+ // 2) 归档(介质见 §2.2,失败则跳过第 3 步、不阻塞压缩)
108
+ const tagId = `ctx-${result.compactionId}`
109
+ // await archiveRecall(tagId, classify(original)) // 分类见 §2.1
110
+ // 3) 单节点 replace 挂 tag
111
+ const summaryMsg = session.deriveEventMessage(session.events[summaryNodeSeq])!
112
+ session.append('user/message', createUserMessage({
113
+ content: [...summaryMsg.content, { type: 'text', text: `\n[原文回溯: ${tagId}] 全文可通过 recall('${tagId}') 读取。` }],
114
+ source: summaryMsg.source, // 保留 compactionId,checkpoint 识别不破
115
+ }), {
116
+ surfaceOp: { op: 'replace', start: summaryNodeSeq, end: summaryNodeSeq },
117
+ sourceEventSeqs: [summaryNodeSeq],
118
+ })
119
+ ```
120
+
121
+ ## 4. 与旧 doc §3 开放问题对照
122
+
123
+ | # | 问题 | 状态 |
124
+ |---|---|---|
125
+ | 1 | 实现路径 A/B/C | 已裁定方案 B(子类化),Feature 1 已落地 v0.1.1+ |
126
+ | 3 | 摘要后编辑口 | **已解决**(本文档 §1):落盘前无插入口,落盘后单节点 replace 等效;且原文可事后读取,归档无需压缩前捕获 |
127
+ | 8 | compactionId 稳定性 | **已解决**(§2.5 自愈:log 重放重建) |
128
+ | 4 | 归档增长上限 | 仍开放(§2.4 候选方案) |
129
+ | 5 | thinking 块剥不剥 | 仍开放(剥=省空间丢推理过程;留=完整但体积翻倍) |
130
+ | 6 | 标记措辞与 token 预算 | 仍开放(§2.3 给出了候选格式) |
131
+ | 7 | 与 pruner 的交互 | 仍开放,但假设已变:recency 路径在 compactRegion 前调 `pruneSession`;若 prune 也是 surface-replace 机制,则 log 原文应仍在(append-only),需实现时验证 |
132
+ | 9 | recency 低于 system prompt 体积 | Feature 1 已带 no-op 守卫,软警告低优先 |
133
+
134
+ ## 5. 验证计划(实现时执行)
135
+
136
+ - **单测**:分类落桶(每条消息恰落一桶)、tag 格式、单节点 replace(`dev/spike-tag-repro.mjs` 已是该场景的可运行骨架)。
137
+ - **e2e**:灌满上下文 → 触发压缩(auto + manual 各一次)→ 断言:(1) 归档存储含被压缩区间原文;(2) 摘要尾部含 tag;(3) 模型执行 `recall(tag)` 能复述原文某细节。
138
+ - **pruner 交互**(开放项 #7):`pruneSession` 之后 log 里的原文事件是否完整。
139
+ - **回归**:未配归档时行为与现状一致(默认全关);测试只增不减(现有 140/140 不动)。
140
+
141
+ ## 6. 参考
142
+
143
+ - `dev/dashr-compaction-window-and-archive.md`(Feature 2 总纲 + Feature 1 已实现记录)
144
+ - `dev/spike-tag-repro.mjs`(API 面复现脚本,`node dev/spike-tag-repro.mjs` 可复跑,exit 0)
145
+ - `dashr/src/compaction/recency-engine.ts`(挂点)、`dashr/src/py-sdk.ts`(recall helper 注入点)
146
+ - 上游源码:`@deepseek-ai/dsh-session`(append/surface)、`@deepseek-ai/dsh-compaction-basic/lib/index.js` `commitCompactionBody`(替换节点 append 顺序)、`@deepseek-ai/dsh-compaction`(CompactionResult)
147
+ - 委托 spike 报告:`/tmp/a2a/dashr-tag-spike.md`(Dash agent 产出,2026-08-20;关键结论已全部吸收进本文档 §1)
@@ -0,0 +1,102 @@
1
+
2
+ // Minimal reproduction: verify that a session surface node's text can be
3
+ // "rewritten" (summary + recall tag) via the append-only replace surfaceOp,
4
+ // and that the new compaction summary node's seq is determinable.
5
+ // Read-only spike — no dashr repo files touched.
6
+
7
+ import { Session } from '/home/u1/workspaces/dashr/dashr/node_modules/@deepseek-ai/dsh-session/lib/index.js'
8
+ import { createUserMessage, createAssistantMessage } from '/home/u1/workspaces/dashr/dashr/node_modules/@deepseek-ai/dsh-llm/lib/index.js'
9
+
10
+ const s = Session.create('spike-tag-demo')
11
+
12
+ const userMsg = (text) => createUserMessage({
13
+ content: [{ type: 'text', text }],
14
+ source: { kind: 'user' },
15
+ })
16
+
17
+ const asstMsg = (text, step) => createAssistantMessage({
18
+ content: [{ type: 'text', text }],
19
+ source: { provider: 'p', model: 'm' },
20
+ })
21
+
22
+ const summarize = (m) => m.map(x => `${x.role}: ${x.content.map(b => b.text ?? '<non-text>').join('')}`).join('\n ')
23
+
24
+ console.log('=== stage 0: four surface appends ===')
25
+ s.append('user/message', userMsg('user-0: hello'), { surfaceOp: 'append' })
26
+ s.append('assistant/message', { turn: 1, step: 1, message: asstMsg('asst-1: world', 1) }, { surfaceOp: 'append' })
27
+ s.append('user/message', userMsg('user-2: question'), { surfaceOp: 'append' })
28
+ s.append('assistant/message', { turn: 1, step: 2, message: asstMsg('asst-3: answer', 2) }, { surfaceOp: 'append' })
29
+ console.log('surface.nodes =', JSON.stringify(s.surface.nodes))
30
+ console.log('deriveMessages:\n ' + summarize(s.deriveMessages()))
31
+
32
+ console.log('\n=== stage 1: simulate compactRegion writing a summary over nodes [1,2] ===')
33
+ // Mirror commitCompactionBody exactly: log-only compaction/summary, then the
34
+ // surface replacement user/message immediately after (summarySeq + 1).
35
+ const summaryEvent = s.append('compaction/summary', {
36
+ compactionId: 'c-1',
37
+ summary: [{ type: 'text', text: 'CONDENSED: user-2 and asst-3 happened.' }],
38
+ shadowedRange: { start: 1, end: 2 },
39
+ shadowedSeqs: [1, 2],
40
+ shadowedTokenCount: 1234,
41
+ provider: 'p', model: 'm',
42
+ })
43
+ const replacement = s.append('user/message',
44
+ createUserMessage({
45
+ content: [
46
+ { type: 'text', text: 'This is an automatically generated checkpoint...\n\n<compacted-summary>' },
47
+ { type: 'text', text: 'CONDENSED: user-2 and asst-3 happened.' },
48
+ { type: 'text', text: '</compacted-summary>' },
49
+ ],
50
+ source: { kind: 'plugin', plugin: 'compact', compactionId: 'c-1' },
51
+ }),
52
+ {
53
+ surfaceOp: { op: 'replace', start: 1, end: 2 },
54
+ sourceEventSeqs: [summaryEvent.seq, 1, 2],
55
+ },
56
+ )
57
+ console.log('summaryEvent.seq (compaction/summary) =', summaryEvent.seq)
58
+ console.log('replacement.seq (surface summary node) =', replacement.seq, ' === summarySeq+1 ?', replacement.seq === summaryEvent.seq + 1)
59
+ console.log('surface.nodes =', JSON.stringify(s.surface.nodes))
60
+ console.log('deriveMessages:\n ' + summarize(s.deriveMessages()))
61
+
62
+ console.log('\n=== stage 2: PATCH the summary node text (append recall tag) ===')
63
+ // The summary surface node seq is determinable as summarySeq + 1 (== replacement.seq).
64
+ const summaryNodeSeq = replacement.seq
65
+ const tagged = s.append('user/message',
66
+ createUserMessage({
67
+ content: [
68
+ { type: 'text', text: 'This is an automatically generated checkpoint...\n\n<compacted-summary>' },
69
+ { type: 'text', text: 'CONDENSED: user-2 and asst-3 happened.' },
70
+ { type: 'text', text: '</compacted-summary>' },
71
+ { type: 'text', text: '\n[原文回溯: ctx-12] 全文可通过 recall(\'ctx-12\') 读取。' },
72
+ ],
73
+ source: { kind: 'plugin', plugin: 'compact', compactionId: 'c-1' },
74
+ }),
75
+ {
76
+ surfaceOp: { op: 'replace', start: summaryNodeSeq, end: summaryNodeSeq },
77
+ sourceEventSeqs: [summaryNodeSeq],
78
+ },
79
+ )
80
+ console.log('patched node seq =', tagged.seq)
81
+ console.log('surface.nodes =', JSON.stringify(s.surface.nodes))
82
+ console.log('deriveMessages:\n ' + summarize(s.deriveMessages()))
83
+
84
+ console.log('\n=== stage 3: shadowed originals remain recoverable from the append-only log ===')
85
+ console.log('shadowedSeqs [1,2] still in session.events:')
86
+ for (const seq of [1, 2]) {
87
+ const e = s.events[seq]
88
+ const m = s.deriveEventMessage(e)
89
+ console.log(` seq ${seq}: type=${e.type} role=${m?.role} text="${m?.content.map(b => b.text ?? '').join('')}"`)
90
+ }
91
+
92
+ console.log('\n=== stage 4: confirm in-place mutation is rejected (deep-frozen) ===')
93
+ const frozenMsg = s.deriveMessages().find(m => m.content.some(b => b.text?.includes('[原文回溯')))
94
+ let mutateOk = false
95
+ try {
96
+ frozenMsg.content.push({ type: 'text', text: 'sneaky' })
97
+ mutateOk = true
98
+ } catch (err) {
99
+ console.log('mutation threw:', err.constructor.name, '-', err.message)
100
+ }
101
+ console.log('in-place push succeeded?', mutateOk)
102
+ console.log('total events:', s.events.length, '| seq (next):', s.seq)
@@ -0,0 +1,128 @@
1
+ # DASHR 上游源码分析(dsh × prime-agent)
2
+
3
+ > 日期:2026-08-16 · 工具:Graphify (graphify-http-mcp :4749, AST 模式)
4
+ > 用途:DASHR 自研运行时选型底图。两部分来源 + 结合方式蓝图见后续文档。
5
+
6
+ ## 0. 前置事实
7
+
8
+ | 项 | deepseek-harness (dsh) | prime-agent (PI) |
9
+ |---|---|---|
10
+ | Clone | `dashr/deepseek-harness` @ `47f9438` (2026-08-13, npm-public PR #2519) | `dashr/prime-agent` @ `97b994c` (2026-08-14, daemon spawn ledger #1387) |
11
+ | 语言主体 | TypeScript (ESM), Node ≥22.19 / ≥24 | TS host + Python runtime(模型面 Python,调度面 TS) |
12
+ | 规模 | ~85MB 源码, 2319 ts + 19 py | ~30MB, 925 ts + 23 py(+runtime) |
13
+ | pnpm workspaces | `packages/<group>/<pkg>` 两级, 50 组 ~230 包 | `packages/{agent,ai,coding-agent,tui}` 4 包 + `prime-agent-runtime/` (py) |
14
+ | 许可 | MIT(预发布期承诺) | MIT |
15
+
16
+ ## 1. Graphify 图谱(AST,无 LLM 语义增强)
17
+
18
+ | 指标 | dsh | prime-agent |
19
+ |---|---|---|
20
+ | nodes | 54,318 | 13,676 |
21
+ | edges | 72,496 | 29,765 |
22
+ | communities | 4,110 | 775 |
23
+ | code 节点 | 33,026 | 10,784 |
24
+ | graph.json | 44.9 MB | 15.4 MB |
25
+
26
+ 产物:各库 `graphify-out/{graph.json, GRAPH_REPORT.md, manifest.json}`。
27
+ graph.html 因 >5000 节点未生成(GRAPHIFY_VIZ_NODE_LIMIT),交互视图需切子图或提限。
28
+ 增量更新:`update_graph(project_path)`;Graphify 缓存于 `graphify-out/cache/`。
29
+
30
+ `.graphifyignore` 已写入两库(排除 node_modules/.git/dist/test fixtures/snapshots)。
31
+
32
+ ## 2. deepseek-harness 结构
33
+
34
+ ### 2.1 布局(依据 AGENTS.md + 实测目录)
35
+
36
+ ```
37
+ vendor/ vendored Cordis 内核(manifest+sync procedure)
38
+ packages/ @deepseek-ai/dsh-<name>,50 组 ~230 包
39
+ core/ agent, agent-loop, session, system-prompt, tools, scope
40
+ llm/ llm, llm-deepseek, llm-pi-ai, llm-retry, token-meter
41
+ subagent/ subagent + acp/claude-code/codex/dsh-sdk/fork-in-process/... 11 包
42
+ 其他能力组: fs, shell, subprocess, terminal, lsp, skill, web, mcp, acp,
43
+ compaction, context, workflow, todo, plan, preset, guard,
44
+ self-modification, hooks, session, identity, settings,
45
+ credentials, interaction, boot, sdk, typert, api, sandbox, ...
46
+ apps/ cli + web
47
+ python/ Python SDK + bundled runtime
48
+ native/ node-addon-landlock-run(Landlock 沙箱 addon)
49
+ .agents/ notes/(设计笔记,一手取证源)
50
+ docs/ architecture, cordis-api, postmortem, subsystems
51
+ ```
52
+
53
+ ### 2.2 图谱枢纽(god_nodes, by degree)
54
+
55
+ - `Context` (790) — Cordis 上下文,全库交汇点
56
+ - `InvariantInstaller` (216), `Service` (138) — 插件注册骨架
57
+ - `createSnapshotStore()` (97), `SnapshotStore` (79), `bindSnapshotSelector()` (88) — 会话快照存储链
58
+ - `launchWebScaffold()` / `WebScaffold` / `watchConsole()` — web 测试脚手架
59
+ - tsconfig `paths` (139) + package `scripts` (124) — workspace 胶水节点多,体现 monorepo 复杂度
60
+
61
+ ### 2.3 关键机制(docs/cordis-api 图谱查询 + AGENTS.md)
62
+
63
+ - 插件注册原语:`ctx.plugin()` / `ctx.inject()` / `Registry`(docs/cordis-api/registry.md)
64
+ - Registrations are effects:所有贡献走 `ctx.effect()` / `ctx.on()`,`register()` 返回 disposer(时空可组合性的实现基础;即 Cordis 论文「可逆效应」机制,论文仓库 `github.com/cordiverse/paper`,解读见 deepseek-dsh.md §1.4)
65
+ - 能力三层拆分:Service Definition(接口)/ provider(实现)/ Consumer(消费者)→ 换实现不动消费者
66
+ - per-session preset:`preset/` 包,agent.cordis.yml 组装,会话锁 `agent-preset-locked`
67
+
68
+ ## 3. prime-agent 结构
69
+
70
+ ### 3.1 布局
71
+
72
+ ```
73
+ packages/
74
+ agent/ 5 文件:agent-loop.ts, agent.ts, proxy.ts, types.ts(薄)
75
+ ai/ provider 层:api-registry, bedrock, stream, oauth,
76
+ models.generated, openrouter-reasoning, mcp
77
+ coding-agent/ 主体:src/core/{kernel, tools, session-manager, ...}
78
+ skills/(Python 技能:rlm, refine, compact, goal, edit,
79
+ websearch, agent-message, rlm-heartbeat, ...)
80
+ tui/ 终端 UI
81
+ prime-agent-runtime/ Python 侧 RLM runtime
82
+ src/rlm/{__init__, harness, skill, mcp_base}.py
83
+ test/ subagent_registry, mcp_base, agent_message_skill, harness
84
+ ```
85
+
86
+ ### 3.2 图谱枢纽(god_nodes)
87
+
88
+ - `AgentSession` (430) — 核心会话对象
89
+ - `InteractiveMode` (388), `TUI` (139), `Component` (134) — 交互层重
90
+ - `AgentDaemon` (184), `DaemonSupervisor` (134), `DaemonAgentConnection` (122) — daemon 体系(最新 commit 正是 supervisor-owned rlm spawn ledger)
91
+ - `SettingsManager` (172), `SessionManager` (108)
92
+
93
+ ### 3.3 关键链路(query_graph 取证)
94
+
95
+ **子 agent spawn(rlm())**:
96
+ `rlm/__init__.py` → `run()` → `host_request()`(comm 通道发 TS host)→ `_spawn_handle_from_payload()` → `RLMSpawnHandle`;Python 侧 `HarnessState/HarnessScope/HarnessEntry/RefinementEvent`(harness.py, community 70/90)构成 Continual Harness 状态。TS 侧 `fork-server.ts SpawnParams`(community 157)承接。
97
+
98
+ **IPython kernel(Context as Variable)**:
99
+ - `core/tools/ipython.ts`:`IpythonKernelProvisioner` → `.startKernel()` / `.ensure()`
100
+ - `core/kernel/index.ts` `KernelManager`:`.doStart()`, `.enqueueExecute()`, `.runIopubPump()`, `.handleExecutionMessage()`, `.shutdown()`
101
+ - 状态持久化:`state-snapshot.ts` `.snapshotState()` / `.restoreState()` / `RestoreResult`(community 217)→ 变量级 checkpoint
102
+ - fork:`fork-server.ts` `forkKernel()`(community 157,与 SpawnParams 同社区 → fork 复用 kernel 状态)
103
+
104
+ ## 4. 结合面初步观察(待蓝图输入)
105
+
106
+ | 维度 | 取 dsh | 取 prime-agent |
107
+ |---|---|---|
108
+ | 内核/装配 | Cordis 插件体系, preset, 能力三层 | — |
109
+ | 模型面 | — | 持久 IPython kernel + state-snapshot |
110
+ | 子 agent | subagent 11 包矩阵 | `rlm()` 函数式 + fork-server |
111
+ | 记忆 | — | Continual Harness (ρ,G,K,M) |
112
+ | 沙箱 | bwrap+Landlock (native addon) | — |
113
+ | LLM 接入 | llm-deepseek/pi-ai/retry/token-meter | ai/ 包多 provider |
114
+
115
+ 风险点:两库语言运行时不同(dsh 纯 TS vs PA 模型面 Python);结合方式决定桥接层(PA 式 comm channel 或 dsh 式 in-process SDK)。
116
+
117
+ ## 5. 图谱查询备忘
118
+
119
+ MCP endpoint `http://127.0.0.1:4749/mcp`(Streamable HTTP, 需 `Accept: application/json, text/event-stream`)。
120
+ `graph_path` 参数 = `graphify-out/` 目录(含尾 graph.json 解析)。常用:
121
+ `graph_stats / god_nodes / query_graph / get_neighbors / shortest_path / graph_affected(影响面) / graph_tree / surprising_connections`。
122
+
123
+ 增量:代码改动后 `update_graph`;LLM 语义增强需 API key(可选 gemini)。
124
+
125
+ ## 6. 相关档案
126
+
127
+ - 调研/实测:`agent-harness/21_CLI-Agent/03_deepseek-dsh/deepseek-dsh.md`
128
+ - `agent-harness/21_CLI-Agent/04_prime-agent/prime-agent-{research,deployment-experiment,rlm-analysis,paradigm-discussion,continual-harness}.md`
@@ -0,0 +1,110 @@
1
+ # REPL 工具调用截断诊断(v0.2.1b 实测中发现,2026-09-01)
2
+
3
+ ## 结论先行
4
+
5
+ **任何从 REPL(`eval` cell)内部发起的工具调用(`tool.<name>(...)`)都会让 dsh web daemon 进程崩溃并自动重启;会话日志在 `tool/code-dispatch-start` 处撕裂,恢复时 repair 机制合成「interrupted-tool-result」,模型看到的即「工具调用被截断、结果未知」。** 4 次探针 4 次复现,与 journald 守护进程崩溃记录逐条对应。
6
+
7
+ 这不是 REPL 同步/异步的问题,也不是 subagent 特有问题——是 dashr 子分派桥接层的结构性缺陷 + 部署运行态注册表视图缺 symbol 的直接原因叠加。
8
+
9
+ ## 一、复现矩阵(第一人称)
10
+
11
+ | # | cell 内容 | 结果 |
12
+ |---|---|---|
13
+ | 1 | `await tool.subagent({… run_in_background: false })` | 截断(daemon 崩溃 17:39:17) |
14
+ | 2 | `await tool.subagent({… run_in_background: true })` | 截断(daemon 崩溃 17:41:15) |
15
+ | 3 | `await tool.bash({… run_in_background: true })` | 截断(daemon 崩溃 17:57:05) |
16
+ | 4 | `await tool.read({path: "dashr/package.json"})` | 截断(daemon 崩溃 18:02:27) |
17
+
18
+ 对照:纯 Python cell(`dir(tool)`、顶层 `return` 判错、`await asyncio.sleep(0)`)全部正常。**凡含 `tool.*` RPC 的 cell 必崩,与工具、与 background 与否无关。**
19
+
20
+ ## 二、截断面定位(三层证据)
21
+
22
+ ### 1. 会话日志(`~/.dsh/sessions/…/session.jsonl.zstd`)
23
+ 4 次截断的共同指纹:`tool/call (eval)` → `tool/code-dispatch-start`(**已持久化**)→ 日志撕裂(无 `tool/code-dispatch`、无 `step/end`/`turn/end`)→ 重载时 `packages/core/session/src/repair.ts` 的 `interruptedTurnClosers` 合成 `interrupted-tool-result-<callId>-<seq>`(`TOOL_OUTCOME_UNKNOWN`)。会话内 **0 条 error 事件**——错误根本没走到会话层。
24
+
25
+ ### 2. 守护进程日志(`journalctl --user -u dsh.service`)——铁证
26
+ ```
27
+ Sep 01 17:39:17 pnpm[414449]: dsh: fatal load failure: TypeError: Cannot read properties of undefined (reading 'prepare')
28
+ at Object.start (…/better-dsh/lib/index.js:11281:41)
29
+ at <anonymous> (…/lib/index.js:11191:21)
30
+ at drive (…/lib/index.js:11207:7)
31
+ at outcome (…/lib/index.js:11310:6)
32
+ at new Promise (<anonymous>)
33
+ at toolFunctions.<computed> (…/lib/index.js:11320:72)
34
+ at Proxy.dispatchHostRequest (…/lib/index.js:2180:21)
35
+ Sep 01 17:39:19 systemd: dsh.service: Main process exited, code=exited, status=1/FAILURE
36
+ Sep 01 17:39:23 systemd: dsh.service: Scheduled restart job, restart counter is at 1.
37
+ ```
38
+ 17:39:17 / 17:41:15 / 17:57:05 / 18:02:27 四次崩溃,栈完全一致。`scheduler.prepare` 中 `scheduler` 为 `undefined`——即 `registry[TOOL_RUNTIME_SCHEDULER]` 在部署运行态取不到值。
39
+
40
+ ### 3. 源码链(`dashr/src/index.ts`)
41
+ - kernel 侧:`tool.<name>(...)` → `binding.call` RPC(`src/bootstrap.ts` `_dashr_callable`)。
42
+ - host 侧:`dispatchHostRequest`(`src/runtime.ts:545`)→ `await fn(args)`(`src/index.ts` `binding()`)。
43
+ - `binding()` 内部(`src/index.ts:666`):`const scheduler = registry[TOOL_RUNTIME_SCHEDULER]` → 单驱动道 `drive()`(`src/index.ts:591-621`)→ `start()` 内 `await scheduler.prepare(input)`(`src/index.ts:712`)。
44
+ - **结构性缺陷**:`drive()` 的 async IIFE 是 `try { … } finally { … }`,**没有 catch**;调用处是 `void drive()`。任何 `start()`/`commit()` 抛错(如 `scheduler.prepare` TypeError)都会变成 **unhandled rejection**——binding promise 既不 resolve 也不 reject,cell 永久挂起,错误外泄给进程。
45
+ **完整链条**:cell 工具调用 → dispatch 启动(`code-dispatch-start` 已入日志)→ `registry[TOOL_RUNTIME_SCHEDULER]` 为 undefined → `scheduler.prepare` TypeError → driver 无 catch → unhandled rejection → `installFailLoud` 判定 fatal → daemon exit(1) → systemd 重启 → 会话重载 repair 合成「interrupted-tool-result」→ 模型收到截断消息。**截断面:binding 桥 → registry symbol 查询 → driver 的未处理拒绝。**
46
+
47
+ ## 三、命名澄清:REPL dispatch log 与 code-dispatch 是两个不同层(重要)
48
+
49
+ 容易混淆,先分清:
50
+
51
+ | 名字 | 层级 | 是谁 | 本次 change 是否改名 |
52
+ |---|---|---|---|
53
+ | `tool/code-dispatch-start` / `tool/code-dispatch` | **会话持久日志事件**(durable log,`agent.session.append`) | dashr `src/index.ts:689,712`;harness `known-event-types.ts:63-64` 注册 | **否**——v0.1.x 就有,历史遗留名 |
54
+ | `dashr/repl-dispatch-log` | **waterfall 扩展点**(内容整形监听事件) | dashr 自注册(`src/index.ts:458`) | **是**——v0.2.1 从上游 `tools/code-dispatch-log`/`tools/ptc-dispatch-log` 改名为「自己的 REPL dispatch log」 |
55
+ | `tools/ptc-dispatch-log` | waterfall(上游 PTC 用) | harness `src/ptc.ts:282` + spill policy 监听(`spill-policy/src/index.ts:217`) | dashr 已不再 emit(仅注释提及) |
56
+
57
+ **结论**:
58
+ - 「自己的 REPL dispatch log」`dashr/repl-dispatch-log` **在代码里存在且自注册**——目标形态已就位。它之所以「没被触发」,是因为崩溃发生在它之前的 `scheduler.prepare`(它在 settle 之后才跑)——**是崩溃的结果,不是原因**。
59
+ - 会话日志里我用作定位证据的 `tool/code-dispatch-start` 是**持久日志事件名**,与 waterfall 改名无关,只是「撕裂点之前最后一条已写入事件」的定位标记。
60
+ - 改名引入了一个**真实但非崩溃的副作用**:harness 的 spill policy 仍监听上游 `tools/ptc-dispatch-log`,dashr 不再 emit → 溢出限幅(oversized 结果的 preview+locator 替换)对 eval 子调用日志**静默失效**。**已补(2026-09-01 晚,见六)**:dashr `shapeDispatchLog` 跑完 `dashr/repl-dispatch-log` 后,再把同一 dispatch(载荷结构与上游 `PtcDispatchLog` 完全一致)喂进 `tools/ptc-dispatch-log` 载体,spill 臂恢复生效。改动在 `dashr/src/index.ts`,lib 已重建(`f0febba7…`),**部署位同步 + daemon 重启待用户执行**。
61
+
62
+ ## 四、为什么 `registry[TOOL_RUNTIME_SCHEDULER]` 是 undefined(直接原因)
63
+
64
+
65
+ - 已排除:dsh-tools 双副本 symbol 不一致(实测插件与 daemon 均解析到同一 realpath `/home/u1/workspaces/dsh-alpha/packages/core/tools`,symbol 严格相等);dashr 分派代码改动(266d232/06e516a/HEAD 逐字节一致);dsh-tools alpha.1 vs alpha.3 差异(仅 import 整理/ToolCallId brand 等外观差异)。
66
+ - 关键观察:`classify()` 调用的 `registry.executionMode(input)` **正常**(`code-dispatch-start` 已写入日志,证明 `start()` 之前的分派逻辑跑通),而 `registry[TOOL_RUNTIME_SCHEDULER]` **取不到**。`executionMode` 是 ToolRuntime 类方法、`TOOL_RUNTIME_SCHEDULER` 是类实例 symbol 字段——即部署运行态 `runtimeCtx.tools` 返回的**不是原生 ToolRuntime 实例**,而是一个有方法、缺实例 symbol 字段的服务视图(cordis 作用域服务解析 / plugin tree 挂载路径差异)。
67
+ - 测试为什么全绿:dashr 测试组合(`test/helpers.ts`)把 `ctx.tools` 解析到**根作用域的原生 ToolRuntime 实例**,symbol 在位;生产组合经 harness plugin tree / scope 层解析,拿到的是视图。
68
+ - **何时坏的(重要修正)**:v0.1.8 实测报告(2026-08-24)实证 DASHR 自己的 `eval` 桥当时**正常工作**——同 cell 并发 `tool.read` + `tool.bash` 完成。2026-08-28 会话用的是 harness 自带 PTC `run_cell`(`tools.bash`,复数命名空间 = PTC SDK 形态;该 PTC 核心经 `@deepseek-ai/dsh-code-runtime-python` 执行 **Python**,非 TS),不能代表 dashr 桥状态。**崩溃由 2026-09-01 的 harness alpha(0.1.2-alpha.1)升级引入**——升级后 plugin tree 里 `runtimeCtx.tools` 变成缺实例 symbol 字段的服务视图;dashr v0.2.1 的 dispatch-log 改名不是崩溃原因。
69
+
70
+ ## 五、REPL 同步/异步问题的回答(顺带闭环)
71
+
72
+ - 模型层:`eval` 是同步工具调用,模型等 cell 结果——这是设计使然,与缺陷无关。
73
+ - cell 内:`tool.<name>(...)` 是 kernel→host 的 `binding.call` RPC,被 cell `await`;host 经与原生调用同一 registry 分派器执行。`run_in_background: true` 的工具实现**快速返回 id**(subagent `{kind:'continuable', subagentId}`、bash 返回 job id),background 语义本应保留、子任务与 cell 结束无关。
74
+ - 但**当前运行时该路径整体坏掉**:任何子分派在 `scheduler.prepare` 即 TypeError,与 background 无关——所以「在 REPL 里 background 是否异步」目前是伪问题,路径不通;修好后 background 语义会按上述设计成立。
75
+
76
+ ## 六、修复方向(分层)
77
+
78
+ 1. **dashr 立即修(阻断 daemon 崩溃)**:`drive()` 加 catch / `start()`、`commit()` 失败必须 settle binding promise 为错误结果——cell 内工具调用失败应变成可见的 `ToolCallError`,**绝不允许以 unhandled rejection 外泄杀 daemon**。这是把「任何分派错误」放大成「整个运行时崩溃」的放大器,无论直接原因是什么都必须先修。
79
+ 2. **dashr 防御**:`registry[TOOL_RUNTIME_SCHEDULER]` 为 undefined 时抛带上下文的 loud 错误(cell 可见),而不是裸 `undefined.prepare` TypeError。
80
+ 3. **根因定位(harness 接线)**:在活体 daemon 上探针确认 plugin tree 中 `runtimeCtx.tools` 的实际对象(cordis 作用域服务视图 / 第二个 tools 实例),修正挂载使插件拿到原生 ToolRuntime(symbol 在位)。这一步需要 daemon 侧可写环境(本会话 bwrap 沙箱只读宿主,无法直接探查活体)。
81
+ 4. **测试补强(部分完成)**:已加「`tools/ptc-dispatch-log` 载体回喂」回归用例(`test/bridge.spec.ts`,全量 396 绿)。仍缺「生产形态」挂载测试——按 harness 的 plugin-tree/scope 方式挂载插件,断言 `ctx.tools[TOOL_RUNTIME_SCHEDULER]` 已定义 + 一个真实工具 RPC 从 cell 完成;防止此环境差异回归。
82
+ 5. **回归核验基线**:修复后重跑本文档第一节的 4 探针矩阵(4/4 应全部返回结果而非截断),并观察 `journalctl --user -u dsh.service` 无新崩溃。
83
+
84
+ ## 附:证据文件
85
+ - 会话日志:`~/.dsh/sessions/--home-u1-workspaces-dashr--/session-ad9ac311-3400-4304-b501-e7303da850df/session.jsonl.zstd`
86
+ - 守护进程日志:`journalctl --user -u dsh.service`(17:39:17 / 17:41:15 / 17:57:05 / 18:02:27 四条 fatal load failure)
87
+ - 对照工作会话:`06ac8b2c-fb2e-458f-80d6-a0d28e50f193`(2026-08-28,9 对完整 code-dispatch,PTC run_cell 路径)
88
+
89
+ ---
90
+
91
+ ## 七、v0.2.1c 修复执行记录(2026-09-01,change v0-2-1c-repl-tool-crash-fix)
92
+
93
+ ### 六.1 + 六.2 已实现(dashr 侧)
94
+
95
+ - `drive()` async IIFE 补 `catch`:lane 级 backstop——任何逃逸 start()/commit() 自身包裹的错误(或 classify() 抛错)settle 所有 queued-but-unsettled 分派为可见错误、清空队列、复位 exclusive、记 warn,**绝不外泄 unhandled rejection**。
96
+ - `PendingDispatch` 新增 `fail(error)`:把 binding promise settle 为 `{ isError: true, message }`(经 `settleError` → `settle`,kernel 转 `ToolCallError`)。
97
+ - `start()` / `commit()` 各自 try/catch:prepare 失败、dispatch body rejection、finalize/finish 失败都 settle 自己的 binding,不再向 lane 抛。
98
+ - `binding()` 内 `registry[TOOL_RUNTIME_SCHEDULER]` 判空:undefined 时抛带上下文的 loud 错误(点名 symbol、指向 harness 挂载接线),替代裸 `undefined.prepare` TypeError。
99
+ - 回归测试 3 个(`test/bridge.spec.ts`,全量 399 绿含 3 新):view 缺 symbol → cell 收到含 `TOOL_RUNTIME_SCHEDULER` 的 loud 错误;`scheduler.prepare` 抛错 → binding settle 为可见错误;classify 抛错 → lane backstop settle 且进程存活。
100
+
101
+ ### 六.3 根因探针(本轮结论,待活体 daemon 复核)
102
+
103
+ - **根因已锁定并更正(2026-09-02 活体探针 + 受控实验;推翻本节 2026-09-01 的 shadow 分支推断)**:真实根因是**部署拓扑 dual-copy**——崩溃期(2026-09-01 17:39–18:02)部署位插件嵌套 `node_modules/@deepseek-ai/*` 的 17 条 symlink(已退役 dev 接线)指向 dsh-alpha 源码树,插件 import 的 `TOOL_RUNTIME_SCHEDULER` 来自 alpha 副本的 dsh-tools 模块实例;daemon 的 `ToolRuntime` 实例由 host vendored 副本构建,其 symbol 字段以 host 副本 symbol 为键。两 symbol 为不同 `Symbol()` 实例:跨副本读实例字段 → `undefined`(`executionMode` 等字符串键方法不受影响、照常工作——症状逐字吻合)。
104
+ - 证据链(2026-09-02):① 活体 4 探针(`tool.bash` fg/bg、`tool.read`、`tool.subagent` bg:false/bg:true)经 `eval` cell 路径 4/4 真实结果——现行生产形态 symbol 在位;② 部署位 `require.resolve('@deepseek-ai/dsh-tools')` = `/home/u1/.local/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-tools/lib/index.js`(host vendored,③→④ 层);③ 受控实验:host 与 z_dsh-alpha 两副本 `TOOL_RUNTIME_SCHEDULER` 严格不等,host 键实例经 alpha symbol 读 → `undefined`、经 host symbol 读 → object;④ 时间线闭环:改名 `dsh-alpha → z_dsh-alpha`(2026-09-01 22:29)→ symlink 悬空 → 2026-09-02 清理删除 → daemon 重启 → 自愈。
105
+ - shadow 分支理论为何被否:它与症状吻合但从未被活体证实,且无法解释「拓扑清理 + 重启后自愈」;dual-copy 机制同时解释故障与恢复。v0.2.1c 报告引「symbol 严格相等」的排除测量与崩溃期拓扑矛盾(疑测同一副本两次或取于改名后),以直接证据为准。
106
+ - cordis traceable proxy 本身无嫌疑(symbol 转发分支在位;现行活体 plain read 即返回带 symbol 原生实例)。
107
+ - dashr 侧交付边界:六.1+六.2(不崩溃 + loud 错误)继续有效;dual-copy 已由部署拓扑收敛消除(部署纪律:插件 `node_modules/@deepseek-ai/` 只留 `schemastery`+`cosmokit`);v0.2.1d 给 guard 错误附 dsh-tools 解析路径实现事件自诊断。
108
+ ### 六.4 生产形态挂载测试(说明)
109
+
110
+ - 三个回归用例以「缺 symbol 视图」「prepare 抛错」「classify 抛错」三种形态驱动 `createRunCellTool`,覆盖六.1+六.2 全部防御面;测试组合(根作用域原生实例)本就 symbol 在位,断言 `runtimeCtx.tools[TOOL_RUNTIME_SCHEDULER]` 定义的用例属于测试组合自身形态(根挂载原生实例),无法复现生产 realm 差异——该差异须在活体 daemon 上复核(六.5)。