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,187 @@
1
+ # DASHR 安全风控分析:kernel subprocess 沙箱边界穿透
2
+
3
+ > 日期:2026-08-17
4
+ > 范围:`dashr/`(`dashr-code-runtime-ipython`)provider 的持久 IPython kernel 子进程
5
+ > 关联:`dashr-blueprint.md` §5 风险登记「沙箱边界穿透」条目
6
+ > 性质:实测证据链 + 风险升级建议
7
+ > 对照:§4 为同一宿主机上 Claude Code 运行时的同探针对照样(2026-08-17)
8
+
9
+ ## 1. 结论先行
10
+
11
+ blueprint §5 已登记「沙箱边界穿透(kernel 内 pip/网络不受 dsh sandbox 管)」为**中**风险,
12
+ 缓解措施「kernel 启动参数收窄 + dsh sandbox 对 kernel 子进程整体套用」标记 M2/M3。
13
+
14
+ 2026-08-17 实测证实:该风险**不仅尚未落地缓解,实际暴露面远超原评估**。kernel
15
+ 子进程当前是**零隔离**的宿主机特权执行体——任意文件读写 + passwordless sudo 提权。
16
+ 建议风险等级从「中」上调为「高」,并给出分层的可落地方案(§5)。
17
+
18
+ 同日对照实测(§4):同一宿主机上的 Claude Code 运行时,其模型代码执行体的暴露面与
19
+ 本 provider kernel 路径**完全等同**(同一 init mount namespace、workspace 外可写、
20
+ `sudo -n true` 通过)——权限继承是普遍事实,任何 Agent 运行时都面对;两者差别仅在
21
+ 沙箱的**覆盖方式**(dsh:漏了 kernel 通道)与**启用状态**(CC:有能力未开启)。
22
+
23
+ ## 2. 实测证据链
24
+
25
+ 测试环境:`/home/u1/workspaces/agent-harness`(dsh session),kernel 由 dsh host 拉起。
26
+
27
+ ### 2.1 权限继承链(进程树实测)
28
+
29
+ ```
30
+ systemd --user (pid 6820)
31
+ └─→ node (pid 3840092) ← dsh host(DeepSeek Harness,node 进程)
32
+ └─→ python3 (pid 3852226) ← ipykernel_launcher,本 provider 的持久 kernel
33
+ └─→ 任意 subprocess ← cell 内 import subprocess 起的子进程
34
+ ```
35
+
36
+ 三层全以 `u1`(uid 1000) 身份运行。kernel 只是 host→脚本 之间的一层 indirection,
37
+ **没有在中间做任何权限收窄**。
38
+
39
+ ### 2.2 kernel 进程的隔离状态(/proc/self/status 实测)
40
+
41
+ | 字段 | 实测值 | 含义 |
42
+ |---|---|---|
43
+ | `NoNewPrivs` | `0` | 未设 no_new_privs → **setuid 提权可用**(sudo 走通) |
44
+ | `Seccomp` / `Seccomp_filters` | `0` / `0` | 无 seccomp filter |
45
+ | `CapEff` / `CapPrm` | `0` | 无 effective/permitted capabilities(非特权进程常态) |
46
+ | `CapBnd` | `000001ffffffffff` | bounding set 基本全量,未收窄 |
47
+ | mount namespace | `mnt:[4026531832]` | **宿主 init namespace**,未进隔离 |
48
+ | 根挂载 `/` | `/dev/sdc3 ext4 rw,relatime` | 真实可写文件系统 |
49
+
50
+ 对比:`tools.bash` 命令看到的根挂载是 `/dev/sdc3 ext4 **ro,nosuid,nodev**,relatime`
51
+ (`dsh` 对 bash 工具套的沙箱视图),而 kernel 进程看到的是 **rw**。
52
+
53
+ ### 2.3 沙箱绕过对比测试(同一位置、同一操作)
54
+
55
+ | 路径 | 目标 | 结果 |
56
+ |---|---|---|
57
+ | `tools.bash` 写 workspace 内 | 正对照 | ✅ 成功(sandbox `mode=workspace-write, denied=false`) |
58
+ | `tools.bash` 写 `/home/u1/.probe` | 沙箱边界 | ❌ `denied=true`,stderr `Read-only file system` |
59
+ | **kernel 内 `subprocess` 写 `/home/u1/.probe`** | 绕过测试 | ✅ 成功,`-rw-rw-r-- 1 u1 u1` 真实落盘 |
60
+
61
+ ### 2.4 提权可达性
62
+
63
+ `sudo -n true` 在 kernel 内 subprocess 中返回退出码 **0**——passwordless sudo 对
64
+ kernel 进程成立。结合 `NoNewPrivs=0`,kernel 内任意 Python 可 `sudo` 到 root。
65
+
66
+ ## 3. 根因与定性
67
+
68
+ **权限来源**:最终权限不是 dsh 代码"授予"的,而是 dsh host 的启动环境——`u1` 用户
69
+ 会话 + 系统配置的 passwordless sudo。任何以 `u1` 身份、能 spawn 子进程的程序都天然
70
+ 具备同样的 sudo 能力。
71
+
72
+ **dsh 的问题不是"制造了 sudo",而是"未在 kernel 这条不受信代码执行路径上收窄继承下来的权限"**。
73
+ dsh 对 `tools.bash` 命令套了 `ro,nosuid,nodev` 文件系统沙箱,但对 `run_cell` 交给
74
+ kernel 的 Python 代码**没有套任何隔离**——而 kernel 内 `import subprocess` / `os.system` /
75
+ `open()` 等 Python 原生能力,全部绕开 bash 工具那条沙箱路径,直接在宿主机 init
76
+ namespace 上裸跑。
77
+
78
+ **两个正交概念**(易混,需区分):
79
+ - *权限继承*:OS 默认行为,子进程继承父进程权限——这是事实,任何框架都面对。
80
+ - *沙箱*:框架**主动**加的一层隔离,目的是打破默认继承。dsh 有该能力(bash 路径
81
+ 已用),只是**未在 kernel 路径启用**。故"沙箱限制不了脚本运行"是误读;准确说法
82
+ 是"dsh 的沙箱只覆盖了 bash 路径,漏掉了 kernel 路径"。
83
+
84
+ ## 4. 对照实测:Claude Code 运行时(同宿主机,同日)
85
+
86
+ 为验证 §3 的定性——**权限继承是 OS 普遍事实、沙箱是框架的主动选择**——2026-08-17
87
+ 在**同一台宿主机**上对 Claude Code 运行时(本分析的撰写 session 本身)跑了一遍 §2
88
+ 的同款探针。结论:CC 的模型代码执行体与 dsh kernel 路径**暴露面完全等同**,但缺失
89
+ 环节的性质不同。
90
+
91
+ ### 4.1 权限继承链(进程树实测)
92
+
93
+ ```
94
+ systemd (pid 1, root)
95
+ └─ sshd → sshd-session(root → u1 降权)
96
+ └─ bash (u1)
97
+ └─ claude (pid 3911309, u1) ← CC Agent Loop 运行时
98
+ ├─ chrome-devtools-mcp / corti mcp_server.py ← MCP 子进程,同权继承
99
+ └─ bash (pid 3920593, u1) ← Bash 工具子进程(模型代码的执行体)
100
+ ```
101
+
102
+ `claude` 进程与其工具 bash 全链路 `u1`(uid 1000),无任何收窄——与 §2.1 同构。
103
+
104
+ ### 4.2 隔离状态与绕过测试(对照 §2.2–§2.4)
105
+
106
+ | 指标 | CC:`claude` / Bash 工具 | dsh kernel(§2) | dsh `tools.bash`(§2.2 对比) |
107
+ |---|---|---|---|
108
+ | `NoNewPrivs` | **0** | 0 | — |
109
+ | `Seccomp` | **0** | 0 | — |
110
+ | `CapBnd` | `000001ffffffffff`(全量) | 全量 | — |
111
+ | mount namespace | **`mnt:[4026531832]`(宿主 init ns)** | init ns | 隔离视图 |
112
+ | 根挂载 | **`/dev/sdc3 ext4 rw`** | rw | `ro,nosuid,nodev` |
113
+ | 写 workspace 外 | **✅ `/home/u1/.cc-probe-outside` 落盘**(已清理) | ✅ 落盘 | ❌ DENIED |
114
+ | `sudo -n true` | **exit 0** | exit 0 | — |
115
+
116
+ CC 实测的 mount namespace inode 与 dsh kernel **完全相同**(`4026531832`)——两者是
117
+ 同一宿主 init namespace 里的裸执行体。
118
+
119
+ ### 4.3 差异定性:为什么暴露面等同,问题却不同
120
+
121
+ | | dsh(kernel 路径) | CC(本环境实测) |
122
+ |---|---|---|
123
+ | 模型代码执行引擎 | **两个**:bash 工具 + 持久 kernel | **一个**:Bash 工具 |
124
+ | 执行路径上的策略点 | kernel 路径**无任何过滤**(`run_cell` 不过 bash 策略层) | 必须过 Bash;PreToolUse hook 管道(matcher `*`)+ 权限模式都在路上 |
125
+ | OS 沙箱 | bash 路径已套 `ro` 视图;**kernel 路径漏配** | **能力在、未启用**:`/usr/bin/bwrap` 已安装,settings 无 sandbox 配置 |
126
+ | 当前暴露面 | u1 + passwordless sudo | **等同** |
127
+ | 问题定性 | **架构覆盖缺口**(第二条执行通道漏掉沙箱) | **配置未启用**(开关没开) |
128
+ | 修复成本 | 需给 kernel spawn 工程化加隔离(§5) | 改配置 |
129
+
130
+ 两点在对照中验证出的一般规律(对任何 Agent 运行时成立):
131
+
132
+ 1. **权限继承不可选择**:`claude` 与 dsh host 一样,没有"授予"子进程权限——权限
133
+ 来自启动环境(`u1` 会话 + 系统 sudo 配置),运行时能做的只有**是否收窄**。
134
+ 2. **策略点 ≠ 隔离**:本 session Bash 通道上有 ECC GateGuard(首条命令要求陈述
135
+ 事实 + 破坏性模式黑名单),实测其拦得住流程失误、拦不住权限——补两句事实
136
+ 陈述后同一条探针原样放行,落盘 + sudo 全部走通。hook 是**流程门**,不是
137
+ **硬边界**;硬边界只有 OS 级沙箱。误读警示:被 hook 拦住恰说明该通道**在**
138
+ 策略层覆盖内,而非存在绕开策略层的裸通道——dsh kernel 的问题才是后者。
139
+
140
+ ### 4.4 对本分析结论的影响
141
+
142
+ CC 的对照不改变 §1 的风险升级建议(dsh kernel 暴露面实测为零隔离,仍应升「高」),
143
+ 但补充一个事实:**"运行时自带沙箱能力"不等于"暴露面受控"**——CC 与 dsh 代表了
144
+ 两种典型的失效模式(未启用 vs 未覆盖),落地 §5 方案时两者都需以实测探针(§2/§4.2
145
+ 这张表)做验收,而非以"沙箱已配置"自证。
146
+
147
+ ## 5. 风控措施(分层,业界对照 + dashr 落地)
148
+
149
+ 业界对"执行不受信代码"默认隔离,从轻到重三层(参考 AgentPatterns sandbox-runtime
150
+ 对比、Modal/Northflank 2026 沙箱综述):
151
+
152
+ | 层级 | 机制 | 代表 | 对 dashr 的落地点 |
153
+ |---|---|---|---|
154
+ | OS 级(轻) | NoNewPrivs + drop cap + seccomp filter + mount ro | Claude Code Bash sandbox(bwrap/Seatbelt) | spawn kernel 时套用,改动最小 |
155
+ | 容器(中) | Docker / containerd | OpenInterpreter Docker 模式 | 包住整个 host 或 kernel |
156
+ | microVM(强) | Firecracker / Cloud Hypervisor | e2b / Modal / Devin | 每执行环境一个轻量 VM,重 |
157
+
158
+ 针对 `dashr/` provider(spawn `ipykernel_launcher` 处)的**最小可用收窄**,按
159
+ blueprint §5 原缓解「kernel 启动参数收窄」落地:
160
+
161
+ 1. **`NoNewPrivs=1`**:Node `spawn` 后经 wrapper 设 `PR_SET_NO_NEW_PRIVS`,直接阻断
162
+ setuid(含 sudo)提权——性价比最高的一条。
163
+ 2. **drop capabilities**:收窄 `CapBnd`,禁 `CAP_SYS_ADMIN` 等;配合 NoNewPrivs。
164
+ 3. **seccomp filter**:默认 deny + 白名单 IPython 所需 syscall(zmq/socket/exec 等)。
165
+ 4. **mount namespace 隔离**:复用 dsh 对 bash 的 `ro,nosuid,nodev` 视图——kernel 只
166
+ 对 workspace 挂 `rw`,其余 `ro`。
167
+ 5. **资源限制**:rlimit(CPU/内存/fork 数),防 fork bomb / 内存风暴。
168
+ 6. **网络隔离**:对应原条目「pip/网络」——net namespace 或策略路由,默认无外网、
169
+ 白名单 pip 源。
170
+
171
+ 以上 1–4 组合即可把 kernel 从"宿主机特权执行体"降到"受限执行体",且不牺牲
172
+ 「持久 namespace」的核心价值。
173
+
174
+ ## 6. 建议后续动作
175
+
176
+ 1. blueprint §5「沙箱边界穿透」条目:风险等级「中」→「高」,暴露面描述从
177
+ 「pip/网络」扩为「任意文件读写 + sudo 提权」,状态 M2/M3 → 未落地(实测证实)。
178
+ 2. 排入 M4/M5 里程碑:优先落地 §5 的第 1 项(NoNewPrivs),成本最低、收益最大。
179
+ 3. 补回归测试:kernel 内 `sudo -n true` 应失败、写 workspace 外应失败、`/proc/self/status`
180
+ 的 `NoNewPrivs=1` 断言。
181
+ 4. 本轮实测的完整交互记录(unified tool path、`dashr.host` comm 桥、ro 挂载对比、
182
+ sudo 探测)已存档于 session memory(Corti)。
183
+
184
+ ---
185
+
186
+ *本文档为实测驱动的安全分析,非 milestone 交付报告;与 `upstream-analysis.md`、
187
+ `dashr-blueprint-review.md` 同层,供 blueprint §5 风险条目升级引用。*
@@ -0,0 +1,97 @@
1
+ # DASHR 表面不变式与 omp 引入组合
2
+
3
+ > 日期:2026-08-28 · 来源:v0.1.8e/8f 五轮实测(docs/v0.1.8e-实测报告.md §1–§11)+
4
+ > 两份调研(`agent-harness/21_CLI-Agent/03_deepseek-dsh/dsh-ptc-tools-research.md`、`…/05_omp/omp-research.md`)+
5
+ > 用户决策(2026-08-28)。
6
+ > 性质:设计原则陈述 + 引入组合排序。A 节应在下一个 change 的 design/spec 里原样落地。
7
+
8
+ ---
9
+
10
+ ## A. 表面不变式(应写入 design/plan 的表述)
11
+
12
+ ### A.1 三句话原则
13
+
14
+ 1. **DASHR 的新工具 = 包装/路由原生定义的真工具**(URL wrapper 的 read/write/grep/glob 如此;delegation 桥也应如此)。
15
+ 2. **REPL 自动桥接注册表放行的一切**(restrict 之后的 visible 全集,单态机械映射,零名单 —— 即 D3)。
16
+ 3. **不变式:运行时直调面 ≡ REPL `tool.*` 面**。两个面是同一个注册表投影的两个消费者,wire 拿到什么,cell 里就绑什么;反之,DASHR 声明给模型的每一个能力,两个面必须同时在场。
17
+
18
+ ### A.2 当前违反不变式的实证(v0.1.8f,五轮实测)
19
+
20
+ | 能力 | wire 直调 | REPL `tool.*` | 目录声明 | 来源机制 |
21
+ |---|---|---|---|---|
22
+ | `eval`(传输)| ✅ | —(排除自身)| —(排除自身)| `tools.register`(真工具)|
23
+ | read/write/grep/glob(URL wrapper)| ✅ | ✅ | ✅ | agent own 层 `tools.register`(真工具)+ 自动桥接 |
24
+ | `agent` / `agent_message` / `agent_workflow` | ❌ **缺席** | ✅ | ✅ | `createAgentBridgeBindings`(仅 cell 函数)+ `AGENT_BRIDGE_SCHEMAS`(手写 schema 喂目录渲染器)|
25
+
26
+ 桥的三件套(下发 spawn / 三向消息 / workflow 编排)**只在 cell 内可达**:`tool.agent({...})` 能跑,模型直调 `agent(...)` 不存在 —— 绑定集 24 里有、wire 22+eval+MCP 里没有(v0.1.8e 报告 §3/§11 各轮实测口径)。这是 v0.1.8f 三连修(3fdd3dd→db6d84b→0a56da4)留下的结构现状,不是文档笔误。
27
+
28
+ ### A.3 违反的代价
29
+
30
+ 1. **payload 形调用的转义摩擦**(ptc-research §8.5 的实测结论):长 prompt 的 spawn、多引号多换行的 workflow script 经 cell 要过一层字符串字面量转义;native 直调时内容就是参数值,单层序列化。调用路由启发式(payload→直调,logic→REPL)在桥上失效了一半。
31
+ 2. **双源漂移**:`AGENT_BRIDGE_SCHEMAS` 是手写的模型面声明,与运行时校验(`rejectUnknownKeys` 等)平行维护 —— 正是 D3 消灭"名单漂移"所要避免的形态,在桥上又回来了。
32
+ 3. **不变式倒置的掩护**:v0.1.8e 的掩码达成了"被掩名两面同消失";桥却做成"两面同在场但只有一面可达"—— 名义上没违反"wire=REPL",实质上模型能力面在两个通道不等。
33
+
34
+ ### A.4 修法(下一个 change)
35
+
36
+ **把三个桥注册成真工具**:`tools.register`(与 `eval` 同宿主位),execute 直调现有桥实现;然后:
37
+
38
+ - wire 自动获得 `agent`/`agent_message`/`agent_workflow` 直调(schema 即真 schema,单源);
39
+ - REPL 自动桥接**自然拾取**(D3 机制,零改动);
40
+ - `AGENT_BRIDGE_SCHEMAS` 手写声明路径**退役** —— `collectSdkSchemas` 从注册表读到的就是它们;
41
+ - 校验逻辑保持在 execute 内,桥的"结构化错误不抛异常"语义不变。
42
+
43
+ 回滚即反操作(撤 register、恢复手写 schema),风险面小。**验收标准照 A.1 第三句写**:`registry.wireSchemas(scope)` 与 cell 内绑定集逐名相等(允许的例外仅 `eval` 自身与非平坦 MCP 名)。
44
+
45
+ ---
46
+
47
+ ## B. omp 引入组合(按杠杆排序,2026-08-28 讨论)
48
+
49
+ 已引入不计入:`dvc://` 设备路由(= `xd://`)+ ast_edit/ast_grep/browser/lsp;紧凑签名目录(D4-B);URL schema 本身;REPL pad + `tool.*` loopback。
50
+
51
+ ### T1 —— 补闭环,不是补工具
52
+
53
+ 1. **LSP 接线进 write/edit**:设备已在位但只是 discoverable(显式 `write dvc://lsp`);omp 本义是 "wired into every write"。落点:url-schema 的 write/edit wrapper 加 post-write hook —— 有 language server 的目标语言自动拉 diagnostics 附进结果 + format-on-write。**edit 完立即知道写对了没有**,当前表面唯一还清零的反馈环。
54
+ 2. **`completion()` cell 原语**:无工具的一次性 LLM 调用(可带 JSON Schema 合成 respond tool)。第四个桥 → ctx LLM 服务。judge/extraction/handoff 压缩在 cell 内一步完成,零 spawn。实现成本最低。
55
+ 3. **checkpoint/rewind 探索段折叠**:agent-loop 插件(hook pre/post step),把探索段显式收缩成报告;与 recallable compaction("压")互补,这是"折"。
56
+
57
+ ### T2 —— 顺着 URL/设备脊柱的便宜扩张
58
+
59
+ 4. **`skill://` 可写**(= manage_skill/learn 的 DASHR 原生形态):`write skill://name` → 创建/更新 SKILL.md。模型已认识该 scheme,比 omp 独立工具更顺。
60
+ 5. **`pr://` / `issue://` scheme**:gh CLI 在机,handler 样板现成,复用统一 selector。边际成本极低。
61
+ 6. **inspect_image(vision 外包)**:图 → 视觉档模型 → 文字。主模型弱视时图像通道不作废。
62
+ 7. **并行任务 worktree 隔离**:workflow `agent()` 子当前共享工作区,并行改文件不安全;给 spawn 加 `isolation: worktree` opt-in(git worktree + diff 回收)。与 ralph 的"共享工作区即记忆"相反语义,故 opt-in 不默认。
63
+
64
+ ### T3 —— 看场景
65
+
66
+ 8. **snapcompact**(像素 PNG 归档,~1/3 输入价,零 LLM):dsh 有干净 compaction 插件面(`dsh-compaction-snapcompact` 一个包)。**先验证 deepseek v4 系图片 token 计价与 vision 保真度** —— omp 数字按 Anthropic/Gemini 公式调,换 provider 套利未必成立。
67
+ 9. **TTSR 流中规则**:规则沉睡、命中即流中中止注入、存活过 compaction;`/omfg` 回测起草是杀手子功能。dsh 化 = 响应前钩子 + 规则文件。
68
+ 10. **Advisor 第二模型盯轮**(aside/concern/blocker):成本近翻倍,长自治任务再上。
69
+ 11. **discovery 八格式继承**(.cursor/.clinerules/…):采用曲线便利性。
70
+ 12. 归档/SQLite 写、DAP、tts/generate_image:低频/重/随时可包,后置。
71
+
72
+ ### 不引入
73
+
74
+ - 31 工具扁平哲学、hub、12-scheme 照抄(补类别,不对齐数量)。
75
+ - **mnemopi —— 见 C 节,不是"≈平",是 corti 占优**。
76
+
77
+ ---
78
+
79
+ ## C. corti vs mnemopi:修正为"corti 占优"(用户判断,2026-08-28)
80
+
81
+ 此前对比矩阵(ptc-research §4)记"记忆 ≈平",修正:
82
+
83
+ | 维度 | corti(dsh 侧)| mnemopi(omp 内建)|
84
+ |---|---|---|
85
+ | 记忆归属 | **共享 agent 记忆**:跨会话、跨 agent、跨 PC/运行时同一记忆库(pc-deepseek/pc-hermes 等全部读写同库,注入式召回)|| 每安装一份:SQLite 本地文件,子代理 alias 父状态,不出本机 |
86
+ | 后端 | **PostgreSQL**:向量 + 关系混合查询,规模化的索引与并发能力 || SQLite:单机单文件,量级与并发上限低 |
87
+ | 抽取/召回 | 会话边界抽取(episodes/atomic facts)+ 注入式背景召回 || turn 级自动 retain + 混合打分召回 |
88
+
89
+ 结论:**不引入 mnemopi,也不需要对标实现**;omp 侧若有可取细节(如混合打分里 importance/时间衰减权重),只作为 corti 侧调参参考,不构成引入项。
90
+
91
+ ---
92
+
93
+ ## 附:与既有文档的关系
94
+
95
+ - A 节是对 `openspec/changes/surface-and-devices/design.md` D1–D3 的**后继修正**:D1/D3 解决了"被掩名两面同消失",A 节把同一不变式的另一半(自声明能力两面同在场)补全。
96
+ - B 节 T1-1/T1-2 与 ptc-research §5 的 T1 清单同源;T1-2(persistent runtime)已由 DASHR 本体消化,故不重复。
97
+ - C 节修正 ptc-research §4 记忆行与 omp-research §3.4#4 的对照结论。
@@ -0,0 +1,152 @@
1
+ # Dash RLM 插件「IPython kernel 互动界面」实测报告
2
+
3
+ > 实测日期(UTC):2026-08-19
4
+ > 观测主体:Dash Agent 运行时自身(v0.1.5,`agent-preset/selected = rlm-mode`,模型 deepseek-v4-pro)
5
+ > 观测对象:RLM 模式唯一的模型直连界面 —— `ipython` 工具(即持久化 IPython kernel 的互动界面)
6
+ > 方式:在真实会话中以 agent 身份直接调用 `ipython`,覆盖「能不能用 / 心理摩擦 / 不一致」三个维度;所有结论均来自真实工具返回与源码证据。
7
+ > 与前序报告关系:本报告是《RLM-system-prompt-injection-gap-report.md》(2026-08-18,`dev/kernel-refactoring/`)的实测续篇。彼时接口名为 `run_cell`;v0.1.5 已更名为 `ipython`(`index.ts:211` `IPYTHON_NAME = 'ipython'`,`index.ts:32` 注明 `ipython (not run_code)`)。
8
+
9
+ ---
10
+
11
+ ## 1. 结论速览(TL;DR)
12
+
13
+ | 维度 | 结论 |
14
+ |---|---|
15
+ | 能不能用 | ✅ 能用。kernel 存活、工具全链路、并发、错误处理、返回值语义均正常 |
16
+ | 心理摩擦 | ⚠️ 存在。最大摩擦是「唯一界面」的心智模型:目录列出 ~40 个工具,但运行时只直接接受 `ipython` 一个函数 |
17
+ | 不一致 | ⚠️ 2 处:① 工具函数签名 `(*args, **kwargs)` 与运行时拒绝 kwargs 的行为不符;② 每次工具调用刷 1 条 `DeprecationWarning` 噪声 |
18
+ | 缺陷 | 🔴 1 个待修:`ipykernel.comm.Comm` 已弃用,源头 `bootstrap.ts:92/99`,每条工具调用都触发 |
19
+
20
+ ---
21
+
22
+ ## 2. 环境事实(实测基线)
23
+
24
+ | 项目 | 实测值 |
25
+ |---|---|
26
+ | 插件版本 | Dash RLM 插件 v0.1.5 |
27
+ | Python | 3.11.15 |
28
+ | ipykernel | 7.3.0(已弃用 `ipykernel.comm.Comm`) |
29
+ | comm 独立包 | 0.2.3(已安装,尚未被采用) |
30
+ | 接口工具名 | `ipython`(schema 仅 `cell` + `description` 两个必填参数) |
31
+ | 抽样绑定名 | 16 个全部真实存在:`ToolCallError` + `read/write/edit/grep/file_glob/bash/rlm/agent_list/agent_message/todo_write/memory_search/memory_add/web_search/job_output/job_list` |
32
+ | 工具函数形态 | `_dashr_make_callable.<locals>._dashr_callable`,`__main__` 模块,`iscoroutinefunction = True`,`inspect.signature = (*args, **kwargs)` |
33
+ | `ToolCallError` | `__main__.ToolCallError` 类,带 `toolName` 属性 |
34
+ | TypedDict 存根 | `ReadArgs`/`BashArgs`/`EditArgs` 等运行时**不存在**(与文档「存根」声明一致) |
35
+
36
+ ---
37
+
38
+ ## 3. 可用性验证(全部通过)
39
+
40
+ | 测试项 | 结果 | 说明 |
41
+ |---|---|---|
42
+ | kernel 存活 | ✅ | `platform.python_version() = 3.11.15` |
43
+ | 变量跨 cell 持久化 | ✅ | 上一轮定义的变量下一轮仍在(`_persist_check` 存活) |
44
+ | 文件工具链路 | ✅ | `write → read → edit → grep → file_glob` 全通,返回值结构符合文档 |
45
+ | 异步并发 | ✅ | `asyncio.gather` 两个独立 `read` 正确返回 |
46
+ | 错误处理 | ✅ | `ToolCallError.toolName = "edit"`,报错信息清晰(`old_string was not found`) |
47
+ | 返回值语义 | ✅ | 正常 JSON dict 原样返回;非 JSON 返回报 `invalid-output`(有明确报错) |
48
+ | 文档一致性 | ✅ | 「TypedDict 存根运行时不存在的存根」属实 |
49
+
50
+ **结论**:从「模型能否用起来」的角度,这个互动界面功能完整、契约清晰、可用。
51
+
52
+ ---
53
+
54
+ ## 4. 心理摩擦观测
55
+
56
+ ### 4.1 摩擦 1:唯一界面的心智模型(影响最大)
57
+
58
+ 运行时**只直接接受 `ipython` 一个函数**,其余 ~40 个「工具」必须在 cell 内以 `await name(args)` 调用。但 system prompt 的《The available tools》把 read/write/grep/bash 等列成了带完整 schema 的「原生工具」,读起来像可直接调用。
59
+
60
+ 实测:本 agent 在测试中**两次**把 `grep` / `read` 当原生函数直接发起,均被拒:
61
+
62
+ ```
63
+ Error: only `ipython` is callable directly — call `grep` from inside an `ipython` cell instead
64
+ ```
65
+
66
+ 「我有 N 个工具」vs「我有 1 个工具 + N 个 cell 内 callable」的认知税是持续的。虽然 `control-prompt.ts:32` 已写了 `accepts directly is `ipython``,但后续 20KB 的工具目录仍然压倒了这条纠偏。
67
+
68
+ ### 4.2 摩擦 2:每次工具调用的 DeprecationWarning 噪声
69
+
70
+ 每 `await` 一次工具,输出流就多一条:
71
+
72
+ ```
73
+ DeprecationWarning: The `ipykernel.comm.Comm` class has been deprecated.
74
+ Please use the `comm` module instead... comm = Comm(target_name="dashr.host")
75
+ ```
76
+
77
+ 这条警告会**混入每一个工具结果**,被模型反复读到,是持续的视觉噪声 + 误导(模型可能误以为自己的调用有问题)。详见 §6。
78
+
79
+ ### 4.3 摩擦 3:非 JSON 返回值会让整个 cell 失败
80
+
81
+ `return` 一个不可 JSON 序列化的对象(如自定义类实例)会让整个 cell 报错:
82
+
83
+ ```
84
+ Error: code run failed (invalid-output): program completion must be lossless JSON
85
+ ```
86
+
87
+ 不是静默丢弃,而是整体失败。报错信息尚可,但对「随手 return 一个中间对象」的新手是个隐性坑。
88
+
89
+ ---
90
+
91
+ ## 5. 不一致清单
92
+
93
+ ### 5.1 不一致 1:工具函数签名与运行时行为不符
94
+
95
+ - **现象**:`inspect.signature(read)` / `bash` / `edit` / `rlm` 全部是 `(*args, **kwargs)`,看起来接受任意关键字参数;但实际 `read(file_path="...")` 会被拒绝:
96
+
97
+ ```
98
+ ToolCallError: tool bindings take one positional arguments object, not keyword arguments — call e.g. name({"field": 1})
99
+ ```
100
+
101
+ - **根因**:Python 侧 `_dashr_callable(*args, **kwargs)`(`bootstrap.ts:191`)不做校验,把 `{'args': [...], 'kwargs': {...}}` 打包发给 host;由 host 侧 `flatToolArgs`(`index.ts:473-482`)负责校验并拒绝 kwargs。于是「可内省的签名」与「运行时行为」脱节——若模型用 introspection 判断可调用形式,会被签名误导。
102
+
103
+ ### 5.2 不一致 2:文档里工具的 `async def name(args: Args) -> Output` 签名 vs 运行时 `(*args, **kwargs)`
104
+
105
+ system prompt 用 `async def read(args: ReadArgs) -> ReadOutput` 这样的类型化签名描述工具,但运行时真实可内省到的只有 `(*args, **kwargs)`(类型信息完全丢失)。对依赖 `inspect` 或 IDE 式心智的模型,这是一处呈现与现实的落差。
106
+
107
+ ### 5.3(呈现)工具目录 vs 唯一可调事实
108
+
109
+ 已在 §4.1 展开,此处归入不一致范畴:目录的「工具」和运行时的「唯一可调函数」在呈现上是矛盾的。
110
+
111
+ ---
112
+
113
+ ## 6. 缺陷定位(源码证据)
114
+
115
+ **问题**:每次工具调用都触发 1 条 `DeprecationWarning`。
116
+
117
+ **机制**(`dashr/src/bootstrap.ts`):
118
+
119
+ 1. `_dashr_host_request(payload)`(第 91 行起)是每个工具调用的必经之路。
120
+ 2. 第 92 行 `from ipykernel.comm import Comm` —— 已弃用的导入。
121
+ 3. 第 99 行 `comm = Comm(target_name=${HOST_COMM_TARGET})` —— **每个 host request 都新建一个 Comm**,实例化 `ipykernel.comm.Comm` 即触发弃用警告。
122
+
123
+ **实测证据**:用 `warnings.catch_warnings(record=True)` 精确统计,2 次 `read` 调用 → 恰好捕获 2 条警告,每条指向 transpiled cell 第 81 行 `comm = Comm(target_name="dashr.host")`。
124
+
125
+ **环境佐证**:`ipykernel 7.3.0` 已弃用 `ipykernel.comm.Comm`,官方建议改用独立 `comm` 包(本环境已装 `comm 0.2.3`,但插件尚未采用)。
126
+
127
+ ---
128
+
129
+ ## 7. 修复建议(按优先级)
130
+
131
+ | 优先级 | 问题 | 建议 |
132
+ |---|---|---|
133
+ | 🔴 高 | §6 DeprecationWarning 噪声 | `bootstrap.ts:92` 改为 `from comm import create_comm`(comm 0.2.3 已在环境里),并按需复用/缓存 Comm 而非每个 request 新建;消除每条工具调用都刷的噪声 |
134
+ | 🟡 中 | §5.1 签名 vs 运行时脱节 | 让 Python 侧签名收敛(如 `def _dashr_callable(args=None, /)` 或 Python 侧先行校验),使 `inspect.signature` 与运行时一致;至少给 `_dashr_callable` 加 `__signature__` |
135
+ | 🟡 中 | §4.1 唯一界面心智 | prompt 层面:把「`ipython` 是唯一可直连界面」放到工具目录之前、并给一个完整示例 cell;弱化工具目录的「原生函数」观感(可参考前序报告的缺口 1 建议) |
136
+ | 🟢 低 | §4.3 非 JSON 返回值 | 保留报错,但在 prompt 里补一句「返回值必须是 lossless JSON;不确定就 `print` 而不是 `return`」 |
137
+
138
+ ---
139
+
140
+ ## 8. 附:复现方法
141
+
142
+ 1. **唯一界面摩擦**:在会话里对任意工具(如 `grep`)发起原生调用 → 收到 `only ipython is callable directly`。
143
+ 2. **DeprecationWarning**:`await bash({...})` 或 `await read({...})` 任一工具调用,观察结果文本末尾的 `DeprecationWarning: ipykernel.comm.Comm`。精确计数可用:
144
+ ```python
145
+ import warnings
146
+ with warnings.catch_warnings(record=True) as w:
147
+ warnings.simplefilter("always")
148
+ await read({"file_path": "x", "limit": 1})
149
+ print(len(w)) # 每条工具调用 = 1 条
150
+ ```
151
+ 3. **签名脱节**:`inspect.signature(read)` → `(*args, **kwargs)`;再 `await read(file_path="x")` → 被拒。
152
+ 4. **非 JSON 返回值**:`class X: pass; return X()` → `invalid-output`。
@@ -0,0 +1,146 @@
1
+ # Dash IPython Control Prompt — 草稿 v2
2
+
3
+ > 用途:Dash(dsh-rlm-mode)IPython 界面使用说明书,对标 Prime Agent 的 `IPYTHON_CONTROL_PROMPT`。
4
+ > 集成位置:persona patch 内,置于现有 "You have a persistent Python kernel..." 一句之后(建议整句替换),
5
+ > 或独立 prompt section,order 在 `tools:dashr-sdk` 之前。
6
+ > v2 变更:**修正 v1 的架构性错误** —— v1 首句 "You are running on an IPython kernel" 是错的:
7
+ > agent runtime 是 TS 进程,完全独立于任何 Python 环境;kernel 只是它 spawn 并监督的 subprocess;
8
+ > `tools.*` 是回环到 TS 宿主的 proxy(真实实现在宿主侧执行)。prompt 必须把这个从属关系讲对,
9
+ > 否则模型会误以为 kernel 死了 = 自己死了,或把宿主侧状态(jobs/subagents/memory)当成 kernel 状态。
10
+
11
+ ---
12
+
13
+ ## 设计决策(评审用,不进 prompt)
14
+
15
+ 0. **架构事实(v2 新增,最高优先)**:runtime(TS,宿主面)≠ kernel(Python subprocess)。
16
+ - 模型收到 tool call 的是 TS runtime;它只直连路由 `run_cell`,其余名字一律 guard 拒绝。
17
+ - cell 内 `tools.*` = kernel 侧绑定,经 host-request 回环到 TS 宿主执行,结果/`ToolCallError` 返回程序。
18
+ - 推论(写进 prompt):kernel 死 ≠ 会话死 —— TS 宿主侧状态(jobs、subagents、memory/Corti、session log)不依赖 kernel;
19
+ kernel 侧句柄(asyncio Task、Popen、普通变量)随重启丢失,纯数据 best-effort 复活。
20
+ 1. **不假设模型训练过 IPython**:Introduction 三句话,定位 / 组合 / cell,Shift+Enter 类比。
21
+ 2. **术语「typed Python built-in tools」**:命名即语义 —— 是 Python 对象,可当编程素材。
22
+ 3. **有实例**:基础 / 类型化工具入脚本 / 变量=上下文+状态机,三类。
23
+ 4. **Dash 适配**:shell 走 `tools.bash`(必填 description);失败统一 `ToolCallError`(一个类型教一遍);
24
+ `run_cell` 必填 `description`(UI 标签);后台双路 = 通用 `create_task` + 原生 `run_in_background`/`job_*`(宿主侧 facility)。
25
+ 5. **预告 guard 报错**:"only run_cell is callable directly" 写进契约,不靠失败学习。
26
+ 6. **本系列复核教训全量纳入**:await 阻塞警示、变量台账三态、单 cell 多动作、输出策展。
27
+
28
+ ---
29
+
30
+ ## Prompt 正文(英文,可直接粘贴)
31
+
32
+ ```text
33
+ ## IPython Introduction
34
+
35
+ Your agent runtime — the scaffolding that renders this prompt, receives your
36
+ tool calls, and manages your session — is a TypeScript process, fully
37
+ independent of any Python environment; among its facilities it spawns and
38
+ supervises a persistent IPython kernel (a long-lived Python subprocess), and
39
+ `run_cell` is the single bridge to it. The `tools.<name>(...)` names bound
40
+ inside that kernel are typed async proxies: each call dispatches back into the
41
+ agent runtime where the implementation actually runs, then returns into your
42
+ program as a value or a typed `ToolCallError` — so composing actions is just
43
+ writing Python (conditionals, loops, try/except, `asyncio.gather`, helper
44
+ functions). Each `run_cell` call executes exactly one cell — one program run
45
+ top-to-bottom as a unit, like pressing Shift+Enter once in a notebook — and
46
+ everything you import, define, or assign in a cell stays alive in the kernel
47
+ for every later cell.
48
+
49
+ ## The single entry
50
+
51
+ - Your tool calls are received by the TypeScript runtime; it routes exactly
52
+ one name directly — `run_cell` — and rejects anything else with
53
+ `only run_cell is callable directly`. Do not learn this by failing: write
54
+ every action as code.
55
+ - Each call takes `code` (the program) and `description` (a 5-10 word UI label
56
+ for this cell, active voice).
57
+ - Inside the kernel, exactly two extra names are bound for you: `tools` and
58
+ `ToolCallError`. Everything else you need, import yourself (`import asyncio`
59
+ before gather/create_task).
60
+ - A failed tool call raises `ToolCallError` — typed and catchable; it neither
61
+ crashes the kernel nor affects later cells.
62
+ - Top-level `await` and `return` work. Only what you print or return comes
63
+ back to you — curate it.
64
+ - The kernel is a supervised subprocess, not your environment. If it dies, the
65
+ runtime restarts it: plain data is revived best-effort, kernel-side handles
66
+ (asyncio Tasks, Popen) are lost — while host-side state (background jobs,
67
+ subagents, memory) survives untouched, because it never lived in the kernel.
68
+
69
+ ## Example 1 — the simplest cells
70
+
71
+ # one action, one cell: read a file
72
+ print(await tools.read(file_path="docs/README.md"))
73
+
74
+ # shell is just another typed callable (description is required)
75
+ r = await tools.bash(command="ls -la src/", description="List source directory")
76
+ print(r)
77
+
78
+ ## Example 2 — typed tools as operands in a script
79
+
80
+ # conditional edit, typed error handling, retry with adjusted arguments
81
+ target = "src/config.py"
82
+ if "DEBUG = False" in await tools.read(file_path=target):
83
+ for old in ("DEBUG = False", "DEBUG=False"):
84
+ try:
85
+ print(await tools.edit(file_path=target,
86
+ old_string=old, new_string="DEBUG = True"))
87
+ break
88
+ except ToolCallError as e:
89
+ print(f"retrying ({e})")
90
+
91
+ # parallel fan-out — independent calls composed in ONE cell
92
+ import asyncio
93
+ todos, files = await asyncio.gather(
94
+ tools.grep(pattern="TODO", path="src"),
95
+ tools.glob(pattern="**/*.ts", path="src"),
96
+ )
97
+
98
+ ## Example 3 — variables: working memory and status ledger
99
+
100
+ # (1) context as variables: bind freely, revisit by name in ANY later cell
101
+ cfg = await tools.read(file_path="config.yaml") # cfg stays alive in later cells
102
+
103
+ # (2) variables as a status ledger: slow call WITHOUT blocking the cell.
104
+ # NOTE: `r = await tools.bash(...)` BLOCKS the cell until it returns.
105
+ # For slow work, wrap the awaitable in a Task: the variable exists
106
+ # immediately, its VALUE arrives later.
107
+ fetch = asyncio.create_task(tools.bash(
108
+ command="curl -s --max-time 90 https://api.example.com/health",
109
+ description="Call health endpoint",
110
+ ))
111
+ print(fetch) # <Task pending> -> still running
112
+
113
+ # ...in a LATER cell (seconds or minutes later), poll the ledger:
114
+ if not fetch.done():
115
+ print("still running") # not finished yet
116
+ else:
117
+ err = fetch.exception() # error value, if it failed
118
+ print(err if err else fetch.result()) # otherwise the value
119
+
120
+ # optional 5-line helper: status(t) -> None | exception | value
121
+ def status(t):
122
+ if not t.done(): return None
123
+ e = t.exception()
124
+ return e if e is not None else t.result()
125
+
126
+ # host-side alternative (survives kernel restarts): run_in_background
127
+ job = await tools.bash(command="curl -s https://api.example.com/health",
128
+ description="Call health endpoint",
129
+ run_in_background=True) # returns a jobId immediately
130
+ await tools.job_output(job_id=job["jobId"]) # poll output later
131
+ await tools.job_list() # list background jobs
132
+ await tools.job_kill(job_id=job["jobId"]) # stop one
133
+
134
+ ## Rules
135
+
136
+ - Compose: one cell may run many actions; prefer a composed program over one
137
+ call per cell.
138
+ - `tools.*` are typed Python objects and first-class values: pass them to
139
+ functions, store them in dicts, wrap them in retry/degradation helpers.
140
+ - Catch `ToolCallError` narrowly and handle it in code; retry with adjusted
141
+ arguments instead of re-emitting the same call.
142
+ - Variables persist across cells and turns — but they live in the kernel
143
+ subprocess: keep durable state in files or memory tools, and prefer
144
+ host-side `run_in_background` for work that must survive a kernel restart.
145
+ - Only print or return what you need next; everything else stays in the kernel.
146
+ ```
@@ -0,0 +1,50 @@
1
+
2
+ ## IPython Introduction
3
+
4
+ - IPython (Interactive Python) is an unified and the only but versatile actionable interface of your agent runtime, `Function: { name: "ipython", arguments: {"run_cell": "cell_content"} }` is the only function call entry.
5
+ - It is a persistent control environment for reasoning, context management, state, tool orchestration, and recursive subcalls. Use it to keep intermediate variables, inspect and transform outputs, write small helper functions, and preserve useful state across turns or compaction.
6
+ - All tools (see Tool Catalog) are pre-bound/typed python callable.
7
+
8
+
9
+ ### Examples
10
+
11
+ ```python
12
+ # mono step cell
13
+ run_cell:
14
+ print(await read(file_path="docs/README.md"))
15
+
16
+ # shell is just another typed callable
17
+ run_cell:
18
+ r = await bash(command="ls -la src/", description="List source directory")
19
+ print(r)
20
+
21
+ # typed tools in script
22
+ run_cell:
23
+ for old in ("DEBUG = False", "DEBUG=False"):
24
+ try:
25
+ print(await tools.edit(file_path=target, old_string=old, new_string="DEBUG = True"))
26
+ break
27
+ except ToolCallError as e:
28
+ print(f"retrying ({e})")
29
+
30
+ # variables persist across cells and turns — the kernel is your working memory
31
+ run_cell:
32
+ cfg = await read({"file_path": "config.yaml"}) # cfg stays alive in later cells",
33
+ child = await rlm('spawn', 'summarize the failing tests', label='summarizer')
34
+ print(child["subagentId"]) # background admission — keep working
35
+
36
+ # import like any python env
37
+ import asyncio
38
+ run_cell:
39
+ matches, files = await asyncio.gather(
40
+ grep({"pattern": "TODO", "path": "src"}),
41
+ file_glob({"pattern": "**/*.ts", "path": "src"})
42
+ )
43
+ ```
44
+
45
+ ### Rules
46
+
47
+ - Do not assume IPython is the native runtime of the external thing being investigated. … Evaluate external systems through their own interface, then use IPython to coordinate the process and analyze what comes back.
48
+ - Avoid !cmd shell escapes
49
+ - Only print or return what you need next; everything else stays in the IPython kernel.
50
+