@sema-agent/server 7.145.0 → 7.147.0

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 (526) hide show
  1. package/MIGRATION.md +876 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +374 -99
  5. package/deploy/facts/CHANGES.json +25 -0
  6. package/deploy/facts/README.md +57 -0
  7. package/deploy/facts/lanes.json +61 -0
  8. package/deploy/facts/lanes.schema.json +57 -0
  9. package/deploy/facts/sandbox-rbac.json +113 -0
  10. package/deploy/facts/sandbox-rbac.schema.json +74 -0
  11. package/deploy/facts/sealed-key.json +9 -0
  12. package/deploy/facts/sealed-key.schema.json +52 -0
  13. package/deploy/sema-up/README.md +2 -2
  14. package/deploy/sema-up/chart/templates/_helpers.tpl +41 -0
  15. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +47 -7
  16. package/deploy/sema-up/chart/templates/server-v4pro.yaml +9 -4
  17. package/deploy/sema-up/chart/templates/server.yaml +9 -4
  18. package/deploy/sema-up/chart/values.yaml +8 -1
  19. package/deploy/sema-up/cluster-up.sh +2 -1
  20. package/deploy/sema-up/lib/flagshape.sh +4 -0
  21. package/deploy/sema-up/lib/lanes.json +2 -5
  22. package/deploy/sema-up/sema-up.sh +12 -9
  23. package/deploy/sema-up/stack/compose.yaml +5 -4
  24. package/deploy/sema-up/stack/sema-stack-cluster.yaml +34 -17
  25. package/dist/adoption/import-limits.d.ts +6 -0
  26. package/dist/adoption/import-limits.js +7 -0
  27. package/dist/adoption/import.d.ts +62 -0
  28. package/dist/adoption/import.js +107 -0
  29. package/dist/adoption/wire.d.ts +52 -0
  30. package/dist/adoption/wire.js +9 -0
  31. package/dist/approval-ask-machine.d.ts +2 -2
  32. package/dist/approval-card.d.ts +7 -16
  33. package/dist/approval-card.js +0 -4
  34. package/dist/approval-gate-currency.d.ts +1 -1
  35. package/dist/approval-gate-currency.js +1 -1
  36. package/dist/approval-reconciler.d.ts +1 -1
  37. package/dist/approval.d.ts +3 -3
  38. package/dist/audit.js +2 -2
  39. package/dist/bench/s1/live-deps.js +3 -1
  40. package/dist/boot/crash-last.js +2 -1
  41. package/dist/boot/deployment-mods.d.ts +11 -1
  42. package/dist/boot/deployment-mods.js +15 -2
  43. package/dist/boot/device-lane.js +7 -5
  44. package/dist/boot/engine-lease.d.ts +195 -22
  45. package/dist/boot/engine-lease.js +146 -20
  46. package/dist/boot/execution-env.d.ts +5 -0
  47. package/dist/boot/execution-env.js +57 -33
  48. package/dist/boot/execution-lane.d.ts +37 -9
  49. package/dist/boot/execution-lane.js +74 -20
  50. package/dist/boot/exit-boundary.d.ts +50 -0
  51. package/dist/boot/exit-boundary.js +32 -0
  52. package/dist/boot/exit-last.d.ts +20 -4
  53. package/dist/boot/exit-last.js +23 -5
  54. package/dist/boot/governance-seams.d.ts +2 -2
  55. package/dist/boot/governance-seams.js +1 -0
  56. package/dist/boot/leader.js +1 -1
  57. package/dist/boot/memory-boundary.d.ts +11 -11
  58. package/dist/boot/memory-boundary.js +3 -3
  59. package/dist/boot/memory-consolidation.d.ts +42 -41
  60. package/dist/boot/memory-consolidation.js +26 -28
  61. package/dist/boot/org-memory.js +3 -2
  62. package/dist/boot/parked-revive-gate.d.ts +1 -1
  63. package/dist/boot/parked-revive-gate.js +1 -1
  64. package/dist/boot/reapers.d.ts +12 -4
  65. package/dist/boot/reapers.js +40 -16
  66. package/dist/boot/resolve-spec.d.ts +2 -0
  67. package/dist/boot/resolve-spec.js +126 -123
  68. package/dist/boot/retention-lane.d.ts +4 -42
  69. package/dist/boot/runner-deps.js +1 -1
  70. package/dist/boot/session-faces.d.ts +1 -0
  71. package/dist/boot/session-faces.js +11 -3
  72. package/dist/boot/shutdown.js +27 -9
  73. package/dist/boot/stage-01-config.d.ts +1 -1
  74. package/dist/boot/stage-01-config.js +7 -3
  75. package/dist/boot/stage-02-web-admin-pull.d.ts +1 -1
  76. package/dist/boot/stage-02-web-admin-pull.js +10 -8
  77. package/dist/boot/stage-03-store-backend.d.ts +2 -1
  78. package/dist/boot/stage-03-store-backend.js +2 -2
  79. package/dist/boot/stage-04-stores.d.ts +2 -1
  80. package/dist/boot/stage-05-execution-env.d.ts +4 -1
  81. package/dist/boot/stage-05-execution-env.js +6 -6
  82. package/dist/boot/stage-06-runners.d.ts +5 -2
  83. package/dist/boot/stage-06-runners.js +12 -13
  84. package/dist/boot/stage-07-capability-layer.d.ts +5 -2
  85. package/dist/boot/stage-07-capability-layer.js +7 -7
  86. package/dist/boot/stage-08-reapers.d.ts +5 -2
  87. package/dist/boot/stage-08-reapers.js +3 -3
  88. package/dist/boot/stage-09-leader.d.ts +6 -2
  89. package/dist/boot/stage-10-http-server.d.ts +6 -2
  90. package/dist/boot/stage-10-http-server.js +5 -3
  91. package/dist/boot/stores.d.ts +10 -0
  92. package/dist/boot/stores.js +53 -26
  93. package/dist/boot/web-admin.js +3 -3
  94. package/dist/boot/workflow-orchestration.d.ts +1 -1
  95. package/dist/boot/workflow-orchestration.js +2 -1
  96. package/dist/capabilities/center-plugins.d.ts +2 -0
  97. package/dist/capabilities/center-plugins.js +6 -2
  98. package/dist/capabilities/composite-members.d.ts +4 -1
  99. package/dist/capabilities/composite-members.js +4 -1
  100. package/dist/capabilities/execution-lane.d.ts +7 -7
  101. package/dist/capabilities/execution-lane.js +2 -2
  102. package/dist/capabilities/hands-lane.d.ts +1 -1
  103. package/dist/capabilities/scenarios.d.ts +34 -20
  104. package/dist/capabilities/scenarios.js +13 -9
  105. package/dist/capabilities/settings-contract.d.ts +29 -0
  106. package/dist/capabilities/settings-contract.js +14 -0
  107. package/dist/capabilities/skills.d.ts +5 -5
  108. package/dist/capability-facts.d.ts +56 -1
  109. package/dist/capability-facts.js +59 -8
  110. package/dist/center-credential-renewer.js +3 -2
  111. package/dist/checkpoint-ctx-seal-m28.d.ts +1 -1
  112. package/dist/config-catalog.js +19 -9
  113. package/dist/config-invariants.d.ts +18 -3
  114. package/dist/config-invariants.js +36 -19
  115. package/dist/config-lkg.d.ts +1 -1
  116. package/dist/config-lkg.js +5 -6
  117. package/dist/config-provider.d.ts +2 -2
  118. package/dist/config-provider.js +2 -0
  119. package/dist/config-types.d.ts +228 -185
  120. package/dist/config.d.ts +74 -6
  121. package/dist/config.js +146 -64
  122. package/dist/degenerate-instrument.js +1 -1
  123. package/dist/deleted-session-ids.d.ts +19 -0
  124. package/dist/deleted-session-ids.js +26 -0
  125. package/dist/deploy-facts.d.ts +155 -0
  126. package/dist/deploy-facts.js +95 -0
  127. package/dist/deployment-shape.d.ts +21 -12
  128. package/dist/deployment-shape.js +24 -6
  129. package/dist/device-ws-hub.d.ts +48 -3
  130. package/dist/device-ws-hub.js +48 -2
  131. package/dist/env-facts.d.ts +7 -5
  132. package/dist/env-facts.js +10 -6
  133. package/dist/error-code-family.d.ts +35 -0
  134. package/dist/error-code-family.js +26 -0
  135. package/dist/execution-config-root.d.ts +5 -5
  136. package/dist/execution-config-root.js +5 -5
  137. package/dist/execution-env-purpose.d.ts +14 -0
  138. package/dist/execution-env-purpose.js +29 -0
  139. package/dist/execution-lane-caps.d.ts +49 -86
  140. package/dist/execution-lane-caps.js +52 -62
  141. package/dist/execution-placement.d.ts +18 -2
  142. package/dist/execution-placement.js +19 -0
  143. package/dist/file-history-disclosure.d.ts +2 -2
  144. package/dist/fleet/drain-entry.d.ts +46 -3
  145. package/dist/fleet/drain-entry.js +6 -0
  146. package/dist/fleet/fleet-bus.d.ts +2 -1
  147. package/dist/fleet/serving-lease.js +3 -4
  148. package/dist/history-anchor.d.ts +22 -0
  149. package/dist/history-anchor.js +14 -0
  150. package/dist/hooks/branch-transcript.d.ts +1 -1
  151. package/dist/hooks/branch-transcript.js +1 -0
  152. package/dist/hooks/classic-stations.d.ts +25 -8
  153. package/dist/hooks/classic-stations.js +29 -3
  154. package/dist/hooks/command-spawn.d.ts +1 -4
  155. package/dist/hooks/command-spawn.js +0 -11
  156. package/dist/hooks/hook-runner.d.ts +85 -24
  157. package/dist/hooks/hook-runner.js +326 -109
  158. package/dist/hooks/hook-source.d.ts +14 -2
  159. package/dist/hooks/hook-source.js +8 -0
  160. package/dist/http/active-run-conflict.d.ts +1 -1
  161. package/dist/http/active-run-conflict.js +1 -1
  162. package/dist/http/admission.js +11 -1
  163. package/dist/http/approval-sweeps.d.ts +2 -5
  164. package/dist/http/approval-sweeps.js +3 -2
  165. package/dist/http/checkpoint-ctx.d.ts +76 -71
  166. package/dist/http/checkpoint-ctx.js +40 -40
  167. package/dist/http/claim-latency.d.ts +10 -0
  168. package/dist/http/claim-latency.js +12 -0
  169. package/dist/http/dispatch.js +19 -3
  170. package/dist/http/durable-run-leg.d.ts +5 -0
  171. package/dist/http/durable-run-leg.js +3 -1
  172. package/dist/http/execution-context.d.ts +7 -1
  173. package/dist/http/execution-context.js +13 -0
  174. package/dist/http/health-self-report.d.ts +6 -2
  175. package/dist/http/health-self-report.js +3 -2
  176. package/dist/http/memory-compliance-failure-reply.d.ts +2 -2
  177. package/dist/http/memory-compliance-failure-reply.js +5 -15
  178. package/dist/http/memory-control-plane-corrupt-reply.d.ts +25 -0
  179. package/dist/http/memory-control-plane-corrupt-reply.js +19 -0
  180. package/dist/http/memory-store-refusal-reply.d.ts +3 -3
  181. package/dist/http/memory-store-refusal-reply.js +3 -0
  182. package/dist/http/owner-access.d.ts +11 -0
  183. package/dist/http/owner-access.js +7 -4
  184. package/dist/http/principal-gate.d.ts +28 -16
  185. package/dist/http/principal-gate.js +10 -19
  186. package/dist/http/request-guards.d.ts +2 -2
  187. package/dist/http/request-guards.js +5 -5
  188. package/dist/http/resume-legs.d.ts +29 -10
  189. package/dist/http/resume-legs.js +94 -56
  190. package/dist/http/rewind-files-reply.d.ts +34 -0
  191. package/dist/http/rewind-files-reply.js +36 -0
  192. package/dist/http/route-ctx.d.ts +43 -26
  193. package/dist/http/route-table.js +4 -0
  194. package/dist/http/routes/adoption.d.ts +11 -0
  195. package/dist/http/routes/adoption.js +80 -6
  196. package/dist/http/routes/agents-roster.js +1 -0
  197. package/dist/http/routes/approvals-assistant.js +7 -26
  198. package/dist/http/routes/background.js +1 -0
  199. package/dist/http/routes/capabilities.js +12 -3
  200. package/dist/http/routes/devices.js +1 -1
  201. package/dist/http/routes/diagnostics.d.ts +9 -14
  202. package/dist/http/routes/diagnostics.js +10 -20
  203. package/dist/http/routes/engine.d.ts +31 -0
  204. package/dist/http/routes/engine.js +81 -0
  205. package/dist/http/routes/memory-bundle.js +3 -0
  206. package/dist/http/routes/memory-consolidation.js +3 -5
  207. package/dist/http/routes/memory-origin.d.ts +2 -2
  208. package/dist/http/routes/memory-origin.js +2 -4
  209. package/dist/http/routes/memory-policy.js +5 -1
  210. package/dist/http/routes/notify-wake.js +2 -1
  211. package/dist/http/routes/observability.js +3 -6
  212. package/dist/http/routes/projects.d.ts +70 -0
  213. package/dist/http/routes/projects.js +86 -0
  214. package/dist/http/routes/rules.js +30 -2
  215. package/dist/http/routes/runs.js +15 -11
  216. package/dist/http/routes/session-sync.js +39 -35
  217. package/dist/http/routes/sessions-list.d.ts +1 -1
  218. package/dist/http/routes/sessions.d.ts +7 -1
  219. package/dist/http/routes/sessions.js +108 -2
  220. package/dist/http/routes/tasks.js +32 -8
  221. package/dist/http/routes/trace-usage.js +1 -1
  222. package/dist/http/run-concurrency-cap.d.ts +86 -0
  223. package/dist/http/run-concurrency-cap.js +54 -0
  224. package/dist/http/server.d.ts +31 -14
  225. package/dist/http/server.js +29 -3
  226. package/dist/http/stored-body-replay.d.ts +12 -7
  227. package/dist/http/stored-body-replay.js +28 -5
  228. package/dist/http/wire-types.d.ts +27 -13
  229. package/dist/index.d.ts +2 -2
  230. package/dist/index.js +1 -1
  231. package/dist/instruction-source-notice.d.ts +1 -1
  232. package/dist/json-body-limit.d.ts +8 -0
  233. package/dist/json-body-limit.js +2 -0
  234. package/dist/leader/diffout.d.ts +1 -1
  235. package/dist/local-data-root.d.ts +2 -2
  236. package/dist/memory-embedder-reembed.d.ts +38 -0
  237. package/dist/memory-embedder-reembed.js +20 -0
  238. package/dist/memory-incident-sink.d.ts +0 -28
  239. package/dist/memory-incident-sink.js +5 -0
  240. package/dist/memory-operator-faces.d.ts +9 -4
  241. package/dist/memory-scope-stores.d.ts +9 -7
  242. package/dist/memory-scope-stores.js +3 -14
  243. package/dist/memory-scope.d.ts +38 -2
  244. package/dist/memory-scope.js +33 -2
  245. package/dist/memory-store-refusal.d.ts +17 -4
  246. package/dist/memory-store-refusal.js +1 -1
  247. package/dist/memory-sync-client.d.ts +2 -0
  248. package/dist/memory-sync-client.js +13 -11
  249. package/dist/memory-sync.js +1 -1
  250. package/dist/model-compat.d.ts +35 -21
  251. package/dist/model-compat.js +49 -21
  252. package/dist/model-entry-refusal.d.ts +11 -1
  253. package/dist/model-entry-refusal.js +7 -0
  254. package/dist/namespaced-rule-name.d.ts +18 -1
  255. package/dist/namespaced-rule-name.js +12 -0
  256. package/dist/net-address.d.ts +53 -0
  257. package/dist/net-address.js +111 -0
  258. package/dist/observability/fail-open.d.ts +29 -17
  259. package/dist/observability/fail-open.js +29 -17
  260. package/dist/observability/metrics.d.ts +2 -0
  261. package/dist/observability/metrics.js +2 -0
  262. package/dist/observability/secret-env-scrub.d.ts +30 -4
  263. package/dist/observability/secret-env-scrub.js +15 -3
  264. package/dist/orchestration/workflow-agent-session-index.d.ts +7 -12
  265. package/dist/orchestration/workflow-agent-session-index.js +1 -6
  266. package/dist/orchestration/workflow-completion-inbox.d.ts +26 -6
  267. package/dist/orchestration/workflow-completion-inbox.js +63 -45
  268. package/dist/orchestration/workflow-notify-journal.js +2 -3
  269. package/dist/org-delivery-gate.js +1 -1
  270. package/dist/org-memory-admission.d.ts +5 -2
  271. package/dist/org-memory-admission.js +25 -10
  272. package/dist/parked-decide.d.ts +33 -26
  273. package/dist/parked-decide.js +36 -44
  274. package/dist/peer-directory.d.ts +6 -12
  275. package/dist/peer-directory.js +5 -1
  276. package/dist/plugins/approval-ask-store-file.d.ts +10 -2
  277. package/dist/plugins/approval-ask-store-file.js +37 -4
  278. package/dist/plugins/approval-ask-store-memory.d.ts +5 -3
  279. package/dist/plugins/approval-ask-store-memory.js +11 -2
  280. package/dist/plugins/approval-ask-store-sql.d.ts +3 -0
  281. package/dist/plugins/approval-ask-store-sql.js +3 -1
  282. package/dist/plugins/blob-backend.d.ts +38 -195
  283. package/dist/plugins/blob-backend.js +33 -229
  284. package/dist/plugins/caching-session-store.d.ts +12 -5
  285. package/dist/plugins/caching-session-store.js +3 -0
  286. package/dist/plugins/checkpoint-index-columns.d.ts +1 -1
  287. package/dist/plugins/checkpoint-index-columns.js +3 -2
  288. package/dist/plugins/checkpoint-store-face.d.ts +426 -0
  289. package/dist/plugins/checkpoint-store-face.js +152 -0
  290. package/dist/plugins/device-store-sql.d.ts +2 -2
  291. package/dist/plugins/device-store-sql.js +5 -10
  292. package/dist/plugins/execution-lane-rename-m40.d.ts +20 -0
  293. package/dist/plugins/execution-lane-rename-m40.js +10 -0
  294. package/dist/plugins/execution-placement-sql.d.ts +63 -88
  295. package/dist/plugins/execution-placement-sql.js +94 -54
  296. package/dist/plugins/file-run-store.d.ts +26 -18
  297. package/dist/plugins/file-run-store.js +64 -75
  298. package/dist/plugins/fork-routing-session-store.d.ts +6 -2
  299. package/dist/plugins/fork-routing-session-store.js +2 -2
  300. package/dist/plugins/imported-layer-root-retirement.d.ts +22 -11
  301. package/dist/plugins/imported-layer-root-retirement.js +4 -8
  302. package/dist/plugins/inbox-payload-column.d.ts +14 -0
  303. package/dist/plugins/inbox-payload-column.js +13 -0
  304. package/dist/plugins/local-checkpoint-store.d.ts +7 -2
  305. package/dist/plugins/local-checkpoint-store.js +13 -8
  306. package/dist/plugins/local-data-dir-modes-m39.d.ts +48 -0
  307. package/dist/plugins/local-data-dir-modes-m39.js +182 -0
  308. package/dist/plugins/local-engine-high-water.d.ts +11 -0
  309. package/dist/plugins/local-engine-high-water.js +59 -0
  310. package/dist/plugins/local-session-store.d.ts +119 -30
  311. package/dist/plugins/local-session-store.js +156 -53
  312. package/dist/plugins/local-task-attachment-store.d.ts +2 -0
  313. package/dist/plugins/local-task-attachment-store.js +10 -11
  314. package/dist/plugins/memory-embedder-fingerprint.d.ts +33 -10
  315. package/dist/plugins/memory-embedder-fingerprint.js +52 -12
  316. package/dist/plugins/memory-embedder.d.ts +1 -1
  317. package/dist/plugins/memory-embedder.js +2 -2
  318. package/dist/plugins/memory-engine-pg.d.ts +30 -6
  319. package/dist/plugins/memory-engine-pg.js +96 -214
  320. package/dist/plugins/memory-engine-sql-apply.d.ts +130 -0
  321. package/dist/plugins/memory-engine-sql-apply.js +358 -0
  322. package/dist/plugins/memory-engine-tidb.d.ts +38 -9
  323. package/dist/plugins/memory-engine-tidb.js +99 -230
  324. package/dist/plugins/memory-key-guards.d.ts +1 -1
  325. package/dist/plugins/memory-origin-law.d.ts +3 -3
  326. package/dist/plugins/one-time-migrations.d.ts +169 -83
  327. package/dist/plugins/one-time-migrations.js +295 -235
  328. package/dist/plugins/owner-only-file-mode.d.ts +29 -0
  329. package/dist/plugins/owner-only-file-mode.js +141 -0
  330. package/dist/plugins/permission-rule-store-file.d.ts +17 -3
  331. package/dist/plugins/permission-rule-store-file.js +20 -1
  332. package/dist/plugins/pg-pool.d.ts +2 -1
  333. package/dist/plugins/pg-pool.js +12 -5
  334. package/dist/plugins/release-version.d.ts +20 -0
  335. package/dist/plugins/release-version.js +28 -0
  336. package/dist/plugins/remote-env-adb.d.ts +20 -4
  337. package/dist/plugins/remote-env-adb.js +39 -11
  338. package/dist/plugins/remote-env-e2b.d.ts +12 -3
  339. package/dist/plugins/remote-env-e2b.js +6 -4
  340. package/dist/plugins/remote-env-host.d.ts +9 -1
  341. package/dist/plugins/remote-env-host.js +6 -5
  342. package/dist/plugins/remote-env-k8s.d.ts +5 -2
  343. package/dist/plugins/remote-env-k8s.js +15 -4
  344. package/dist/plugins/remote-env-local-docker.d.ts +4 -1
  345. package/dist/plugins/remote-env-local-docker.js +3 -3
  346. package/dist/plugins/remote-env-ssh.d.ts +8 -2
  347. package/dist/plugins/remote-env-ssh.js +26 -24
  348. package/dist/plugins/remote-shell.d.ts +59 -30
  349. package/dist/plugins/remote-shell.js +175 -62
  350. package/dist/plugins/retention-declaration.d.ts +37 -0
  351. package/dist/plugins/retention-declaration.js +9 -0
  352. package/dist/plugins/retention-deployment.d.ts +126 -0
  353. package/dist/plugins/retention-deployment.js +88 -0
  354. package/dist/plugins/retention-lane-store-sql.d.ts +74 -4
  355. package/dist/plugins/retention-lane-store-sql.js +103 -1
  356. package/dist/plugins/retention-sql.d.ts +29 -0
  357. package/dist/plugins/retention-sql.js +8 -0
  358. package/dist/plugins/run-store-sql.js +2 -2
  359. package/dist/plugins/schema-consistency.d.ts +0 -6
  360. package/dist/plugins/schema-consistency.js +1 -2
  361. package/dist/plugins/schema-ensure-legs.d.ts +1 -1
  362. package/dist/plugins/schema-ensure-legs.js +13 -3
  363. package/dist/plugins/session-ext-table.d.ts +74 -0
  364. package/dist/plugins/session-ext-table.js +121 -0
  365. package/dist/plugins/session-fork.d.ts +31 -9
  366. package/dist/plugins/session-fork.js +3 -2
  367. package/dist/plugins/session-placement.d.ts +4 -2
  368. package/dist/plugins/session-row-sql.d.ts +8 -7
  369. package/dist/plugins/session-row-sql.js +15 -7
  370. package/dist/plugins/session-store-sql.d.ts +131 -0
  371. package/dist/plugins/session-store-sql.js +298 -0
  372. package/dist/plugins/session-workspace-cell.d.ts +2 -2
  373. package/dist/plugins/session-workspace-cell.js +2 -2
  374. package/dist/plugins/shared-memory-store-sql.d.ts +10 -0
  375. package/dist/plugins/shared-memory-store-sql.js +13 -13
  376. package/dist/plugins/snapshot-bucket-presence.js +1 -1
  377. package/dist/plugins/sql-driver.d.ts +23 -7
  378. package/dist/plugins/sql-driver.js +41 -8
  379. package/dist/plugins/sql-errors.d.ts +5 -2
  380. package/dist/plugins/sql-errors.js +4 -0
  381. package/dist/plugins/sql-json-column.d.ts +5 -1
  382. package/dist/plugins/store-backend.d.ts +46 -16
  383. package/dist/plugins/store-backend.js +192 -69
  384. package/dist/plugins/store-contracts.d.ts +10 -13
  385. package/dist/plugins/store-contracts.js +1 -9
  386. package/dist/plugins/store-sweep.d.ts +63 -0
  387. package/dist/plugins/store-sweep.js +48 -0
  388. package/dist/plugins/task-attachment-store.d.ts +6 -8
  389. package/dist/plugins/task-attachment-store.js +15 -10
  390. package/dist/plugins/task-list-store-sql.d.ts +1 -1
  391. package/dist/plugins/text-column-capacity.d.ts +21 -0
  392. package/dist/plugins/text-column-capacity.js +5 -0
  393. package/dist/plugins/tidb-pool.d.ts +3 -2
  394. package/dist/plugins/tidb-pool.js +20 -32
  395. package/dist/plugins/user-input-ledger-sql.d.ts +1 -1
  396. package/dist/prompt-screen.d.ts +6 -9
  397. package/dist/prompt-screen.js +2 -5
  398. package/dist/read-face-posture.d.ts +6 -6
  399. package/dist/read-face-posture.js +2 -2
  400. package/dist/request-value-closure.d.ts +5 -5
  401. package/dist/request-value-closure.js +1 -1
  402. package/dist/rules-consent.d.ts +48 -27
  403. package/dist/rules-consent.js +147 -23
  404. package/dist/run-cancel-context.d.ts +4 -1
  405. package/dist/run-cancel-context.js +2 -1
  406. package/dist/run-local.js +17 -13
  407. package/dist/runs.d.ts +48 -30
  408. package/dist/runs.js +27 -17
  409. package/dist/runtime-caps-resolver.d.ts +15 -8
  410. package/dist/runtime-caps-resolver.js +14 -10
  411. package/dist/sandbox-egress-policy.js +6 -11
  412. package/dist/sandbox-rbac-rules.d.ts +88 -0
  413. package/dist/sandbox-rbac-rules.js +80 -0
  414. package/dist/sealed-key-layout.d.ts +16 -0
  415. package/dist/sealed-key-layout.js +3 -0
  416. package/dist/sealed-key.js +8 -6
  417. package/dist/security.d.ts +43 -9
  418. package/dist/security.js +9 -2
  419. package/dist/sema-config-dir.js +2 -2
  420. package/dist/server-secret-env.d.ts +2 -0
  421. package/dist/service-version.d.ts +2 -1
  422. package/dist/service-version.js +2 -2
  423. package/dist/session-error-wire.d.ts +1 -1
  424. package/dist/session-incarnation.d.ts +8 -5
  425. package/dist/session-notify-door.d.ts +5 -3
  426. package/dist/session-resident-background.d.ts +110 -0
  427. package/dist/session-resident-background.js +88 -0
  428. package/dist/session-row.d.ts +1 -1
  429. package/dist/session-sync-kernel.d.ts +53 -14
  430. package/dist/session-sync-kernel.js +70 -1
  431. package/dist/session-sync.d.ts +5 -3
  432. package/dist/session-workspace.d.ts +12 -2
  433. package/dist/session-workspace.js +6 -0
  434. package/dist/sighup-idle.d.ts +9 -7
  435. package/dist/sighup-idle.js +4 -5
  436. package/dist/spec-fields.d.ts +16 -0
  437. package/dist/spec-fields.js +7 -0
  438. package/dist/store-posture.d.ts +2 -1
  439. package/dist/stored-request-env-credentials-m38.d.ts +3 -0
  440. package/dist/stored-request-env-credentials-m38.js +8 -0
  441. package/dist/stored-request-hooks-m20.d.ts +3 -2
  442. package/dist/stored-request-hooks-m20.js +19 -14
  443. package/dist/stored-request-prompt-profile-m30.d.ts +1 -1
  444. package/dist/stores-sql/blob-index-sql.d.ts +148 -0
  445. package/dist/stores-sql/blob-index-sql.js +154 -0
  446. package/dist/{plugins → stores-sql}/checkpoint-store-sql.d.ts +14 -428
  447. package/dist/{plugins → stores-sql}/checkpoint-store-sql.js +43 -220
  448. package/dist/{plugins → stores-sql}/file-history-store-sql.d.ts +23 -11
  449. package/dist/{plugins → stores-sql}/file-history-store-sql.js +23 -18
  450. package/dist/{plugins → stores-sql}/mailbox-store-sql.d.ts +2 -2
  451. package/dist/{plugins → stores-sql}/mailbox-store-sql.js +4 -4
  452. package/dist/{plugins → stores-sql}/pg-session-storage.d.ts +27 -67
  453. package/dist/{plugins → stores-sql}/pg-session-storage.js +82 -207
  454. package/dist/{plugins → stores-sql}/retention-store-sql.d.ts +123 -217
  455. package/dist/{plugins → stores-sql}/retention-store-sql.js +66 -190
  456. package/dist/{plugins → stores-sql}/roster-store-sql.d.ts +2 -2
  457. package/dist/{plugins → stores-sql}/roster-store-sql.js +1 -1
  458. package/dist/{plugins → stores-sql}/session-event-payload.d.ts +1 -1
  459. package/dist/{plugins → stores-sql}/session-event-payload.js +1 -1
  460. package/dist/stores-sql/session-policy-store-sql.d.ts +1 -1
  461. package/dist/stores-sql/session-write-hooks.d.ts +59 -0
  462. package/dist/stores-sql/session-write-hooks.js +2 -0
  463. package/dist/{plugins → stores-sql}/tidb-session-storage.d.ts +10 -11
  464. package/dist/{plugins → stores-sql}/tidb-session-storage.js +10 -21
  465. package/dist/{plugins → stores-sql}/tidb-session-store.d.ts +35 -112
  466. package/dist/{plugins → stores-sql}/tidb-session-store.js +73 -169
  467. package/dist/{plugins → stores-sql}/tool-result-store-sql.d.ts +3 -3
  468. package/dist/{plugins → stores-sql}/tool-result-store-sql.js +6 -6
  469. package/dist/stores-sql/windowed-session-storage.d.ts +56 -0
  470. package/dist/stores-sql/windowed-session-storage.js +61 -0
  471. package/dist/{plugins → stores-sql}/workflow-journal-store-sql.d.ts +7 -12
  472. package/dist/{plugins → stores-sql}/workflow-journal-store-sql.js +3 -8
  473. package/dist/task-a2a.d.ts +7 -1
  474. package/dist/task-a2a.js +15 -14
  475. package/dist/task-cwd.d.ts +11 -3
  476. package/dist/task-cwd.js +17 -1
  477. package/dist/task-mcp.d.ts +10 -2
  478. package/dist/task-mcp.js +13 -16
  479. package/dist/task-settings.d.ts +68 -8
  480. package/dist/task-settings.js +63 -6
  481. package/dist/tool-approval.d.ts +23 -10
  482. package/dist/tool-approval.js +10 -6
  483. package/dist/trace/core-keyset-guard.d.ts +17 -10
  484. package/dist/trace/engine-notice-wire.d.ts +1 -1
  485. package/dist/trace/engine-notice-wire.js +4 -0
  486. package/dist/trace/exit-seat.d.ts +29 -1
  487. package/dist/trace/exit-seat.js +4 -0
  488. package/dist/trace/project.d.ts +33 -16
  489. package/dist/trace/project.js +21 -9
  490. package/dist/trace/redact.d.ts +13 -2
  491. package/dist/trace/redact.js +347 -78
  492. package/dist/trace/self-minted-wire.d.ts +587 -16
  493. package/dist/trace/self-minted-wire.js +215 -9
  494. package/dist/trace/sema-provenance.d.ts +45 -41
  495. package/dist/trace/sema-provenance.js +81 -10
  496. package/dist/trace/task-notification-facets.d.ts +70 -2
  497. package/dist/trace/task-notification-facets.js +71 -1
  498. package/dist/trace/wire-projection-faces.d.ts +39 -3
  499. package/dist/trace/wire-projection-faces.js +27 -2
  500. package/dist/unread-request-members.d.ts +66 -0
  501. package/dist/unread-request-members.js +41 -0
  502. package/dist/usage-analytics.d.ts +4 -0
  503. package/dist/user-input-ledger.d.ts +2 -2
  504. package/dist/web-admin-client/apply-effective.js +22 -26
  505. package/dist/web-admin-client/candidate-gate.d.ts +35 -3
  506. package/dist/web-admin-client/candidate-gate.js +53 -5
  507. package/dist/web-admin-client/center-request.d.ts +4 -1
  508. package/dist/web-admin-client/center-request.js +2 -1
  509. package/dist/web-admin-client/effective-keys.js +0 -1
  510. package/dist/web-admin-client/facade.d.ts +1 -1
  511. package/dist/web-admin-client/http-client.d.ts +1 -0
  512. package/dist/web-admin-client/http-client.js +17 -9
  513. package/dist/web-admin-client/restart-signal.js +5 -2
  514. package/dist/web-admin-client/skills-mcp.d.ts +20 -6
  515. package/dist/web-admin-client/skills-mcp.js +26 -21
  516. package/dist/web-admin-client/types.d.ts +34 -49
  517. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +2 -2
  518. package/package.json +22 -13
  519. package/scripts/drain-check-local.mjs +8 -55
  520. package/scripts/drain-check.mysql.sql +3 -40
  521. package/scripts/drain-check.pg.sql +3 -40
  522. package/skills/update-config.md +1 -1
  523. package/wire-changes/server@7.146.0.json +3221 -0
  524. package/wire-changes/server@7.147.0.json +813 -0
  525. package/dist/approval-batch-census.d.ts +0 -27
  526. package/dist/approval-batch-census.js +0 -27
package/USAGE.md CHANGED
@@ -61,18 +61,23 @@ docker compose up --build --scale service=4 # 4 个无状态 worker,nginx
61
61
 
62
62
  > 🔴 7.96.0 起根 `docker-compose.yml` 的 `REMOTE_EXEC`(执行车道)是**必填**:不设 ⇒ `docker compose config` 当场失败(此前静默起成一个没有手的 worker)。单人自用 `REMOTE_EXEC=host`(手落在 worker 容器里);多人 / 生产选隔离车道 `e2b`(配 `E2B_API_KEY`)或 `k8s`(`K8S_SANDBOX_IMAGE`、`K8S_NAMESPACE`〔没有内置缺省〕都是必需件,未设、空串或只含空白一律按缺 ⇒ 起服拒;`K8S_API_URL` 与 `K8S_TOKEN` 也要设;根 compose 透传前两名〔空串透传 = 按缺,要填〕,后两名要在 compose override 里加进服务的 `environment`,或改走随包 chart `deploy/sema-up`)。
63
63
 
64
- > 🔴 **`REMOTE_EXEC` 的形是部署声明的有序车道表**(契约 `runtime.execution.lanes` 渲成 env 的形:逗号分隔、首项是缺省车道;单词 = 一条车道的表,读法与以前逐字同)。每段逐字是一个车道词(不 trim、不折大小写),不许空段、不许同一车道出现两次;写着契约退役车道值(如 `remote-docker`)⇒ 拒句给出去向。**本引擎每个进程只装一条车道的执行座**:表里不止一条(`REMOTE_EXEC=k8s,device`)⇒ 起服拒,拒句点名全部声明的车道与出路(一台引擎一条车道:`REMOTE_EXEC=k8s`,其余车道各起一台)—— 不只服务首项而让其余车道无声地什么都不跑。
64
+ > 🔴 **`REMOTE_EXEC` 的形是部署声明的有序车道表**(契约 `runtime.execution.lanes` 渲成 env 的形:逗号分隔、首项是缺省车道;单词 = 一条车道的表,读法与以前逐字同)。每段逐字是一个车道词(不 trim、不折大小写),不许空段(只含空白也算空)、不许同一车道出现两次,`none`(不给工具执行环境)只能独成一表;拒句一次列出全部问题,以契约的车道表规则句收尾;写着契约退役车道值(如 `remote-docker`)⇒ 拒句给出去向。**本引擎每个进程只装一条车道的执行座**:表里不止一条(`REMOTE_EXEC=k8s,device`)⇒ 起服拒,拒句点名全部声明的车道与出路(一台引擎一条车道:`REMOTE_EXEC=k8s`,其余车道各起一台)—— 不只服务首项而让其余车道无声地什么都不跑。
65
65
 
66
- > 🔴 **7.104.0 起多租户姿态不许跑不分租户的执行车道**:`REQUIRE_PRINCIPAL=true` ∧ 最终生效的车道在执行车道能力表上不分租户 ⇒ **拒启**。按表不按词,今天覆盖 `host` / `ssh` / `adb`(所有任务落同一个共享目标:worker 宿主 / 一台 SSH 对端 / 一台 ADB 设备)。判的是合成后的车道(env 的 `REMOTE_EXEC`、`CONFIG_PROVIDER=local` 而 `REMOTE_EXEC` 未设的隐式 host、local 形 `config.d/remote-exec.json` 的覆写,在中心拉取与任何配置应用之前合成)。拒因(逐字,以 host 为例):`REQUIRE_PRINCIPAL=true (multi-tenant posture) with the "host" execution lane runs every tenant's Bash on one shared target — this lane does not separate tenants; set REMOTE_EXEC to e2b / k8s / local-docker / device, or unset REQUIRE_PRINCIPAL for a single-user box`。车道 × 租户形:
66
+ > 🔴 **7.104.0 起多租户姿态不许跑不分租户的执行车道**:`REQUIRE_PRINCIPAL=true` ∧ 最终生效的车道在执行车道能力表上不分租户 ⇒ **拒启**。按表不按词,今天覆盖 `host` / `ssh` / `adb`(所有任务落同一个共享目标:worker 宿主 / 一台 SSH 对端 / 一台 ADB 设备)。判的是合成后的车道(env 的 `REMOTE_EXEC`、local 形 `config.d/remote-exec.json` 的声明或覆写,在中心拉取与任何配置应用之前合成;7.147.0 起两处都没有 ⇒ 拒启)。拒因(逐字,以 host 为例):`REQUIRE_PRINCIPAL=true (multi-tenant posture) with the "host" execution lane runs every tenant's Bash on one shared target — this lane does not separate tenants; set REMOTE_EXEC to e2b / k8s / local-docker / device, or unset REQUIRE_PRINCIPAL for a single-user box`。车道 × 租户形:
67
67
  >
68
68
  > | 车道 | 单用户 | 多租户(`REQUIRE_PRINCIPAL=true`) | `READ_FACE` 缺席时的读面缺省(7.133.0 起车道行声明) |
69
69
  > |---|---|---|---|
70
- > | `host`(显式 / `CONFIG_PROVIDER=local` 隐式 / 文件覆写) | ✅ | ❌ 拒启(7.104.0 起) | 单用户 `open`(来源 `posture`) |
70
+ > | `host`(env 显式 / 文件声明或覆写) | ✅ | ❌ 拒启(7.104.0 起) | 单用户 `open`(来源 `posture`) |
71
71
  > | `ssh`(一台常驻对端) | ✅ | ❌ 拒启(7.104.0 起) | 单用户 `open`(来源 `posture`) |
72
72
  > | `adb`(一台设备) | ✅ | ❌ 拒启(7.104.0 起) | 单用户 `open`(来源 `posture`) |
73
73
  > | `e2b` / `k8s` / `local-docker`(每任务一只沙箱) | ✅ | ✅ | **`open`**(来源 **`lane`**,单用户 / 多租户同;车道行显式声明)。🔴 **挂载告诫**:把宿主目录(`hostPath` / bind mount)或共享卷挂进沙箱的部署,`open` 面上这些宿主文件对模型零问可读 —— 显式 `READ_FACE=roots`(env 恒赢)或给它们加读拒行(`READ_DENY_PATTERNS`) |
74
74
  > | `device`(每人自己的设备) | ✅ | ✅ | 单用户 `open`(来源 `posture`);多租户 不铸 = 引擎默认 `roots` |
75
- > | 未点名(云形进程内 stub,零手带工具、非手带工具照常执行) | ✅(`REQUIRE_EXECUTION_LANE=true` 可改成拒启) | ✅(同左) | 单用户 `open`(来源 `posture`);多租户 不铸 = 引擎默认 `roots` |
75
+ > | 未点名(7.147.0 起) | ❌ 拒启(拒句一行给修法;旧拒启旋钮 `REQUIRE_EXECUTION_LANE` 已退役,设了即拒启) | ❌ 拒启 | — |
76
+ > | `none`(声明「不给智能体的工具执行环境」:执行环境工厂与执行环境座都不给,零手带工具、非手带工具照常执行;`GET /v1/capabilities` 报 `none`;7.146.0 及以前「非 local 没写车道」的进程内桩改名而来) | ✅ | ✅ | 单用户 `open`(来源 `posture`);多租户 不铸 = 引擎默认 `roots` |
77
+
78
+ > 🔴 **硬断(自 7.147.0 起)**:每个部署都要显式声明自己最终生效的执行车道 —— env `REMOTE_EXEC`(`CONFIG_PROVIDER=local` 形下 `config.d/remote-exec.json` 也算);没有声明 ⇒ 拒启,拒句以 `No execution lane is declared` 起、一行给出照旧行为的修法(`CONFIG_PROVIDER=local` 不再隐含 `host`,非 local 未设也不再静默落进「不挂手」的进程内桩)。「不给智能体的工具提供执行环境」写成车道词 `none`,取代词表外的状态名 `in-process`(wire 五处换值,零别名:`GET /v1/capabilities` 的 `executionLane.provider`、`GET /v1/sessions/:id/execution-placement` 的 `lane`、409 `execution_lane.not_served` 与 409 `execution_lane.tree_bound` 的 `boundLane`、403 `execution_lane_not_allowed` 的 `extra.lane`);`REQUIRE_EXECUTION_LANE` 已退役(设了任何非空值 ⇒ 拒启,拒句指向 `REMOTE_EXEC`),自己拼的清单或拷过随包部署产物的 env 里写着它的,升级前要去掉。库里或数据根里记着 `in-process` 的会话 / 审批行由一次性迁移 M40 在起服时改成 `none`:停全部旧副本、再起 7.147.0,不可滚动混跑;SQL 后端上判活窗(`REAP_RUN_STALE_SEC`)内还有 run 在心跳 ⇒ 起服拒 `config.execution_lane_rename_refused`,拒句给出可再起的时刻;守卫只看心跳,没有在跑的 run 的空闲旧副本它看不见 —— 停全部旧副本是运维的动作(细节与回滚见 MIGRATION 7.147.0 节「执行车道恒点名」一条)。web-admin 渲染 env 的中心 worker,须先停渲 `REQUIRE_EXECUTION_LANE`、同时让每个 worker 的 env 带 `REMOTE_EXEC`,再升到 7.147.0。照旧行为的一行修法:7.146.0 里非 local 且未设车道的 ⇒ 7.147.0 写 `REMOTE_EXEC=none`,`CONFIG_PROVIDER=local` 且未设的 ⇒ 写 `REMOTE_EXEC=host`(7.146.0 及以前不认 `none` 这个词,别提前写)。
79
+
80
+ > **设备 WS 未鉴权握手的 per-remote 帽(`DEVICE_MAX_PREAUTH_PER_REMOTE`,缺省 8)按真实对端计**:缺省按 socket 对端计 —— 引擎在反代 / 托管入口 / 公司 NAT 之后时,全员同一个 socket 对端,共用这 8 个名额。把**自己的**反代写进 `DEVICE_TRUSTED_PROXY_CIDRS`(CIDR,逗号分隔;缺省空 = 不读任何代理头):socket 对端落在名单内才读 `X-Forwarded-For`,取自右向左第一个不在名单内的地址作帽键;名单外的对端一律不采代理头;请求头行数到顶(100 行)/ 头缺席 / 坏形 / 全在名单内 ⇒ 退回 socket 对端 + 一行 warn `device_preauth_forwarded_unusable`。名单每项是 IP 地址或 CIDR(裸地址 = 单台);名单里的来源能改帽键,所以只放自己控制的反代;前缀 0、主机位非零、不是 IP 地址或 CIDR 的项、空项拒启。不读 `Forwarded` / `X-Real-IP`,不支持 PROXY protocol。全局帽 64 不变。这根旋钮的语义行与缺省句也由 `GET /v1/config/catalog` 报出(域 `orchestration`,安全轴)。
76
81
 
77
82
  > 🔴 **`REMOTE_EXEC=device` 必须声明设备入口 `DEVICE_RELAY_URL`(无缺省)**。两种合法值:设备连不到引擎自己的地址(引擎在中继 / 网关之后)时设备该拨的那一个 `ws://` / `wss://` 绝对地址(逐字上 `GET /v1/capabilities` 的 `deviceExecutor.relayUrl`);或字面 `none`(没有中继,设备按引擎地址 + `deviceExecutor.wsPath` 拨号,能力面不带 `relayUrl`)。缺席 / 空串 / 只含空白 ⇒ 起服拒,拒在车道必需 env 门(与 `K8S_NAMESPACE` 等其余车道必需键同一句:`required env DEVICE_RELAY_URL is unset, empty or whitespace-only`);写了却不合文法 ⇒ 起服拒,拒句点名两种合法值(规则文字取自契约包)。`none` 而根会话上还有设备绑定 ⇒ 起服 warn `device_relay_withdrawn_with_bindings`(`bindings` = 绑定条数,不带会话 id)。撤中继声明的回滚序:先恢复引擎自己的入口,再把值改成 `none`。
78
83
 
@@ -169,6 +174,7 @@ ANTHROPIC_API_KEY=sk-ant-… # 可选:ANTHROPIC_BASE_URL / ANTHROPIC_VERSION /
169
174
  - **会话自动标题与 hooks 的 `prompt` / `agent` 条目走哪条线路,和主请求读同一只判据**:`provider="anthropic"` 的模型只在 anthropic 路由装上(配了 `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN`)时走云 `/v1/messages`;
170
175
  没装(如 `MODEL_PROVIDER=anthropic` 却只配了 `MODEL_GATEWAY_BASEURL` + `MODEL_API_KEY`)时主请求落网关,这两个面的廉价直连调用同样落网关的 openai 兼容线路(`/chat/completions`),
171
176
  端点与凭据照网关的来 —— 此前它们按 `provider` 字面去找一条没装的 anthropic 路由而整条拒发,会话列表没有自动标题。网关端点也解析不到才拒(`config.model_route_endpoint_missing`)。
177
+ - **已知限制:hook 的 LLM 出口不拦明文非回环(会话自动标题与 hooks 的 `prompt` 条目的直连调用)**:这两个面的请求由本服务自己发,发送前过引擎的路由门(凭据 ↔ 端点配对、来源绑定),但那一道门不问「经什么链路发」—— 目录条目的 `baseUrl`(没写时落到所在腿的根:网关腿是 `MODEL_GATEWAY_BASEURL`,anthropic 腿是 `ANTHROPIC_BASE_URL`)写成 `http://<非回环主机>` 且带 key,请求照带着 key 经明文链路发出,**今天不拒**。引擎的明文判官到货前由部署自审:被这两个面选中的目录条目(或它们落到的腿的根)只写 `https://` 或回环地址。判官到货后本条改为拒(届时 MIGRATION 另写升级前做)。
172
178
  - **预览与自动标题只读你自己写的那段文字**:会话列表的 `objectivePreview`、run / leader 行的 `objective_preview`、自动标题取材、收件箱 `objective` 与任务视图 `name` 读同一张闭集 ——
173
179
  引擎注入的 `<system-reminder>` / `<task-notification>` / `<attachment>` 块,以及壳铸进用户轮的**本地命令回显**(`<command-name>` / `<command-message>` / `<command-args>`、`<local-command-stdout>` / `<local-command-stderr>` / `<local-command-caveat>`、
174
180
  `<bash-input>` / `<bash-stdout>` / `<bash-stderr>` / `<bash-exit-code>`、`<user-memory-input>`)都不算你写的:`/compact` 一类本地命令之后的下一条提交,预览与标题取的是随后那句话,不再是回显;只有回显、没有你的话 ⇒ run / leader 行的预览是空串(任务名折成 `(notification turn)`),会话列表的预览回落到更早的一句、都没有则缺席(`null`)。
@@ -307,12 +313,22 @@ MODEL_DEGRADE_ON=rate_limit,breaker_open # 可选,反应式触发器子集;词
307
313
  只有 `qwen-chat-template`(`chat_template_kwargs.enable_thinking=false`)关得掉,而 `MODEL_EXTRA_BODY`
308
314
  穿不过引擎的 OpenAI 保留键表 —— 不设它,这类部署每一轮都白烧一段思考且读数上看不出来。它与
309
315
  `MODEL_REASONING_EFFORT_LEVELS` 铸进**同一个** `compat` 对象(两根旋钮同时设两键都在)。
310
- - 🔴 **逐模型声明(目录腿):`models.models[].compat`**(settings-schema ≥1.10.0;7.69.0 消费)——
311
- 同一张 `OpenAICompletionsCompat` 五键(`thinkingFormat` / `reasoningEffortLevels` / `maxTokensField` /
312
- `requiresReasoningContentOnAssistantMessages` / `supportsReasoningEffort`)可以**按模型**写在
313
- `config.d/models.json` 或配置控制面的目录条目上,逐键盖过上面两根 env 缺省(BL-8:没写的键仍继承 env)。
314
- **坏形/坏词/未知键 ⇒ 丢掉这条 `compat` 声明、模型本体保留**,并打一条 `model_compat_dropped`
316
+ - 🔴 **逐模型声明(目录腿):`models.models[].compat`**(settings-schema ≥1.10.0;7.69.0 消费;7.147.0 起按线路读)——
317
+ 键集由条目的 `api` 定:`openai-completions` 条目是 `OpenAICompletionsCompat` 五键(`thinkingFormat` / `reasoningEffortLevels` / `maxTokensField` /
318
+ `requiresReasoningContentOnAssistantMessages` / `supportsReasoningEffort`);`anthropic-messages` 条目是 `AnthropicMessagesCompat` 五键
319
+ (`thinkingMode`:`budget` | `adaptive`、`effortLevels`、`supportsTemperature` / `contextManagement` / `interleavedThinking` 三个布尔;7.146.x 及更早这一线路的 `compat` 整只丢)。
320
+ 可以**按模型**写在 `config.d/models.json` 或配置控制面的目录条目上,逐键盖过上面 env 缺省(BL-8:没写的键仍继承 env);
321
+ env 缺省只铸 openai 线路的键,只盖同一线路的条目(anthropic 条目不继承它们)。
322
+ **坏形/坏词/未知键(含写了别的线路的键)⇒ 丢掉这条 `compat` 声明、模型本体保留**,并打一条 `model_compat_dropped`
315
323
  点名 warn(引擎回落到自己的推断 = 今天的行为);一条写坏的声明**不会**让这只模型或整套目录消失。
324
+ - 🔴 **逐档思考声明(目录腿):`models.models[].thinkingLevelMap`**(settings-schema 20.0.0;7.147.0 消费)—— 这只端点每一档思考发什么值:
325
+ 键是 `off` / `minimal` / `low` / `medium` / `high` / `xhigh` / `max`,值是非空串(那一档发给端点的拼写)或 `null`(这一档端点不支持),没写的档引擎照自己的推断。
326
+ 本服务原样交给引擎(不补缺省、不另判),`GET /v1/models` 的行原样带它;openai-completions / responses 条目上引擎据它发请求、答 `supportsThinkingOff` / `supportedEffortLevels`
327
+ (`null` 档不进 `supportedEffortLevels`;`openai-completions` 条目的 `compat.thinkingFormat` 缺席或是 `openai` 时,`off` 写成非空串 ⇒ 关得掉,写 `null` 或不写 ⇒ 最低一席是 `low`;
328
+ 其余 `thinkingFormat` 关不关得掉由引擎按方言自判,不读 `off`)。⚠️ **anthropic-messages 条目上当前引擎(core 7.65.0)整个不读这张表**:原样带、零效果 ——
329
+ `null` 档照旧在选项集里、请求照发,`off: null` 不改「关得掉」;那条线上要「关不掉」写 `compat.thinkingMode: "adaptive"`,要收窄档位写 `compat.effortLevels`。
330
+ 形由契约包判:未知键 / 非对象 / 空串 / 非串非 `null` ⇒ **那一条被丢**(读腿点名);被 `roles` / `atMention.only` / `tierGroups` 引用的条目坏了 ⇒ 契约包把整个 `models` 域读成缺省,本服务据此**整份拒候选**(停在上一份好配置;本地 `config.d` 起服回落 env)。
331
+ env 腿(`AGENT_MODEL_*`)没有这一席。
316
332
  - 反应式降级 brain 包在**最外层**;fallback brain 用自己的凭据(decorator 清掉主模型的 per-call key 防外泄给别的 provider)。**7.106.0+**:
317
333
  降级目标的自有凭据按**启动那一代的身份**取值(env 变量名 / 托管密钥),与目标端点同代 —— 同名变量换值热生效;目录换 key 名字 / 重贴托管密钥 / 换端点
318
334
  要按 `/health.restart.reasons ∋ degrade-route` 重启。**坑**:`MODEL_DEGRADE_TO` 最好别和被限流的是同一网关/账号,否则反应式切过去照样撞同一个 rate_limit。
@@ -392,6 +408,18 @@ MODEL_DEGRADE_ON=rate_limit,breaker_open # 可选,反应式触发器子集;词
392
408
  是引擎账本 —— 别拿它做自动重试/取消的机器判据。
393
409
  - **per-principal 累计配额**:用 `AsyncLocalStorage` 把 principal 透传到 cost tracer,所以**委派子任务的花费也算到发起人头上**。**配了 `SESSION_BACKEND=mysql` 时自动跨副本共享**(`cost_quota` 表,写后聚合的**原子自增** `micro=micro+delta`,对齐固定窗,最终一致——多副本花费 SUM 到一起、不丢增量);否则 in-memory per-replica 计数(单副本兜底;窗形与 SQL 腿同一个时钟对齐桶)。启动日志 `costQuota` 字段回显 `shared(mysql)`/`in-process`/`off`。跨副本是最终一致(flush 间隔内峰值可能略超,由**硬 per-task `maxCostUsd` 兜底**)。
394
410
  - **`RATE_LIMIT_RPM` 请求限流同样自动跨副本**(`SESSION_BACKEND=mysql` 时,`rate_limit` 表,与配额共用 `WriteBehindCounter`;启动日志 `rateLimit` 回显)。注意:写后聚合 = **软限流**(边界上短暂略超 OK,适合公平/热调用方防护);要**硬合规上限**得另走 CAS/原子计数,不靠写后聚合——和断路器跨副本同款权衡。
411
+ - **在飞 run 并发帽 `RUN_MAX_CONCURRENT` / `RUN_MAX_CONCURRENT_PER_PRINCIPAL`**(整数,缺省 `0` = 关,升级零变化;负数 / 小数 / 非数 boot 拒启)。`RATE_LIMIT_RPM` 限的是「每分钟提交几条」,不是「此刻有几条在跑」;一条 run 可以跑几十分钟,
412
+ 共享一台 GPU 或一只小模型网关的部署(调用方自己限并发不可靠)要的是后一个量:
413
+ - **数什么**:**本副本**此刻在飞的 run —— 与排空读数(`drainState.inflight` 里的 run 部分)同一份在飞集:后台 run、同步 / 流式腿,以及有 run 行的续跑腿都占一格。副本之间不共享,各副本各数各的。
414
+ `RUN_MAX_CONCURRENT` 是全局帽;`RUN_MAX_CONCURRENT_PER_PRINCIPAL` 数同一**已验提交主体**(与成本配额门同键 —— 自报头不进键)的在飞数。
415
+ - **判哪几扇门**:**新提交**的四个口 —— `POST /v1/runs`、A2A `message/send`、`POST /v1/tasks`、`POST /v1/tasks/stream`。排在幂等重放**之后**:同 `Idempotency-Key` / 自带 `taskId` 的重试拿回自己的回执,不吃新 429。
416
+ 续跑、唤醒、审批 / 问答作答**不判**(它们占格但不被拒 —— 拒一张已经在等人的卡的作答只会把它卡死),所以帽闸的是**新提交**,不是在飞总数的硬上限:一条停驻的 run 续跑回来时,在飞数可以越过帽。
417
+ `POST /v1/leader` 的后台 run 不在这份在飞集里,本帽不数它也不判它。同键的并发重复提交若赶在首发还在受理(尚未落行、尚未合流)的窗里到达,按新提交判:帽满时与速率 / 配额门同形,可能拿到一次 429,按 `Retry-After` 重投即拿回首发的回执。
418
+ - **超帽的形**:`429`,`errorCode: "limit.rate_exceeded"`(沿用现有限流码,不新铸一枚)+ 体里机读 `reason: "run_concurrency_cap"` + `retryAfterSec: 5` + 头 `Retry-After: 5`(头体同值;是退避提示,不是承诺 ——
419
+ 在飞的 run 可能再跑很久)。拒在建行、认领会话与 SSE 头之前(流式口的 `429` 也是干净的 JSON);`POST /v1/runs` 与 A2A 口的帽排在受理漏斗之后(与 lease / 治理窗的 `429` 同位),拒时漏斗已为新会话做的登记不撤。消费端把它当「稍后再来」,要区分来源就读 `reason`(其余 `limit.rate_exceeded` 的产地不带 `reason`)。
420
+ 每次拒打一行 `run_concurrency_cap_refused`(`scope` = `global` | `principal`,在飞数与帽)。
421
+ - **无主体请求只受全局帽**:无头 service 凭据、`REQUIRE_PRINCIPAL=false` 的单用户部署没有主体可分账,按主体帽对它不判。
422
+ - **并发一阵提交不越帽**:放行即占一枚席位、run 登记进在飞集那一拍放席位,同一瞬间到的 N 条提交恰放行帽允许的条数。没有 run store 的部署上同步 / 流式腿不进在飞集,它们的席位活到请求结束,帽照样数得到。
395
423
 
396
424
  **可选 — 生成调参透传 + 退化打捞(core 1.59/1.60)**
397
425
  ```bash
@@ -470,6 +498,10 @@ MODEL_CODE_ROLES=default,subagent # 不设=全中立;仅这些角色在「
470
498
  ⚠️ **口径别混**:「受限」是**会话级的这一个声明**;请求键 `memoryWrite:false` 铸出的只读**平面**(以及 org 层默认只读、双根非写面)**保持原样的 adopt-on-read**——它只是不 harvest,照旧看得见盘上的新内容。
471
499
  ⚠️ **core 5.26.0 起,remote 执行车道上的 `# Memory` 写指令被撤**(沙箱手带够不着 host 记忆根;读与注入不变)。**判据是「`REMOTE_EXEC` 有没有设」,不是那个旗**——凡设了 `REMOTE_EXEC`(含 `host`)且这次任务真有记忆面,写指令就被撤。因此被打到的**不止**显式 `MEMORY_ENGINE_REMOTE_LANE=allow` 的部署,还包括:① `MEMORY_ENGINE_BACKEND=pg|mysql` 的 DB 记忆面(它不受那个旗管辖——旗只管 file 引擎,库是持久真身,任何车道上都照常点亮)= 最常见的云形;② `REMOTE_EXEC=host` + file 引擎(文件平面就在本机,但 core 的执行环境判决仍是 remote)。若该车道确实够得着记忆根,`MEMORY_PERSISTENCE_CAPABLE=true` 是官方恢复路径(启动日志 `memory_remote_lane_write_instruction_dropped` 会点名这一条);够不着就表 `false`,让只读状态对模型说明白。
472
500
  另:记忆**没有写工具**,模型是用普通文件工具往记忆根写的——所以记忆根必须落在任务的 fs 授权边界内。结构性够不着时启动会 warn 并给出三条改法(把根挪进 workspace / 经 `additionalDirectories` 授权 / 声明 `MEMORY_PERSISTENCE_CAPABLE=false` 让披露诚实)。⚠️ **「挪根」用哪个旋钮按记忆后端分腿**:file 引擎是 `MEMORY_ENGINE_DIR`;`MEMORY_ENGINE_BACKEND=pg|mysql` 的根是 `<数据根>/memory-work`(数据根 = `LOCAL_DATA_ROOT`/`AGENT_DATA_DIR` 族),那两条腿**不读** `MEMORY_ENGINE_DIR`——启动 warn 的文案自己会按腿指对旋钮。
501
+ ⚠️ **两条 SQL 记忆腿声明 core 的 `admitsLandings`(7.146.0 起)**:把它们交给 core 记忆引擎、自己驱动记忆整合的嵌入方,整合 apply 把写闸判决(`admit`)与整合计划的持有者核对(`confirm`)交给后端
502
+ (本服务自己的整合阀门 `MEMORY_CONSOLIDATION_DRIVER` 在这两条腿上照旧拒启,见下文该旋钮);后端在写任何一条之前按最终拼写(含撞名后缀、改名 / 移动留下的旧拼、删除移走的拼)一次问全,所有的问都在第一笔写之前(拒 ⇒ 一字不写;最多 4 次 —— 轮数上限与超限的码是引擎的问询圈,与 file 腿同一只 —— 仍在动 ⇒ `memory.landing_unstable`,一字未写);开始写之后落点才被别的副本变成没问过的拼写的那一条报冲突不写、下次 apply 再问;`confirm` 在每条写语句之前同步调。
503
+ **已知限制**:SQL 记忆腿的 confirm 在写语句前同步调,不持事务锁;要事务内验 fence 等包里的腿(7.64.0 核心存储包)。
504
+ 🔴 **会话的记忆写家(7.146.0 起,`GET /v1/sessions/:id/memory-status` 的可选键 `writeHome` / `planes`)**:会话最近写入的写域的 canonical 绝对目录 —— 引擎给的、字节原样(壳不要自拼:名字带哈希后缀;本服务不拼不折不择一)。`writeHome` 是**店绑定**的键:带 `?scope=<层键>` 时是那一层住的店的引擎答案、顶层 `writeHome`;不带 `?scope=` 时顶层 `writeHome` 是引擎在店之间择好的会话级一个家(点名家的店里最近写入的那只的家;任一店的写家读不出、两店同一时刻点名、或没有店点名 ⇒ 缺席,引擎不铸替身;单店部署 = 那只店自己的),逐店明细里每只店引擎答的值原样在 `planes: [{ plane, writeHome? }]`(每只店一行)。**只在单用户 `host` 车道上来**(= `GET /v1/capabilities` 的 `projectContext` 为真:手在本 worker 的盘上、部署只有一个主人);托管 / 沙箱 / 设备 / 无手车道两键都不来(那是 worker 宿主的路径,不是调用方的文件系统)。缺席的成因不止一个:部署不兑现(`projectContext` 为假)、引擎不答(没有源命名写域 / 源读不出 / 记忆库布局还不能派生家,此时行里没有 `writeHome`)、不带层座时引擎不择(另一只店的写家读不出 / 两店同刻)—— 都不是「老 server」。
473
505
  - **记忆店根搬了家 ⇒ 拒启(7.126.0 / 引擎 7.49.0 起,file 记忆后端)**:店根的物理路径记在店的控制面里(`root-anchor.json`),每次打开都比对。目录被搬走、
474
506
  或换成指向别处的链接之后,起服**拒**(码 `memory.target_outside_store`;不降级成「记忆暗」—— 店还在,只是搬了家,静默降级会让这台部署在没人知道的情况下丢掉全部记忆)。
475
507
  拒句点名控制面记下的路径与现在解析到的路径,并给出两条出路:**① 把目录放回原处**;**② 在本服务的目录里跑一次重锚**:
@@ -493,18 +525,22 @@ MODEL_CODE_ROLES=default,subagent # 不设=全中立;仅这些角色在「
493
525
  | `MEMORY_EMBEDDER_DIM` | 缺省 | 声明维度(**正整数**,如 `1024`)。必须与模型真实维度一致 |
494
526
  | `MEMORY_EMBEDDER_TIMEOUT_MS` | `30000`(`[1000, 300000]`) | 单次 embed 的活性上限。挂死的端点会把整条记忆写入路径吊住 |
495
527
  | `MEMORY_EMBEDDER_API_KEY` | 缺省 | 可选 Bearer(自托管 TEI/vllm/ollama 通常不需要;公有云端点需要) |
528
+ | `MEMORY_EMBEDDER_REEMBED` | 缺省 | **清列重嵌的确认口令**,值 = 要确认的那个身份 `<model>/<dimensions>`(维度在**最后一个** `/` 之后,模型名本身可带 `/`,如 `BAAI/bge-m3/1024`)。只在下条指纹门判「对不上」时起作用:在场且恰好点名本次配置的 `MODEL`/`DIM` ⇒ 清掉向量列、按这个身份重嵌一次;缺席 ⇒ 对不上就拒启、一行不清。形不对(`true` / `1` 这类开关形一律不是口令)或对不上本次配置 ⇒ 拒启;三键不全时单独在场同上面的半配拒 |
496
529
 
497
530
  ⚠️ **半配 = 拒启**:上表前三键是一个整体,少任何一件都启动报错并**点名缺哪几键**。静默忽略的后果不是「功能缺席」而是「operator 以为向量面开着」——检索照常出结果(词面档),差异只在答案质量里,零日志。同理:只配 `TIMEOUT`/`API_KEY` 而三键不全、或 `MEMORY_ENGINE=off` 却配了 embedder,都是死旋钮,一律拒启。
498
- ⚠️ **维度守卫是拒启/抛错,不是 warn**:向量列写错维度是**静默毒库**——本仓 pg 记忆表的 embedding 列是 `jsonb`(无维度约束),错长度向量既不会让 DB 报错,也不会让检索报错(那些行只是悄悄退回词面档)。所以坏 `DIM`(非正整数)启动即拒;运行期端点返回的向量长度与 `DIM` 不符,embedder **直接抛错**,绝不截断/补零。换 `MODEL`/`DIM` 的**清空由指纹门自动做**(见下条),不必手动清。
531
+ ⚠️ **维度守卫是拒启/抛错,不是 warn**:向量列写错维度是**静默毒库**——本仓 pg 记忆表的 embedding 列是 `jsonb`(无维度约束),错长度向量既不会让 DB 报错,也不会让检索报错(那些行只是悄悄退回词面档)。所以坏 `DIM`(非正整数)启动即拒;运行期端点返回的向量长度与 `DIM` 不符,embedder **直接抛错**,绝不截断/补零。换 `MODEL`/`DIM` 时**起服拒、不自动清**(指纹门,见下条):要清列重嵌须显式给确认口令 `MEMORY_EMBEDDER_REEMBED`。
499
532
  ⚠️ **embedder 出故障的方向**:端点 5xx/超时会让当次记忆**写入**报一条 `io error: …` 冲突(不写坏向量),检索腿则退回词面档继续服务——即「坏了退回 lexical」,不是「坏了写坏数据」。故障有专门信号:metric `memory_embed_failed_total{backend}` + 日志 `memory_embedder_failed`(只看 PatchReport 里的冲突会把供应商故障误读成并发改动)。
500
533
  ⚠️ **只对新写入的条目生效(没有回填腿)**:开启前就存在的记忆条目 embedding 列是空的,只有被再次写入时才会补上向量;这些行在检索里落**词面档**。🔴 **词面档不是「排名靠后」,是有门槛**:与查询**零词交集**的行被直接**丢出结果集**(词面距离在无交集时无定义,检索当场跳过该行),而带向量的行有余弦读数可用、**不会被这道门丢掉**(前提是 embedder 当时也把**查询**嵌出来了;embedder 故障那一刻查询侧拿不到向量,整轮检索退回词面档,于是零交集的行连同它们一起被丢)。所以没有向量的行对**改述 / 同义 / 跨语言**这类查询**检索不到**——那恰恰是向量档买的东西。要立刻全量生效,得自己重写一遍语料。
501
- 🔴 **换 `MODEL`/`DIM` 的行为:下次 boot 自动清空向量列(embedder 指纹门,7.15.0 之后的下一个发布起)**。本服务在 `agent_memory_engine_meta` 单行元表里记住当前 embedder 的**指纹** `{model, dimensions}`(明文,便于排障;**endpoint 不进指纹**——换域名/代理不换语义空间)。每次启动比对一次:
502
- - **相等** ⇒ 零动作、零写;
503
- - **不等 / 元表还没有这一行而库里已有向量 / 那一行读不出来**(手改、回滚残留)⇒ **先把 `agent_memory_engine_entry.embedding` 整列清成 NULL,再写新指纹**(次序不可换:反过来若中途崩溃,新指纹已记而旧向量还在,此后每次 boot 都判「相等」,残留永不复检);
504
- - 启动日志 `memory_embedder_identity_changed` 带**旧值/新值/清了几行/耗时 ms/原因**(`changed` | `unattributed` | `unreadable`),首次记录指纹是 `memory_embedder_identity_armed`;比对腿**自身失败一律拒启**(吞掉 = 门在场却没跑成)。⚠️ 这条行按「identity 变了」发,**不是**按「清了几行」发:当时库里恰好没有向量可清(上一轮已清过 / 缺席窗内被写成 NULL)照样发,`cleared: 0` —— 换 embedder 这件事在启动日志里不该无痕。真正零响亮的只有「相等」那一档。⚠️ **这句承诺以「进程活到把日志发出来」为界**:清列与写元表是两条各自提交的语句,日志在两步都完成之后才发,所以进程恰好在清完之后崩溃(或被杀),下次启动会按当时的真实状态重判(报「首次记录指纹、没有向量可清」,或者元表已写完就直接判「相等」不发声)——**那一次清掉了多少行会从日志里丢失**。数据面不受影响(次序钉保证的就是这个:向量已 NULL、不会残留旧空间),丢的只是审计行;要精确到行数的审计请以 DB 侧的变更记录为准。
505
- - 清空**只丢可再生的向量缓存**(正文/frontmatter 分毫不动);清完**不回填**,行被下次写入时自然按新模型重嵌,期间检索退回词面档。🔴 **代价按上面那条读**:退词面档 = 与查询零词交集的行**检索不到**(不是排到后面),所以清空之后、重嵌之前,那些行对改述/同义/跨语言查询是**不可达**的。⚠️ **误配代价诚实说**:把 `MODEL` 改错一次再改回来,向量**不会自动回来**——只随行被再次写入才重嵌;对写完就不动的记忆库,误配一次 = 向量面实质长期缺席(正文零损失,检索退 lexical,零交集查询够不着)。要立刻恢复只能自己重写一遍语料(或接受词面档)。
534
+ 🔴 **换 `MODEL`/`DIM` 的行为:下次 boot 拒启,向量一行不动;清列重嵌只在运维显式确认后做(embedder 指纹门)**。本服务在 `agent_memory_engine_meta` 单行元表里记住当前 embedder 的**指纹** `{model, dimensions}`(明文,便于排障;**endpoint 不进指纹**——换域名/代理不换语义空间)。每次启动比对一次:
535
+ - **相等** ⇒ 零动作、零写;元表还没有这一行且库里没有向量 ⇒ 只记下指纹(`memory_embedder_identity_armed`);
536
+ - **对不上**,三态:`changed`(记下的与配置的不等)/ `unattributed`(元表还没有这一行而库里已有向量)/ `unreadable`(那一行读不出来:手改、回滚残留)⇒ **缺省拒启**,一行不清、元表不写;拒句点名所处的态、记下的与配置的身份和出路:
537
+ ① `changed` 才有的一条 —— 把 `MEMORY_EMBEDDER_MODEL` / `MEMORY_EMBEDDER_DIM` 改回记下的身份;② **显式确认重嵌** —— 设 `MEMORY_EMBEDDER_REEMBED=<本次配置的 model>/<dim>`(拒句里给出可逐字照抄的那一条),**停机**(`scale 0`)后再起;③ 撤掉 `MEMORY_EMBEDDER_*`,服务以词面档起来,存量向量原样留着(回头配回同一身份可续用,见下面「没配」一条)。
538
+ 为什么缺省是拒启:清列**不可逆**(行只在下次被写时才重嵌),拒启**可逆**;换模型 / 改维度的一次手滑不该在起服时就丢掉全部向量。
539
+ - **确认了**(口令恰好点名本次配置的身份)⇒ **先把 `agent_memory_engine_entry.embedding` 整列清成 NULL,再写新指纹**(次序不可换:反过来若中途崩溃,新指纹已记而旧向量还在,此后每次 boot 都判「相等」,残留永不复检)。口令绑定身份,所以它天然是一次性的:新指纹记下之后下一次起服判「相等」,口令不再有东西可确认(留着无害,可随时撤);之后再换一次模型,留着的旧口令对不上新配置 ⇒ 起服拒,不会替下一次换模型静默清列。
540
+ - 已确认的清列,启动日志 `memory_embedder_identity_changed` 带**旧值/新值/清了几行/耗时 ms/原因**(`changed` | `unattributed` | `unreadable`),`note` 点名确认口令;首次记录指纹是 `memory_embedder_identity_armed`;比对腿**自身失败一律拒启**(吞掉 = 门在场却没跑成)。⚠️ 这条行按「identity 变了」发,**不是**按「清了几行」发:当时库里恰好没有向量可清(上一轮已清过 / 缺席窗内被写成 NULL)照样发,`cleared: 0` —— 换 embedder 这件事在启动日志里不该无痕。真正零响亮的只有「相等」那一档。⚠️ **这句承诺以「进程活到把日志发出来」为界**:清列与写元表是两条各自提交的语句,日志在两步都完成之后才发,所以进程恰好在清完之后崩溃(或被杀),下次启动会按当时的真实状态重判(报「首次记录指纹、没有向量可清」,或者元表已写完就直接判「相等」不发声)——**那一次清掉了多少行会从日志里丢失**。数据面不受影响(次序钉保证的就是这个:向量已 NULL、不会残留旧空间),丢的只是审计行;要精确到行数的审计请以 DB 侧的变更记录为准。
541
+ - 清空(已确认时)**只丢可再生的向量缓存**(正文/frontmatter 分毫不动);清完**不回填**,行被下次写入时自然按新模型重嵌,期间检索退回词面档。🔴 **代价按上面那条读**:退词面档 = 与查询零词交集的行**检索不到**(不是排到后面),所以清空之后、重嵌之前,那些行对改述/同义/跨语言查询是**不可达**的。⚠️ **误配的代价**:把 `MODEL` 改错一次,起服就拒,改回来即照常起 —— 向量一行不丢;只有在**给了确认口令**之后,清掉的向量才**不会自动回来**(只随行被再次写入才重嵌;对写完就不动的记忆库 = 向量面实质长期缺席,正文零损失,检索退 lexical,零交集查询够不着,要立刻恢复只能自己重写一遍语料)。
506
542
  - ⚠️ **换 embedder 请停机换:先 `scale 0`,再起**。指纹门管的是 **boot 面**;**滚动升级窗**里新副本清完列之后,仍在服役的旧副本还在按**旧**模型写向量,滚动结束后表里混着两个空间而元表已是新指纹 ⇒ 此后恒判「相等」,门再也不会复检。这是纪律面的残余,不假装根治(行级 embedder 记账列可结构性根治,超出本件射程)。滚动事故后的补救 = 手动清一次:`UPDATE agent_memory_engine_entry SET embedding = NULL WHERE embedding IS NOT NULL;`(同一条也可用于「误配后立刻重置」)。
507
- 🔴 **同族的第二格:回滚到指纹门之前的版本**(7.14.x / 7.15.x —— 有 embedder、无指纹门)。那些版本照写 `embedding` 列却**根本不认识这张元表**,所以「装本版(元表记 B)→ 回滚到旧版跑一段(往列里写 A 空间向量,元表纹丝不动)→ 再升回本版、配置仍是 B」这条路走完,元表与配置都说 B 而列里躺着 A 空间向量 ⇒ 此后恒判「相等」,门再也不会复检。**跨门回滚与滚动窗要做同一件事**:回滚之前(或重新升级之后)手动跑上面那条清列 SQL,或干脆 `DELETE FROM agent_memory_engine_meta WHERE meta_key = 'embedder_identity';` 让下次启动按「无主向量」保守清一次。
543
+ 🔴 **同族的第二格:回滚到指纹门之前的版本**(7.14.x / 7.15.x —— 有 embedder、无指纹门)。那些版本照写 `embedding` 列却**根本不认识这张元表**,所以「装本版(元表记 B)→ 回滚到旧版跑一段(往列里写 A 空间向量,元表纹丝不动)→ 再升回本版、配置仍是 B」这条路走完,元表与配置都说 B 而列里躺着 A 空间向量 ⇒ 此后恒判「相等」,门再也不会复检。**跨门回滚与滚动窗要做同一件事**:回滚之前(或重新升级之后)手动跑上面那条清列 SQL,或干脆 `DELETE FROM agent_memory_engine_meta WHERE meta_key = 'embedder_identity';` 让下次启动按「无主向量」判(拒启;带上确认口令 `MEMORY_EMBEDDER_REEMBED` 起一次即清一次)。
508
544
  - **没配 `MEMORY_EMBEDDER_*` 时门整个不装**(元表不动,旧指纹保留),启动日志 `memory_embedder_identity_absent` 说明:缺席期间**被改写过**的行,其 embedding 本来就被写成 NULL,所以「配置回归同一 identity 后向量续用」只对缺席期间**未被触碰**的行成立。
509
545
  - 残余:同名模型在不同供应商若其实是不同实现(自托管 finetune 撞名),指纹辨不出——明知异实现时改 `MEMORY_EMBEDDER_MODEL` 名,或手动跑上面那条清列 SQL。`MEMORY_ENGINE_BACKEND=mysql` 无向量面(v1 词面档),没有这张元表也没有这道门。
510
546
  📋 **档位对运维可见**:启动日志 `memory_engine_enabled` 行带 `vectorMode` 字段(取自后端实例的真值,不是配置推断)——`lexical` 在跑就必须看得见;配了 embedder 时同行还带 `embedderModel`/`embedderDim`,换模型这件事在日志里留痕。
@@ -532,7 +568,7 @@ MODEL_CODE_ROLES=default,subagent # 不设=全中立;仅这些角色在「
532
568
  | env | 缺省 | 说明 |
533
569
  |---|---|---|
534
570
  | `MEMORY_ORG_ADMISSION_MODE` | `enforce` | `enforce`=判决即结果;`audit`=**运维诊断位**——准入面判决照算、拒绝降为日志+metric,零行为变化(不整拒、不窄化写面);`/v1/memory/export`、`/v2/memory/sync/:scope` 的 `org:` 属主门在 audit 下**逐字保持收编前的 operator-only**(未验证的目录不得开数据面)。两模式都要求目录 client 在场 |
535
- | `MEMORY_ORG_DIRECTORY_JSON` | 缺省 | 单机形静态授权表 `{"<principal>":{"org:acme":{"write":true}}}`。**启动期整表校验,坏表拒启动**。有 web-admin 时被忽略(center 胜 + warn) |
571
+ | `MEMORY_ORG_DIRECTORY_JSON` | 缺省 | 单机形静态授权表 `{"<principal>":{"org:acme":{"write":true}}}`;scope 键须过引擎的 org scope 键判官(`org:` 加一段名:`[A-Za-z0-9._-]` 直写、其余字符写成大写 `%XX`,段至多 64 个编码字符;可用引擎的 `formatOrgScope` 铸,例 `org:team%40acme`);web-admin 远程腿下发的目录同判 —— 一代目录里有一把键不过,该主体的这一代整代不采用(瞬时拒,可重试;服务日志 `org_directory_generation_refused` 点名坏键与版本)。**启动期整表校验,坏表拒启动**。有 web-admin 时被忽略(center 胜 + warn) |
536
572
  | `MEMORY_ORG_GRANT_TTL_MS` | `60000`(`[1000, 3600000]`) | 授予/负结果的缓存 TTL = 「有界 LKG」:**新任务/新 resume 腿**的准入判决滞后 ≤ 此值;**在跑任务不受吊销影响**(判决点在 prepare 期) |
537
573
  | `MEMORY_ORG_UNAVAILABLE_BACKOFF_MS` | `10000`(`[500, 600000]`) | 目录取不到之后的退避窗(只用于取数失败臂,不用于负结果) |
538
574
  | `MEMORY_DELEGATION_EVIDENCE` | 缺省(=引擎自缺省 `static-face`) | **委派臂的证据标准**(core 5.45.0 起)。`static-face`=今日行为:委派的 attestation 缺失/未知**且**静态工具面够得着外部内容 ⇒ 标记本会话记忆为已污染(可能性即暴露)。`attested-only`=**只**豁免那一条静态面标记,且**真的豁免了一次**才响亮通告(`engine_notice` 的 `memory.delegation_static_mark_waived`,每个 prepared leg **至多**一次);送达 `external` attestation 照标、非委派的污染类工具照标、委派工具自身被分类为污染的照标。⚠️ 通告**不是 leg 计数器**:不含委派的 leg、污染面没挂载的 leg、送达了 attestation 的委派都不发 —— 看不到通告的常态含义是「没有可豁免的事」,不是「配置坏了」。坏值**启动期拒**。**部署席 only**(TaskSpec 无同名键,任务/受治脚本无法据此放松部署)。⚠️ **已接受的代价**:`attested-only` 下**后台**子代的真实外部接触不标记本会话(其内容经 TaskOutput / 任务通知注入 / AgentTranscript 摘要回流,三条都不带 attestation);异常收尾的前台子代同理。已标记的会话永不回滚清洗。运维读面 = `GET /v1/diagnostics/wiring` 的 `memoryPosture.delegationEvidence`(`null`=本部署未设,引擎自缺省) |
@@ -559,8 +595,9 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
559
595
  - **库形嵌入**(包根 `.` 导出:`loadConfig` + `createHttpServer` + 自建 `StoreBackend` / `ServiceDeps`):`loadConfig()` 之后调
560
596
  `composeExecutionLane(config, ctx)`(HTTP 服务与 `run-local` 都这么做;7.104.0+)。执行车道的四条拒启(`leader-host-lane` /
561
597
  `multi-tenant-shared-lane` / `device-durable-store` / `device-durable-approval`)自 7.104.0 起判在这一步,**不在** `loadConfig()` 里 ——
562
- 只调 `loadConfig()` 的装配对这几形照起。`ctx.configSource === "local"` 时它先读 `config.d/remote-exec.json` 覆写车道(`drivesFromFile`
563
- = 你的装配驱动得了的文件车道),再判;不走文件覆写的装配直接调同一份判据 `assertExecutionLaneInvariants(config)`。
598
+ 只调 `loadConfig()` 的装配对这几形照起。`ctx.configSource === "local"` 时它先读 `config.d/remote-exec.json` 覆写车道,再判。`ctx.entry`
599
+ = 你的装配这只入口:`runs` 是它跑得了的车道闭集(文件或 env 声明了表外的一条 ⇒ 拒),`declares` 可选(文件与 env 都没点名时由入口声明,排在二者之后;文件在场即终结选择 —— 文件点的车道取不到坐标 ⇒ 拒,不由 env 的另一条或入口声明顶上;
600
+ `run-local` 声明 `host`);自建 HTTP 装配递包根导出的 `HTTP_SERVICE_LANE_ENTRY`(全部车道、不替部署声明);不走文件覆写的装配直接调同一份判据 `assertExecutionLaneInvariants(config)`。
564
601
  `ctx` 的型 `ExecutionLaneComposeCtx` 也从包根导出(自己的装配里要写它的签名 / 抽 helper 时直接 import,不必手抄一份形)。
565
602
  读口场景门 `scenarioGate` 是 `createHttpServer` 的必填件(7.131.0+):用包根导出的 `createScenarioGate({ config, scenarios, principalCaps, logger }, "peek")` 铸(与任务提交腿同一个工厂,型 `ScenarioGate` / `ScenarioGateLeg` / `ScenarioGateSources` 一并导出),没递就是 tsc 红。
566
603
  **模型路由端点的拒启同理判在目录应用之后**:`judgeServedModelRoute(config, { envTableIsPlaceholder })`(包根导出,HTTP 服务与 `run-local` 调的就是它)只判将要服务的那张表(目录表或 env 表,句子随视图);
@@ -577,6 +614,22 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
577
614
  (另一个宿主重启了共享引擎):壳据此把「引擎换代」当**一等事实**处理,不必等某个带鉴权的请求先撞一次
578
615
  401 才发现(`/health` 无鉴权,心跳恒绿)。⚠️ 只给这一个数:service token 本体与代际计数**不上**这一面。
579
616
  钉:`test/health-identity-contract.test.ts`。
617
+ - **数据根引擎版本高水位 marker(7.147.0+;只 `DB_BACKEND=local`)**:`<数据根>/engine-high-water.json`(`/health.dataRoot` 那只目录的顶层),
618
+ 形 = **恰一键** `{"highWater":"x.y.z"}` —— 打开过这只数据根的最高引擎(本包)版本(引擎判过之后、往根里写第一个字节之前就抬,所以它是「可能写过这只根的最高版本」,不是服务成功的记录)。`highWater` 是纯 `x.y.z`(无前导零、无 `v` 前缀、无预发布 / 构建后缀、
619
+ 每段是安全整数),三段按数比。宿主(壳侧判官)与引擎读**同一只文件、同一读法**,不另记第二份高水位:
620
+ - 文件不在 ⇒ **缺席**:还没有 7.147.0 及以后的引擎打开过这只根 —— 不比;
621
+ - 在而读不出(I/O 错;悬空符号链接 —— 链接在、目标不在)/ 不是 JSON 对象 / 缺 `highWater` / 有别的键 / `highWater` 不是纯 `x.y.z` ⇒ **读不出**:引擎起服拒,**不当缺席**;有别的键 = 更新的引擎写下的形(数据根比本构建新);
622
+ - 读得出 ⇒ 与本端版本比:`highWater` 高于本端 ⇒ 本端引擎起服拒 `config.schema_mismatch`(拒句点名这只文件、高水位与本构建版本;
623
+ 包根导出的 `LocalDataRootSchemaMismatchError.findings` 里一段 `problem: "newer-engine"`,同一段必带 `engineHighWater: { highWater, engine }` —— 读数类型 `LocalDataRootMismatch` 是以 `problem` 为判别位的联合),不必先起一只注定被拒的引擎再解析 stderr。
624
+ - **写者只有引擎**,判与抬在**打开数据根的那一刻**一段做完(数据根锁在手 = 单写者;先于引擎这一侧的任何一只店被建起 —— 店在构造里就会写):先判,判拒 ⇒ 本仓零字节写、不抬;
625
+ 判过 ⇒ 先抬再写 —— 本构建高于记录(或缺席)⇒ tmp + rename 原子替换(属主独占 `0600`,目录项也要证落盘;并发读者只见整份旧值或整份新值),相等不写(只证它在盘上:fsync 文件与目录)。所以店构造里的自修、
626
+ 起服对账、一次性迁移与任何服务写都在抬之后:写到一半进程死了,水位已是本构建,比它旧的引擎重开被拒,本构建重开续跑。对账(严格边档 / 快照消化)或迁移在抬之后被拒 ⇒ 水位留在本构建。
627
+ 本构建版本按**发布线**读:`x.y.z`,可带 `-预发布` / `+构建` 后缀,后缀只剥不比(`7.147.0-rc.1` 按 `7.147.0` 判与抬,marker 恒写纯三段);读不出(例:读不出 `package.json` 时的 `unknown`)⇒ 引擎拒开这只根(marker 不读不写)。
628
+ 宿主只读:不写、不删、不改。写不进(含目录项证不了落盘)⇒ 不以这只根服务(起服拒;缺省推导的 local 在起服时为空的根上按既有降级臂退内存)。
629
+ 残余(如实):引擎内核 `FileStorageBackend` 的构造把取锁与它自己的店放在同一步(建子目录、店目录锁、内核账本撕裂尾的收拢)—— 那几笔在判之前。
630
+ - SQL 后端不读不写它(滚动升级窗内合法混跑,另有列册对账)。只有**旧的那一端**是 7.147.0 及以后才守得住(更早的引擎不读它)。
631
+ 回滚的唯一支持形 = 恢复升级前的数据根备份(marker 随备份回到旧值);同一台机器要并行跑多个引擎版本 ⇒ 各给一个数据根(`LOCAL_DATA_ROOT`)。不设绕过旋钮。
632
+ 钉:`test/local-engine-high-water.test.ts`。
580
633
  - `/health` 活体 store 探测(5.14.0,SQL 后端专属 additive):后台探针(`STORE_PROBE_INTERVAL_MS`,
581
634
  默认 15000;0=关;负/非数启动响亮拒)缓存一份 DB 真往返结果,`storeProbe:{live,ageMs,error?}` 在
582
635
  探针接线时恒在,顶层告警键 `storeLive:false` 只在死时出现。**status 保持 "ok"**(liveness≠readiness
@@ -612,9 +665,10 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
612
665
  新身份的桶里还没有这一代)。在那只身份的桶里确认即缺席。**没有耐久声明的部署恒缺席**:那里「重启靠中心重拉」是部署形本身(配置时声明的事实),不是事故;没接中心 / 本地腿
613
666
  也缺席。`version` = 应用着的那一代,`since` = 这一代开始没有确认接力的时刻。与 `restart.withheld`(要重启但扣着)是两个事实两张脸,可同在。7.120.x 的 `modelPlaneDeferred` 已删。
614
667
  - **自报位(7.121.0+)**:`/health` 恒带三键 `auth` / `operator` / `center`(缺席 = 老引擎),给壳在**带凭据之前**判出路用,
615
- 全部是装配事实的投影、不是另立的规则:`auth = { required, kinds, identityAttach }` —— `required` = 全局凭据门武装(配了
668
+ 全部是装配事实的投影、不是另立的规则:`auth = { required, kinds, identityAttach, serviceTokenActsForDeployment }` —— `required` = 全局凭据门武装(配了
616
669
  `SERVICE_AUTH_TOKEN(S)`),`kinds` ⊆ `service_token` / `sso`(装了鉴权桥:登记中心的登录令牌)/ `direct_door_jwt`(直连门),
617
- `identityAttach` = `available`(kinds 里有 `sso` 或 `direct_door_jwt`)/ `unavailable`;`operator` = `present`(`OPERATOR_PRINCIPALS`
670
+ `identityAttach` = `available`(kinds 里有 `sso` 或 `direct_door_jwt`)/ `unavailable`;`serviceTokenActsForDeployment`(**7.146.0+**,恒在场)=
671
+ 不带主体头的 service token 在这台部署上代不代部署本人 = `required` ∧ `SERVICE_TOKEN_ACTS_FOR_DEPLOYMENT`(缺省 `true`;没配 service token ⇒ `false`;见 §「不报主体的 service token 是谁」);`operator` = `present`(`OPERATOR_PRINCIPALS`
618
672
  非空,或单用户声明 `OPERATOR_SINGLE_USER=true`)/ `absent`;`center = { issuer?, webBaseUrl?, sessionsVisible }` —— `issuer` = 鉴权桥的 issuer(`AUTH_BRIDGE_ISSUER`,缺席回退
619
673
  `SEMA_WEB_ADMIN_URL`;没有桥 ⇒ 缺席),`webBaseUrl` = `SEMA_WEB_ADMIN_WEB_URL` 原样(缺席即缺席),`sessionsVisible` = `true` /
620
674
  `false` / `"unknown"`:中心车道配齐 ∧ `FLEET_ADVERTISE_ADDRESS` 在 ∧ 鉴权桥在 ∧ 会话枚举面在,缺一 ⇒ `false`;齐了而多租户 ⇒ `true`,
@@ -657,7 +711,7 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
657
711
  行与编号高水位一并抹掉(同 anchors/附件/快照的级联)——会话 id 可被重新认领,残留就等于把上一位
658
712
  主人的任务标题与编号进度交给下一个化身。
659
713
  - **会话的行事实只住在数据根的 `session-incarnations.json`**(每只会话一格:属主 / 化身 / 执行放置 / 建档时刻 / fork 谱系);`sessions/*.jsonl` 是匿名的对话内容。
660
- 备份 / 迁数据根时两样一起搬;更早版本写下的带属主戳的存档由起服一次性迁移改写(改不了的点名,fork 与改日志推送答 409 `conflict.session_archive_stamped`;会话同步导入的第一阶段对 fork / stale 且没带同意的推送也直接答它),快照目录 `session-archive-m33/` 非空且消化不了 ⇒ 拒启(MIGRATION 写了出路)。
714
+ 备份 / 迁数据根时两样一起搬;更早版本写下的带属主戳的存档由起服一次性迁移改写(改不了的点名,fork 与改日志推送答 409 `conflict.session_archive_stamped`;会话同步导入的第一阶段对 fork / stale 且没带同意的推送也直接答它,7.147.0 起干跑 `POST …/sync/plan` 对这两形同样),快照目录 `session-archive-m33/` 非空且消化不了 ⇒ 拒启(MIGRATION 写了出路)。
661
715
  `session-incarnations.json` 本构建读不出(更新的引擎写过、格里多出不认得的键;或文件坏了 / 读不了)⇒ 起服拒 `config.schema_mismatch`(与 SQL 后端列册对账同一个码,拒句点名文件与键;
662
716
  出路 = 部署写下它的那一版,或恢复升级前的数据根备份;读不了 ⇒ 修好权限 / 盘再起);起服之后才读不出(跳过了 `ensureSchema()` 的嵌入方;或违规共享的数据根上另一只引擎写了它)⇒ 同码拒,
663
717
  形读不出则那只边档的每一读每一写拒到重启,盘上字节不被盖掉。
@@ -697,10 +751,11 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
697
751
  那是文档化的通道,而不是两条路各有一套默认。
698
752
  🔴 **同一只窗 7.119.0 起也管「已配置的 SQL 库此刻连不上」**:`DB_BACKEND=mysql|pg`(含 `SESSION_BACKEND=auto`)boot 期开不了店 ⇒ 窗内每 250ms 重试、到窗拒启(点名后端与 host:port;
699
753
  永久错如凭据 / DDL 权限也拖到窗尾才拒,要当场拒设 `0`;唯一例外是**确定的拒启表态**,见下一条「跳版升级的旧库」,它不等窗)。k8s 清单里库与 server 同时起,所以清单把窗给到 `300000`。
700
- ⚠️ **裸 boot(没有显式 `DB_BACKEND`)那一形的时序也跟着变**:它的锁冲突走的不是拒启而是既有的
701
- 「退回内存并 warn」降级臂(`store_db_unreachable_fallback_memory`),所以第二只实例此前**当场**降级、
702
- 现在要**等满窗**才降级 —— 方向仍是好的(窗内拿到就用上真数据根,而不是静默丢掉持久面),但它是一次
703
- 真实的启动时长变化。显式 `DB_BACKEND=local` 不走降级臂(拒启),`0` 关掉两形的等待。
754
+ ⚠️ **裸 boot(没有显式 `DB_BACKEND`)撞锁与显式 `DB_BACKEND=local` 同形**:等满窗仍拿不到 ⇒ 拒启(同一句拒句)。
755
+ 「退回内存并 warn」降级臂(`store_db_unreachable_fallback_memory`)只对**空 / 不存在**的数据根开 —— 有人持锁的根上
756
+ 一定有东西,开不了就拒,不退内存(退了 = 这只根上已有的会话 / run 账本整片看不见)。`0` 关掉等待。
757
+ 🔴 **新引擎的锁等待窗应 ≥ 前任的排空窗 + 15 s 终局 + 一拍**(前任的锁要到它收尾链尾部才还回来;前任最晚何时退出见 `DRAIN_GRACE_MS` 下面的「停机上界(契约句)」):
758
+ 壳 / 桌面按**自己的**排空窗给新引擎进程注入这个值 —— 那是配置渠道的本意;server 不改缺省(缺省 `60000`:改缺省会让所有部署的起服失败延后)。
704
759
  三问成文见 `docs/DEPLOY-PREREQS.md`。
705
760
  - **跳版升级的旧库(退役表 `snapshot_manifest` / 退役列 `checkpoint.pending_steer` 还在)起服即拒 `config.schema_mismatch`,而且拒启之前库里一张表都没建**:
706
761
  连上库后**先探这两件、再建表** —— 探针先于第一条 `CREATE`(两个 SQL 方言同一只判据),所以按拒句指路去 7.125.x 起服一次时,库还是你交给本版之前的样子,
@@ -753,8 +808,9 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
753
808
  —— 没有任何东西把 B 的路径映到 A。要么把服务(或 `run-local`)跑在放着文件的那台机器上,要么走容器车道
754
809
  (`e2b`/`k8s`/`local-docker`),那里的工作区是沙箱自己的,`cwd` 被忽略。
755
810
  - **读面(7.86.0 起)**:`GET /v1/capabilities` 的 `executionLane` 把本部署的车道广告成
756
- `{provider:<执行车道八词>, toolsOnThisHost:boolean}`。7.97.0 起 `REMOTE_EXEC` 未设 ⇒
757
- `provider:"in-process"`、`toolsOnThisHost:false`(本部署没有执行车道 = 无手;改前折报 `"host"`/`true`)。
811
+ `{provider:<执行车道八词>, toolsOnThisHost:boolean}`。没有执行环境的部署(无手)报
812
+ `provider:"none"`、`toolsOnThisHost:false`(`REMOTE_EXEC=none` 显式声明;7.97.0 起到 7.147.0 之前 `REMOTE_EXEC` 未设落进的是同一个无手状态,在这个读面上叫 `in-process`,此前折报 `"host"`/`true`);
813
+ 7.147.0 起未声明车道即拒启,`in-process` 这个状态名随之改名 `none`(见上文车道表之后的「硬断(自 7.147.0 起)」一段)。
758
814
  要本机有手,显式设 `REMOTE_EXEC=host`(sema-up 产物已显式写车道)。
759
815
  壳/客户端判「工具跑不跑在本机」**读这一位**,不要再从自己这一侧的 `REMOTE_EXEC` 猜 —— 连远端引擎时
760
816
  那个 env 说的是**壳自己这台**的事。⚠️ 它是**文件系统同一性**位,**不是**权限位:`cwd` 兑不兑现仍由
@@ -772,20 +828,20 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
772
828
  - `writeFile`(Write / Edit / NotebookEdit 落盘的那一步)= **整文件替换:目标要么整份是旧内容、要么整份是新内容;取消(`aborted`)⇒ 目标未碰**。
773
829
  host 车道 = 引擎的整文件替换(同目录暂存 + fsync + rename);ssh / adb / local-docker / k8s / e2b = 先在目标目录下建一只生来 0700 的私有目录
774
830
  `.<名>.stage-<12 位十六进制>/`,整份传进去,再在目标机上一次 `mv -f` 换上去;device = 设备侧执行器在同一步里暂存再换上。
775
- 传输中途断、取消、写到一半出错 ⇒ 目标逐字是旧内容,私有目录被删(删不掉 ⇒ 错误句点名残留的目录)。修前 host 与五条远端车道是就地截断写:中途中断留下新内容的前半截,原件找不回。
831
+ 传输中途断、取消、写到一半出错 ⇒ 目标逐字是旧内容,私有目录被删(删不掉 ⇒ 错误句点名残留的目录,跟一跳重新暂存的那只也点名;脚本点名的残留目录补清一次,仍在的才计 `server.remote-write.staging-dir-left`)。修前 host 与五条远端车道是就地截断写:中途中断留下新内容的前半截,原件找不回。
776
832
  - **服务进程被 `SIGKILL` 时,暂存目录会留在目标旁(可直接删)**:上一条的清理是服务进程自己做的 —— `kill -9`、内存杀手、容器被强停这类**没有机会跑清理**的退出,正在传输的那一次写留下 `.<名>.stage-<12 位十六进制>/`,里面是在传的新内容(上传途中被杀只有前半截,传完才被杀则是整份);**目标逐字是旧内容**,整文件替换的保证不受影响,多出来的只是这只目录。ssh 车道实测(被强杀的写都留下了);其余四条远端车道走同一段暂存脚本,同形。取消(Ctrl-C 中断那一次写)不留 —— 取消路径上清理那一往返照跑。**本版不自动清**:下一次对同一目标的写不会去扫陈旧的暂存目录,它会一直留着,确认没有另一个进程正在写同一个目标之后 `rm -rf` 即可(名字里的十六进制每次写都不同,在写的那一只和陈旧的那一只光看名字分不出)。host 车道的暂存是引擎的,同形的残留名是 `<目标>.tmp.<进程号>.<十六进制>`,同样可以删。
777
833
  - 暂存自创建起不比目标更可读:远端五车道的暂存只在私有目录里(别的用户进不去;替换 0600 的文件时新内容在传输期间只有写入方与 root 读得到),device 的暂存件以 0600 打开;
778
834
  host 车道的这一保证在引擎原语里:引擎 7.60.0 起目标存在时暂存件以 0600 打开。新建的文件是写入方身份的 umask 形(local-docker = 容器内镜像用户的)。
779
835
  私有目录建出后要读回核实是 0700 且属写入方、且目录上没有 ACL 条目:macOS 的原生 ACL 与 FreeBSD / illumos ZFS 的 NFSv4 ACL 不经模式位 —— 工作区父目录带可继承的允许条目时,新建的私有目录一建出来就带着它,读回 `700` 也挡不住,
780
- 所以读到条目就去掉(macOS `chmod -N`、FreeBSD `setfacl -b`、illumos `chmod A-`)、去完再读;**核不过(`chmod` 被拒、文件系统不认权限位、缺省 ACL 的掩码收不下来、继承来的 ACL 条目去不掉或读不出、目标机的系统不在 Linux / macOS / FreeBSD / illumos 之内、目标机没有 `stat`)⇒ 不暂存,退回就地写目标**
836
+ 所以读到条目就去掉(macOS `chmod -N`、FreeBSD `setfacl -b`、illumos `chmod A-`)、去完再读;**核不过(`chmod` 被拒、文件系统不认权限位、缺省 ACL 的掩码收不下来、继承来的 ACL 条目去不掉或读不出、目标机的系统不在 Linux / macOS / FreeBSD / illumos 之内、目标机读不出模式串〔`ls` 不可用 / 输出读不懂〕)⇒ 不暂存,退回就地写目标**
781
837
  (两只文件面、五条腿都有;非原子;ssh / local-docker / k8s 写进原 inode、权限位不变,e2b 的 `files.write` 与 adb 的 `adb push` 整份写目标、inode / 权限位由对端定;
782
838
  遥测计 `server.remote-write.stage-seal-fallback`);读 ACL 的臂按**目标机**的 `uname -s` 选(Linux:POSIX ACL 由模式位读回的掩码覆盖,不另读 · Darwin · FreeBSD · SunOS);FreeBSD / illumos 两臂按系统文档写、未上真机,读 ACL 的工具报错 / 输出形不对 / `uname` 退出状态不成都按读不出处理(读 ACL 的工具自己静默吞掉的读取错误看不出);
783
839
  Linux 上经 NFS 挂载、由服务端自己执行的 NFSv4 ACL 在客户端没有可读的条目,不在覆盖内;其它系统(OpenBSD / NetBSD / AIX / Cygwin 一类)的目标机每一次写都走就地写并计数。
784
840
  host 车道的暂存是引擎的,device 车道的暂存是设备侧执行器的 —— 这一道不管它们;adb 的追加 = 7.138.x 的形:读旧 + 拼 + 整份 push(push 没有追加写)。Android 共享存储(`/sdcard` 一类)上 adb 的写按 sdcardfs / MediaProvider FUSE 的权限语义**推断**每一次都走这一形(未上真机,真机读数候 test)—— adb 的工作区放在 `/data/local/tmp` 一类认权限位的目录即照常暂存。
785
841
  - 一个身份:准备目录、上传、提交、清理都以同一个身份做。local-docker 的上传是容器内以镜像的用户经 stdin 收字节(不是守护进程以 root 的 `docker cp`)——
786
842
  写文件与代理的 shell 同一套权限,镜像用户写不了的路径答 `permission_denied` —— 镜像里没预建的工作区目录由容器启动以 root 建出,镜像用户 ≠ root 时要在镜像里预建并归镜像用户。
787
- - 回落(host 与五条远端车道,与引擎同一套;device 由设备侧执行器自定,非常规文件拒写):单跳符号链接 ⇒ 替换它指的那只常规文件(链接仍是链接;远端车道先在那只文件的目录里重新暂存,rename 只在一个目录之内);链接链 / FIFO / 设备文件 / rename 被拒(如单文件绑定挂载)⇒ 由已落定的暂存件就地拷入(非原子);
788
- 目录不收新名字(只读目录里的可写文件)⇒ 直接写目标一次(旧形,同样非原子;远端车道在这一形里写目标那一步因取消中断答 `unknown`「目标可能只写了一半」,不答 `aborted`;取消落在那一步开始之前(读旧也在这之前)照答 `aborted`、目标未碰;那一步不收取消(ssh 的 sftp)⇒ 照样写完、答成功;
843
+ - 回落(host 与五条远端车道,与引擎同一套;device 由设备侧执行器自定,非常规文件拒写):单跳符号链接 ⇒ 替换它指的那只常规文件(链接仍是链接;远端车道先在那只文件的目录里重新暂存,rename 只在一个目录之内;那里的私有目录同样核不过 ⇒ 与上面同一条退路:清掉原暂存件、直接写目标、计 `stage-seal-fallback`);链接链 / FIFO / 设备文件 / rename 被拒(如单文件绑定挂载)⇒ 由已落定的暂存件就地拷入(非原子);
844
+ 目录不收新名字(只读目录里的可写文件)⇒ 直接写目标一次(旧形,同样非原子;远端车道在这一形里分两段读取消:备件阶段〔第一个字节发往目标机之前:adb 在本机落临时件、ssh 开 sftp 通道含懒重连、k8s 建连、追加的读旧 + 拼〕取消 ⇒ `aborted`、目标未碰;传输那一步因取消中断 ⇒ `unknown`「目标可能只写了一半」,不答 `aborted`;那一步不收取消(ssh 的 sftp)⇒ 照样写完、答成功;
789
845
  host 车道的回落形由引擎就地写,中途取消目前仍答 `aborted` —— 已知缺口,修在引擎)。
790
846
  已有文件的权限位带过去;属主不带(替换后的文件属于写入方)。
791
847
  - 已知语义变化(与引擎 / 参照实现同形):rename 换的是目录项 ⇒ **硬链接被断开**(另一条链接留旧内容);目录可写时**没有写权限位的文件也会被替换**(权限位保留);
@@ -829,7 +885,7 @@ SEND_USER_FILE_SANDBOX_PUT_ENDPOINT=… # 可选:沙箱直传 PUT 的端
829
885
  - **🔴 云形(`DB_BACKEND=mysql|pg`)快照 blob 强制对象存储(1.295+,clay 拍)**:缺 MinIO 三键 ⇒ **boot 拒启**——单行 SQL blob 写会撞 TiDB `txn-entry-size-limit`(默认 6MiB,真库实测矮墙)/ mysql 协议 `max_allowed_packet`,大字节归对象存储(与 D-1 附件面同裁定)。单机/测试台显式逃生:`SNAPSHOT_BLOB_ALLOW_SQL_BYTES=true`(SQL 店此时带 per-blob 帽,MySQL 协议(InnoDB 与 TiDB)默认 = **写入那条数据库连接**报的两堵已知墙取最小:单行上限减 1 KiB(blob 行自己的余量:哈希键 + 长度 + 时间戳;TiDB 才有)与包墙 `max_allowed_packet` 扣 1 KiB 语句余量后折半(⌊(包墙 − 1 KiB) ÷ 2⌋:文本协议把 blob 写成十六进制,语句至少是它的 2 倍);任一读数读不出时按该库族文档缺省并留一行运维日志 `fail_open`(tag `server.sql.packet-cap-unread` / `server.sql.tidb-row-cap-unread`),超限 PUT 413 `blob_too_large_for_sql`,句子按帽的出处分三形(运维旋钮 / 库报的墙 / 读不出而按文档缺省假定的墙),机读位 `wallSource` 同词 `operator` / `reported` / `assumed`。包墙是每条连接自己的会话值(7.133.0 起按连接判):DBA 调大 `max_allowed_packet` 之后,池里的旧连接在它第一次按包墙拒 blob 时自换(运维日志一行 `sql_connection_packet_wall_stale`,那一笔在新连接上重判),不必重启副本;`GET /v1/diagnostics/wiring` 的 `sqlEngine.maxRowBytes` / `sqlEngine.maxPacketBytes` 是最近一次初始化的那条连接的读数(见部署前置清单 DEPLOY-PREREQS(仓内)「数据库」条);帽可用 `SNAPSHOT_BLOB_SQL_MAX_BYTES` 按部署真实限值覆写,两方言生效;pg 默认无帽)。
830
886
  - **per-file rewind 历史的边界上限**(与 workspace 浏览面同一个整树快照纪元退役批留下的保留条款):`FILE_HISTORY_RETENTION_KEEP` —— 每个 scope(会话)存活的**最新** boundary 数,**每次 boundary 提交后自剪**(最旧者先亡,刚提交者恒存活;失引用的版本行级联删,blob 字节走异步 grace 窗 GC;v1 基线永存)。未设=core 缺省 **100**(由 core 的 `resolveFileHistoryRetention` 兑现,server 不复制那个数);`unbounded` 是**唯一**的「不剪」拼法(整店自剪关闭,GC 归部署自排 `reap`);`0`/负数/小数/非数 ⇒ **boot 拒启**(码 `config.retention_policy_invalid`,与 core 内置后端同码同判——**省略这个键不等于不剪**,它选的是缺省 100)。三条车道(TiDB / PG / local 文件店)同旋钮同判。⚠️ 升级即生效:存量超 100 boundary 的 scope 在下一次提交时被剪到 100;要保全量先写 `unbounded`。被剪 entry 的键**已花掉**(再次 snapshot 同 entry 典型拒,不复铸)。排序=DB 分配的 per-scope 发布序号 `file_history_boundary.publish_seq`(不是墙钟):**自 7.50.x 有存量 `file_history_boundary` 的部署升级本版会在 boot 期被 `assertFileHistorySchema` 拒启,拒因打印这张表缺的列的 `ALTER TABLE … ADD COLUMN …`,由部署者自己执行**(列集对账与拒启句只打印语句、永不改库;per-file rewind 历史照留,会话与其他持久面不动)。⚠️ 打印的语句取自建表语句原文:`publish_seq` 是 `NOT NULL` 且无缺省的列,在已有行的 PostgreSQL 表上那句原文会被引擎拒(列含空值)—— 这种存量表要部署者自己按 per-scope 的发布顺序补值后再改写执行;这是起服对账拒启句同一生成器的既有性质。
831
887
  - **会话同步重推(同图)要读 blob**(7.136.0 起):`POST /v1/sessions/:id/sync/import` 的 identical 重推臂,对云端「已握着同一张文件历史图」的目标,也先把信封引用的每一枚 blob 经云端自己的店读一遍、核哈希(不回写),再答成功 —— 对象存储里的对象若在图发布之后丢了,这一腿答 `422 missing_blob`(`PUT` 缺的 blob 再推;图不动),不再答 `200 identical`。代价:无变化会话的频繁重推多付对象存储读流量(每枚引用的对象一次下载,正比于引用的 blob 字节);没有旋钮。
832
- - **文件历史捕获总开关**(7.97.0 立,7.104.0 改语义):`FILE_HISTORY_CAPTURE`(缺省 `true`;布尔词表 true/false/1/0/yes/no/on/off,未知词 ⇒ boot 拒启;TiDB / PG / local 三条车道同义)。`false` ⇒ **只关捕获**:历史店照接,引擎拿到部署捕获位 `RunnerDeps.fileHistoryCapture: "off"`,改动文件的首次快照**一个都不捕获**、不铸回合边界(`wiring_manifest.fileHistory` 报 `"disabled"`);**已存历史照常可用** —— `resumeAt`+`restoreFiles` / `rewindFilesTo` 照常还原、2c session-sync 照常导出 / 导入。`GET /v1/capabilities` 的 `fileHistoryCapture` 报 `"off"`,而 `rewindFiles` / `restoreFiles` / `rewindFilesTo` / `sessionSync` 仍为 `true`(缺省部署报 `"on-optional"` = 捕获中且请求可按 run 关掉,见上一段)。🔴 **7.97.0–7.103.x 的旧语义**:`false` = 不把店交给引擎,连已存历史也读不了(还原走 `rewind.store_unconfigured`、sync 501)—— 把本旋钮当成「整个文件历史功能关掉」来用的部署,升级后已存历史**重新可以被还原 / 同步**。要让已存历史也不可用,今天的出路只有**逐会话** `DELETE /v1/sessions/:id`(擦除面不受本旋钮管)与 SQL 后端的留存清扫(`RETENTION_MODE=enforce` + `RETENTION_MAX_AGE_DAYS` + `RETENTION_SWEEP_INTERVAL_SEC`,按会话年龄删,local 车道没有留存车道);**没有**「整库历史一键不可用」的旋钮。⚠️ **关掉 ≠ 删掉**:翻旋钮不批量删除存量历史;local 车道上删会话走 `reap` —— 边界删光、v1 基线按 core 契约留存,既有残余。关着时 `FILE_HISTORY_RETENTION_KEEP` 没有效果(不再有边界提交,自剪不会触发),值仍照常校验。没有存储后端的部署(env-only worker)本来就没有历史店,旋钮在那里无事可关、不下发捕获位(引擎对「off 但没店」拒 prepare)。
888
+ - **文件历史捕获总开关**(7.97.0 立,7.104.0 改语义):`FILE_HISTORY_CAPTURE`(缺省 `true`;布尔词表 true/false/1/0/yes/no/on/off,未知词 ⇒ boot 拒启;TiDB / PG / local 三条车道同义)。`false` ⇒ **只关捕获**:历史店照接,引擎拿到部署捕获位 `RunnerDeps.fileHistoryCapture: "off"`,改动文件的首次快照**一个都不捕获**、不铸回合边界(`wiring_manifest.fileHistory` 报 `"disabled"`);**已存历史照常可用** —— `resumeAt`+`restoreFiles` / `rewindFilesTo` 照常还原、2c session-sync 照常导出 / 导入。`GET /v1/capabilities` 的 `fileHistoryCapture` 报 `"off"`,而 `rewindFiles` / `restoreFiles` / `rewindFilesTo` / `sessionSync` 仍为 `true`(缺省部署报 `"on-optional"` = 捕获中且请求可按 run 关掉,见上一段)。🔴 **7.97.0–7.103.x 的旧语义**:`false` = 不把店交给引擎,连已存历史也读不了(还原走 `rewind.store_unconfigured`、sync 501)—— 把本旋钮当成「整个文件历史功能关掉」来用的部署,升级后已存历史**重新可以被还原 / 同步**。要让已存历史也不可用,今天的出路只有**逐会话** `DELETE /v1/sessions/:id`(擦除面不受本旋钮管)与 SQL 后端的留存清扫(`RETENTION_MODE=enforce` + `RETENTION_MAX_AGE_DAYS` + `RETENTION_SWEEP_INTERVAL_SEC`,按会话年龄删,local 车道没有留存车道);**没有**「整库历史一键不可用」的旋钮。⚠️ **关掉 ≠ 删掉**:翻旋钮不批量删除存量历史;删会话照删已捕获的历史(三条车道同读:local 车道自 7.146.0 / 引擎 7.64.0 起也走整会话清理动词 —— 边界、版本记录、v1 基线与只被这只会话引用的 blob 一起删;此前 local 走 `reap`,v1 基线留存)。关着时 `FILE_HISTORY_RETENTION_KEEP` 没有效果(不再有边界提交,自剪不会触发),值仍照常校验。没有存储后端的部署(env-only worker)本来就没有历史店,旋钮在那里无事可关、不下发捕获位(引擎对「off 但没店」拒 prepare)。
833
889
  - **🔴 公桶策略必须 GetObject-only**:`mc anonymous set download` 会**连带打开 ListBucket**——匿名 `GET /<bucket>/?list-type=2` 能枚举全部不可猜 key,能力链接设计即告失效。正确姿势=`mc anonymous set-json`,policy 只含 `Action:["s3:GetObject"]` on `arn:aws:s3:::<bucket>/*`(验证:对象 GET 200、桶 LIST 403)。
834
890
  - **两条轨**:ttl=0(默认)→ 匿名 GET 公桶下 `uuidv7/<name>` 不可猜 key,链接永久、可回收(删对象);0<ttl≤7 天 → 私桶 + SigV4 限时签名链接(7 天是 SigV4 物理上限,更久用 ttl=0)。
835
891
  - **执行 lane 与租户门**:e2b/k8s 沙箱 lane 任意租户可用(沙箱文件系统=租户边界,沙箱内 `curl -T` 直传、字节不中转、凭据不进沙箱);host/ssh lane 仅单用户部署(`REQUIRE_PRINCIPAL` 未开)时挂载。
@@ -924,6 +980,7 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启公
924
980
  上一代 —— 上一代若还是 env,env 模型就一直服务)。颗粒更粗的失效(整个域读不懂、拼错键、坏文件)仍整拒候选、保 LKG。
925
981
  - **没有 `MODEL_ID` 也能起**(7.101.0 起):目录(`config.d/models.json` 或中心)有可用条目时 env 可以不写 `MODEL_ID` —— 缺省模型取
926
982
  目录缺省。判据在目录应用**之后**:env `MODEL_ID` 或目录缺省任一在场即过;两者皆无 ⇒ 拒启(码 `config.default_model_missing`,指路二选一)。
983
+ 若是因本地候选被判官整拒(悬空符号链接 / 坏 JSON / 空文件 / 域判形失败 …)才落到 env 表,7.147.0 起拒启句同一行多一段回落原因:被拒的文档名、拒码、原因类词(悬空链接 / 读不出 / 解析错误 / 空文件 …)与文档路径(悬空链接给出链接自己的路径,不带它指向的目标路径;不带配置值,原句只在 `config_candidate_rejected` 行);句首 `MODEL_ID is required when no catalog default resolves [config.default_model_missing]` 不变。
927
984
  中心拉取超出 `CONFIG_BOOT_FETCH_BUDGET_MS` 又没有 LKG 时,boot 那一刻没有目录 ⇒ 没有 `MODEL_ID` 兜底就拒启(调大预算让 boot 等目录,
928
985
  或写一个 `MODEL_ID` 兜底)。
929
986
  - **灰度姿势**(配置控制面 AI 建议):先 `SEMA_WEB_ADMIN_DRY_RUN=true` 起一轮,看日志 `web_admin_dry_run`(中心给的 models/roles/teams + 会否覆盖 default、per-model apiKeyEnv)对得上 env 再去掉该 flag 真正 apply。
@@ -1424,6 +1481,12 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1424
1481
  4. **请求体自带的技能**(`skills[]`):只补这一 run **没被占的名字** —— 与部署的技能同名的那条被丢(一行 `user_skills_dropped`),不覆盖;
1425
1482
  `disableSkills: true` ⇒ 这一 run 一只技能都不装(部署的、请求的都不装)。
1426
1483
 
1484
+ **把部署的场景面收窄到只剩自己的(部署自配,没有新语法)**:出厂技能默认进每个场景(`scenarios` 为空),内建的 `code-review` / `scan` 也一直在册。一台只想服务某个场景(例:`twin`)的部署这样收:
1485
+ - 技能:`SKILLS_DIR` 指向一个**空目录**(整替换,出厂技能清零;目录空 = 零出厂技能,不报错),再在 `config.d/skills.json`(或中心的 skills 域)里给自己的每条技能写 `"scenarios": ["twin"]` —— 它们只进 `twin` 这一个场景,不进内建场景。
1486
+ - 内建场景:`code-review` / `scan` 本身没有「隐藏」写法,但可以在 `config.d/scenarios.json`(或中心的 scenarios 域)里写同名条目 `{ "name": "code-review", "toolset": "none" }`,把它**遮蔽**成只有 `Now` 工具、不挂手的空场景
1487
+ (起服日志 `web_admin_scenarios` 的 `shadowsBuiltin` 点名被遮蔽的内建名);`"enabled": false` 的条目是**跳过**,不遮蔽 —— 内建场景照在。
1488
+ - 常驻面各有现成开关:`LSP_HOST_ENABLED=false` 关 host 车道的语言服务器,`SCHEDULER_ENABLED=false` 关自唤醒调度。
1489
+
1427
1490
  **看一个场景装着哪些技能(7.135.0 起)**:`GET /v1/capabilities/scenarios/<name>` 的 `skills` = 上面 1–3 叠出来、按这个场景过滤后的那一组(第 4 条请求自带的不在内 —— 那是调用方自己的),
1428
1491
  每条 `{ name, description, source }`:`source` 是 `"factory"`(第 1 条,本部署的技能目录)或 `"center"`(第 2、3 条,配置源交来的;盖掉出厂同名的也是 `"center"`),不带正文。
1429
1492
  与这个场景的 run 交给引擎的是**同一只过滤**:卡上列的就是本服务交给这一 run 的部署技能,点名(提交体 `userInvokedSkills`)用它的 `name`;没有技能 ⇒ `skills: []`。
@@ -1465,11 +1528,10 @@ TASK_WRITE_FACE_ESCALATION=allow # 词表 allow|refuse;坏词启动期响亮
1465
1528
  整份不是对象)的对端 7.131.x 当时就整条丢了,重读照旧整条不挂(`a2a_injection_dropped` 点名);窗关之后回到上面的读法(整条不挂)。
1466
1529
  - **请求体自带的 `mcpServers[].transport.env` / `transport.headers`**(「名 → 串」记录,与 `a2aPeers[].headers` 同一只读法):名叫 `__proto__` 的成员、值不是串的成员、
1467
1530
  整只不是纯数据 ⇒ 新提交 `400 request.field_invalid` 点名第一处(`__proto__` 成员点名成员级路径 `…headers.__proto__`;值不是串 / 整只读不成点名那个键,原因句点名成员)。
1468
- 续跑重放存量请求体(JSON 文本铸出来的记录)时复现 7.131.x 的读法,唯一偏离是引擎拒的 `__proto__` 成员:记录里有值不是串的成员 ⇒ 整键不交(7.131.x 原跑就是这样挂的,不半程换身份);
1469
- 等价只对 JSON 存体成立 —— 直接递 `Map`、类实例、`process.env` 的嵌入方按上面的更严读法(7.131.x 对带自有串属性的类实例照收);
1470
- 只有 `__proto__` 名的成员坏 ⇒ 剔掉它、其余照交。server 照挂,运维 warn `mcp_transport_members_dropped`(`server` / `lost` / `lostTotal?` / `note`)点名没交出去的键 / 成员与整键没交的
1471
- `transport.args` / `transport.principalHeader`。A2A 对端 `headers` 同读:值不是串 ⇒ 对端照旧不挂;只有 `__proto__` 成员坏 ⇒ 续跑照挂、剔掉它,一行 `a2a_peer_headers_members_dropped`
1472
- (`peer` / `lost` / `note`);新提交上两形都整条不挂。
1531
+ 续跑重放存量请求体时,按 7.131.x 的读法会交不出去的成员 —— `__proto__` 名的成员、有值不是串的成员的整键、`transport.args` / `transport.principalHeader` —— 在本部署兑现请求 MCP 时**拒续跑**(名字被部署占住的条目不算:它整条输给部署):
1532
+ `409 resume_blocked_by_policy`(`blockedBy: "request.field_invalid"`,`field` = 第一处成员坐标如 `mcpServers[1].transport.headers`;句子列出全部坐标,只给键名、不回显值),卡 / 任务仍待决、行不动;
1533
+ A2A 对端 `headers` 只在 `__proto__` 名的成员上同拒(`a2aPeers[i].headers.__proto__`,本部署兑现请求 A2A 时);`headers` 里有值不是串的成员 ⇒ 那一条对端整条不挂(`a2a_injection_dropped` 点名)、任务照跑 —— 整条不进不在拒的射程里(MCP 一面同:整条不进的条目照旧 `mcp_injection_dropped`)。7.146.x 及以前续跑照挂、剔掉那几处,一行运维 warn `mcp_transport_members_dropped` / `a2a_peer_headers_members_dropped` ——
1534
+ 调用方只看到续跑成功,而 MCP 请求没带它写的鉴权头;这两行 warn 本版退役。出路:续跑请求带 `mcpServers` / `a2aPeers` 的合形整表(下一条「续跑时换表」;判的是盖过这张表之后的体),或取消那只任务、按本版的形重提。新提交上两形都 `400 request.field_invalid`(同一只判)。
1473
1535
  - **续跑时换表(7.143.0 起)**:停着的任务续跑(`POST /v1/approvals/:sessionId/decide` · `POST /v1/assistant/tasks/:id/plan_review` · `POST /v1/assistant/tasks/:id/resume` · `POST /v1/sessions/:id/wake`,各续各的门:工具审批卡 / `plan_review` 门 / 资源限停驻 / `task_done` 停驻)默认重放提交时存下的 `mcpServers` / `a2aPeers`;续跑请求体带上同名键(与提交体同形、同一只判官)就以它为准,
1474
1536
  并写回成之后续跑的表 —— 用户移除过一台 MCP 服务器之后,壳送此刻的整张表,续跑就不再重连那一台(停在那一台调用上的任务由引擎判:引擎 7.61.0 起三码各答各形,以 core 为准:那只工具不在了 ⇒ `resume.tool_unavailable`(引擎重开检查点:卡仍 pending、可再决);同名而已不是批准时那一只 ⇒ `resume.tool_contract_mismatch`(同样重开);还是那一只而它的面或声明变了 ⇒ `resume.target_moved`(这次调用不执行、批准用掉,须重新发起),本服务逐字过境);直连门部署的两道回决门拒收这两键(契约 §4e-bis)。
1475
1537
 
@@ -1513,9 +1575,9 @@ A2A_SERVE_BLOCKING_WAIT_MS=45000 # blocking:true 的服务端等待
1513
1575
  本设计唯一点名禁止的事),只决定点名这条 skill 的请求跑哪个场景。
1514
1576
  - 两条路由的**鉴权位置刻意不同**:`GET /.well-known/agent-card.json` 在服务凭据门**外**(公网匿名可
1515
1577
  发现,零存量披露);`POST /v1/a2a` 在门**内**(与本仓其余 API 同一套凭据)。
1516
- - `message/send` / `tasks/get` 还需要 **durable run store**(`DB_BACKEND=mysql|pg`)——缺它时两个方法
1517
- 回具名 JSON-RPC `-32004`(卡照发)。开关打开时 boot 日志有一行 `a2a_serve_enabled`,里面的
1518
- `tasksUsable` 就是这件事。
1578
+ - `message/send` / `tasks/get` 都站在 **durable run store** 上(能力位 `a2aServe` 与这条是同一只判据)—— 缺它时这台 worker 整个**不作为 A2A agent 对外存在**:
1579
+ 卡与 `POST /v1/a2a` 一起 404(7.116.0 起;此前卡照发、两个方法回 JSON-RPC `-32004`,现在不再出现这个码)。开关打开时 boot 日志有一行 info `a2a_serve_enabled`
1580
+ (键:`card` · `rpc` · `advertisedUrl` · `skills` · `blockingWaitMs`);开关打开而没有 run store 时改打一行 warn `a2a_serve_not_served`(`reason` + `detail`,`detail` 点名出路)。
1519
1581
  - 完整 wire 契约(方法表、错误码全表、Task 形与任务态投影、反枚举语义、版本槽)见
1520
1582
  [`docs/ASSISTANT-WIRE-CONTRACT.md` 附录 C](docs/ASSISTANT-WIRE-CONTRACT.md)。
1521
1583
 
@@ -1559,10 +1621,19 @@ ENGINE_ORPHAN_LINGER_MS=2000 # 可选,默认 2s:成孤儿之后还要静多久
1559
1621
  取值(半夜一次滚动重启,宁可等长一点也不要砍掉一条在跑的 turn)。而**壳自 spawn 的引擎会被壳注入更短
1560
1622
  的窗**:sema-cli 在装子进程 env 时把它填成一个分钟级的值(2026-09-22 亲读壳侧实现:`60000`;那是**壳的**
1561
1623
  常量,以你手上那版壳为准 —— 单机形下这个窗直接等于"退出后多久不能重开",见上一条的撞锁退避),
1562
- **用户/运维显式设的值原样尊重、壳不覆盖**。⇒ 排壳形的关机/重开预算时,以**进程实际
1563
- 生效值**为准:`GET /v1/config/catalog` 里 `DRAIN_GRACE_MS` 那一行的 `effectiveValue` 就是本进程真在用的
1564
- 数(它读的是活 config,不是这份文档里的默认值),启动期的夹取/警告行同源。本文档其余各处谈这条排空腿
1565
- 时都不带数字,数字只有这一处 —— 照抄默认值去算壳形预算会差一个数量级。
1624
+ **用户/运维显式设的值原样尊重、壳不覆盖**。⇒ 排壳形的关机/重开预算时,窗 g 取**壳自己注入的那个值**
1625
+ (壳读不到运维面的配置目录:`GET /v1/config/catalog` 是 operator-only,没有运维名册时答 `501`,不是 `403`);
1626
+ 一次排空**开始之后**,本次排空定死的到期点在 `GET /health` 的 `engineSignal.draining.deadlineAt`
1627
+ (= 进入排空那一拍 + 窗;之后热改排空窗不动它;排空前这一格不在场)。启动期的夹取/警告行报的是夹取后真在用的数。
1628
+ 本文档其余各处谈这条排空腿时都不带数字,数字只有这一处 —— 照抄默认值去算壳形预算会差一个数量级。
1629
+ **从停止信号到进程退出的上界**见下一条「停机上界(契约句)」。
1630
+ - **停机上界(契约句)**:从收到停止信号到进程退出,**单发 `SIGTERM`** 的上界 = `engineSignal.draining.deadlineAt`(信号进的排空 = 第一发被处理那一拍 + 排空窗,入口定死)
1631
+ + ≤ 1 s(排空驱动一拍)+ ≤ 15 s 终局;**两发形**(第二发与第一发相隔 g ≥ `DRAIN_SECOND_SIGNAL_MIN_GAP_MS`,缺省 250 ms;t0 = 第一发被处理的那一拍)= t0 + g + ≤ 15 s。
1632
+ 硬停里 10 s 在飞宽限在 15 s 之内,不相加(宽限 = 强断残余连接、审批面收尾、检查点店收尾共读的那一个窗;终局闸从硬停开始那一拍起算,到点无条件退出)。
1633
+ 卡上要写「最多等多久」的壳,诚实的上界是它自己的梯:窗 g + 它愿意给的宽限 H(到 H 壳自己 `SIGKILL`);H 取 ≥ 16 s(= 15 s 终局 + ≤ 1 s 一拍),否则会在 server 还没到自己的终局闸时把它杀掉。以上是按定时器算的名义上界(排空窗与终局闸;不含进程被同步工作卡住的时间),真正强制的上界是壳自己梯子里的 `SIGKILL`。
1634
+ 排空事实里的动作号(`GET /health` 的 `engineSignal.draining.actionId`):信号致排空的**闭集** = `signal:SIGTERM` / `signal:SIGHUP`;
1635
+ 经运维排空口 `POST /v1/admin/drain` 进的排空 = 该请求自带的 id;硬停(未经排空)已在进行时,排空口回执里的动作号 = 固定的 `hard-stop`。
1636
+ 另两类引擎自铸的值:附着租约自退 `self-exit:attachment`、服务租约被拒 `lease:<代际>`。
1566
1637
  - **排空期的第二发信号(`DRAIN_SECOND_SIGNAL_MIN_GAP_MS`,缺省 `250`,7.102.0+)**:排空中再来一发 `SIGTERM` /
1567
1638
  `SIGINT`,只有与**本次排空的第一发停止信号**相隔 ≥ 这个毫秒数才算「别等了,硬停」;更近的是同一拍被投递了两遍(壳心跳误判、
1568
1639
  编排器重试),打一行 `drain_duplicate_signal_ignored`(带 `signal` / `sinceFirstMs` / `minGapMs`)后照常排空。SIGTERM 进的排空,
@@ -1571,15 +1642,19 @@ ENGINE_ORPHAN_LINGER_MS=2000 # 可选,默认 2s:成孤儿之后还要静多久
1571
1642
  修前那一发被读成第二发)。k8s 例:`preStop` 调排空口 → kubelet 发一发 SIGTERM(吸收)→ `terminationGracePeriodSeconds` 到点 SIGKILL。
1572
1643
  真要立刻停:隔过这个间隔再发一次,或 `SIGKILL`;设 `0` 回到「每个第二发都硬停」的旧行为。合法域 `[0, 10000]` 整数毫秒
1573
1644
  (上界 = `DRAIN_GRACE_MS` 的地板),坏值拒启。不在排空时 `SIGINT` 仍是立即硬停。
1574
- - **自退留痕 `<数据根>/exit-last.json`**:自退在 **drain 开始那一刻**就把判据写进数据根(`orphan` /
1575
- `reason` / `idleForMs` / `attached` / `inflight` / `lingerMs` + `version` / `at` / `pid`)。壳的 exit
1645
+ - **自退留痕 `<数据根>/exit-last.json`**:自退在 **drain 开始那一刻**就把判据写进数据根(`lane` /
1646
+ `reason` / `idleForMs` / `attached` / `inflight` / `lingerMs`,孤儿还带 `orphanedForMs` + `format` / `version` / `at` / `pid`)。壳的 exit
1576
1647
  watch 往往在流断之后才跑,只看退出码分不清"它自己退的"与"它崩了";读到这个文件就能把这个 pid 归入
1577
1648
  **预期退出**。退出码仍是 **0**,不另立专用码。引擎下次在同一数据根启动时读到它 ⇒ 一行 `exit_last_found`
1578
1649
  然后**删掉**(在场 ⇔ **上一次**退出是自退;它与 `crash-last.json` 是对偶的两只判别位)。
1650
+ 文件带格式号 `format`(当前为 `3`):读到别的格式号(缺键 = 7.145.x 及以前写的)⇒ 一行 **`warn`** `exit_last_stale_format`
1651
+ (`fileFormat` / `buildFormat`)后删掉,**不**投影成 `exit_last_found` —— 只丢那一次自退的留痕,不迁移、不挡启动。
1652
+ 格式 3 的 `lane` 是自退那一拍的车道(`"handover"` / `"orphan"` / `"awaitingFirstAttach"` / `"owned"`,见下一节「交接车道」):7.145.x 的 `orphan:true` ⟺ `lane:"orphan"`;
1653
+ 候首触窗到期 ⟺ `lane:"awaitingFirstAttach"`;`reason !== "idle"` ⟺ `lane === "orphan"`。
1579
1654
  - **拍频不开旋钮**(2s 写死)。有真需求再议 —— 这里刻意不增殖一个只有一个人会调的键。
1580
1655
  - 日志:装配时一行 `parent_watch_armed`(`info`,带 `pid` / `ppidAtArm` / `intervalMs`),成孤儿时一行
1581
1656
  `parent_watch_orphaned`(**`warn`** —— 非计划内的生命周期事件,带 `reason: "reparented" | "esrch"`),
1582
- 真退的时候才是 `engine_lease_auto_exit`(带 `orphan: true` + 同一个 `reason`)+ `draining_started`。
1657
+ 真退的时候才是 `engine_lease_auto_exit`(带 `lane: "orphan"` + 同一个 `reason`)+ `draining_started`。
1583
1658
  - 坏值(`0` / 负数 / 小数 / 非数字 / 超过 `2147483647`)**拒启并指路**:`0` 和负数在 `process.kill` 里的
1584
1659
  语义是**进程组**,超出 int32 的值 `process.kill` 根本收不下(会变成"armed 了但永远探不动")。要关掉就
1585
1660
  **不设**这个键。探活若给出既不是 `ESRCH` 也不是 `EPERM` 的 errno,会打一条(仅一条)**`warn` 档**
@@ -1601,6 +1676,8 @@ peer 还在用。这条腿问的是那个真正的问题 —— **还有没有
1601
1676
  ENGINE_AUTO_EXIT=true # 壳 spawn 引擎时注入;服务器部署**不要**设
1602
1677
  ENGINE_LINGER_MS=60000 # 可选,默认 60s,下界 1000(父还活着时的窗)
1603
1678
  ENGINE_ORPHAN_LINGER_MS=2000 # 可选,默认 2s(父没了之后的窗;见上一节)
1679
+ ENGINE_FIRST_ATTACH_MS=180000 # 可选,默认 180s(7.146.0 起;还没有过任何非探针请求时的窗,取 max(本值, ENGINE_LINGER_MS);0 = 不给额外宽限)
1680
+ ENGINE_HANDOVER_LINGER_MS=1800000 # 可选,默认 30 min,下界 60000(壳经 POST /v1/engine/handover 交接之后的窗;见下文「交接车道」)
1604
1681
  ```
1605
1682
 
1606
1683
  - **默认 `false` = 不自退**,存量部署逐字零变化。一台 k8s 上的 replica 半夜没人用是**正常低谷**,
@@ -1608,16 +1685,52 @@ ENGINE_ORPHAN_LINGER_MS=2000 # 可选,默认 2s(父没了之后的窗;见上一
1608
1685
  🔴 自退腿在「本键为真 **或** `SEMA_PARENT_PID` 在场」时装配(两件并成一件)。本键关着而
1609
1686
  `SEMA_PARENT_PID` 设了 ⇒ 父还活着期间**永不自退**(窗 = ∞,与以前逐字同),父没了才用孤儿窗。
1610
1687
  两键都缺席 ⇒ 整件不装配,零定时器。
1611
- - **自退判据 = 三合取,且必须连续满**「当前窗」(父还活着 = `ENGINE_LINGER_MS`;父没了 =
1612
- `ENGINE_ORPHAN_LINGER_MS`。判据一条,只有窗长两态):① 没有附着的 SSE 流;② 没有在飞的 run
1613
- (与 `/health` 的 `inflight` **同一个读数**);③ 没有非探针 HTTP 触点(`/health`、`/readyz`、`/metrics*` 不算 ——
1688
+ - **自退判据 = 三合取,且必须连续满**「当前窗」(窗长由车道定,从上到下第一条成立者:交接 = `ENGINE_HANDOVER_LINGER_MS`;父没了 =
1689
+ `ENGINE_ORPHAN_LINGER_MS`;还没被碰过 = `max(ENGINE_LINGER_MS, ENGINE_FIRST_ATTACH_MS)`;其余(父还活着、已被碰过)= `ENGINE_LINGER_MS`;孤儿窗见上一节,「首次附着前的宽限」与「交接车道」见下面各自一条。判据一条,窗长按车道取):① 没有附着的 SSE 流;② 没有在飞的 run
1690
+ (读的是**阻断集** `http + leader + backgroundAgents` —— 含活过起它的那条腿、还在跑的后台子代理 —— 与排空等的、SIGHUP 空闲窗同一个读数;`/health` 的 `inflight` 是它再加会话驻留的后台 shell / monitor 的**总数**,见下一条);③ 没有非探针 HTTP 触点(`/health`、`/readyz`、`/metrics*` 不算 ——
1614
1691
  探针不是「有人在用」),**且**最后一条附着流断开也已满窗。任何一条不满足即把计时**清零**。
1615
1692
  (最后那半句不是修辞:一条活得比 15s 拍频还短的流整个生命周期都落在两拍之间,自查腿从没看见过它 ——
1616
1693
  只有在**关闭点**记一笔离场时刻,「已经没人附着满 N 秒」才是精确的。)
1694
+ - **`/health.inflight` 的两个事实(7.146.0 起;后台子代理 7.147.0 起)**:模型在会话里 `run_in_background` 起的 shell / monitor 与(单用户形下)后台子代理活过起它的那一条腿 —— 此前 `/health.inflight` 对它们读 0,读 `/health` 判「引擎是否空闲」的判官只好另拼谓词。
1695
+ 现在 `/health.inflight` 是**总数**:这台引擎此刻托管着多少活,把仍在跑的后台子代理与仍在跑的、会话驻留的、**没声明 retain** 的后台 shell / monitor 一并数进去;
1696
+ 分项在 `/health.inflightBreakdown { http, leader, backgroundAgents, sessionResident }`,`inflight` 恒等于四项之和。
1697
+ **判停读的是另一个和 —— 阻断集 `http + leader + backgroundAgents`**(还有没有必须等完的活):自退、排空、SIGHUP 空闲窗都读它。后台子代理在内 —— 它是一条在干活的模型腿,
1698
+ 壳没了、没人附着时引擎等它干完再退(7.147.0 起;此前随受控退被中断);停在审批门上的子代理不计(耐久停驻,下次起来仍可续)。驻留 shell 只报、不阻断
1699
+ (驻留进程随受控退被收掉;一只不退出的 `while true` 不会让孤儿引擎永生,也不会让排空等满窗)。活过起它的腿的 workflow 不计(任务表分不出它在干活还是在等审批)。
1700
+ **谁受影响**:把 `inflight` 当「HTTP 腿数」读的监控面(数值变大,原口径 = `http + leader`);排空会多等还在跑的后台子代理(上界仍是排空期限);一只挂死的后台子代理撑着孤儿窗 / 租约窗,至多一个停滞窗(下一条)。
1701
+ **补偿**:分项键;`POST /v1/sessions/:id/children/retire` 结清以该会话为根的后台子代;要不要因为引擎托管着驻留 shell 而推迟换代 / 回收由判官自己定。
1702
+ - **后台子代理的停滞收割(`BG_AGENT_STALL_MS`,7.147.0 起)**:读在飞数之前(判停的每一拍、`/health`),引擎先把**最后一次工具活动**距今超过这个窗的会话驻留后台子代理收掉 ——
1703
+ 行落 `killed`、归因 `system`、它的 run 中止,运维日志一行 `background_agent_stall_reaped`。于是一只不再有进展的子代理(挂死的,或收尾时引擎等的那次账本写挂住的)最多撑住自退窗一个停滞窗。
1704
+ 「静默」只按工具活动算:子代的一次工具开始 / 结束是一拍;一轮很长的模型输出、一条很长的工具调用、在审批 / 问答卡上等人,在窗内都只是静默。
1705
+ 缺省 = 下限 = 本部署等人的四个窗取大(`STREAM_ASK_WINDOW_MS` / `UNREACHED_ASK_TTL_MS` / `QUESTION_TTL_MS` / `MCP_ELICITATION_TTL_MS`)+ 引擎单命令上限(`BASH_MAX_TIMEOUT_MS`,缺省 600 s);
1706
+ 单用户形 = 24 h(活审批卡窗)+ 10 min。**只许拉大**:写得比下限小(含 `0`)⇒ 钳到下限并在起服 warn 点名 —— 比本部署自己允许的最长等人还短的窗,会把一只等你回来批的子代当僵尸收掉;
1707
+ 想让僵尸收得更快,调小那几个等人窗(没开审批 / 问答的部署,那几个窗本来就不起作用,调小它们没有别的代价)。
1708
+ **谁受伤**:单步静默超过窗的合法子代会被收 —— MCP 调用(引擎时限缺省约 27.8 h,调用中的进度通知不算活动)、前台嵌套委派(子委派里的工具活动与等人都不给它打拍)、
1709
+ 一轮极长的模型输出;引擎按墙钟量静默,墙钟前跳或整机睡眠超过窗,醒来第一读就会收掉睡前之后再没打拍的子代。补偿 = 把 `BG_AGENT_STALL_MS` 拉大。
1710
+ 读数只会少数、不会多数:枚举的会话集是本进程提交过的会话(LRU 上界 4096、删会话即清),被挤出的会话、不经提交链的内部车道起的驻留行、删会话级联中途失败的会话不可见。
1711
+ 某个会话的任务表读失败时按 1 只在跑的后台子代理计(读不出 ⇒ 非空闲,判停也看得见)并记一行 `session_resident_background_unreadable`(每会话一次),读口本身不抛。
1617
1712
  - **「run 在跑就不退」是结构性保护,没有旋钮可以关掉它** —— 最后一个壳关掉、后台 run 还在烧的场景里,
1618
1713
  引擎必须活到那条 run 落地。
1619
1714
  - **`ENGINE_LINGER_MS` 防的是壳重启窗**:壳崩了/重启的 1-3s 里附着数确实是 0,窗太短就在用户眼皮底下
1620
1715
  把引擎杀了。窗内任何一次重连即清零重新起算。
1716
+ - **首次附着前的宽限(`ENGINE_FIRST_ATTACH_MS`,7.146.0 起)**:引擎起服以来**还没有过任何非探针 HTTP 请求**(打开 SSE 流算;
1717
+ `/health` `/readyz` `/metrics*` 不算)时,「当前窗」是 `max(ENGINE_LINGER_MS, ENGINE_FIRST_ATTACH_MS)`(缺省 180s);首个非探针请求一到就回到
1718
+ `ENGINE_LINGER_MS`,**不再回来**;父没了照旧是孤儿窗(孤儿优先)。防的是一次性 `-p` 壳在 spawn 与第一个真请求之间被饿住(高载宿主 / CI)
1719
+ 超过 60s —— 以前引擎会在从没被碰过的时候就退掉,壳醒来只剩起步失败。在飞的 run 本身不算首个请求(它在跑时本来就不退,跑完仍按这个窗计)。
1720
+ 判据与采样拍都没变,只是窗长多了一态(「还没被碰过」;连同下一条的交接车道,窗长共四条车道:交接 → 孤儿 → 还没被碰过 → 已被碰过)。**代价**:spawn 之后从没被碰过、父却一直活着的引擎要多占
1721
+ 端口 / 数据根锁 / 库连接到 180s;`ENGINE_FIRST_ATTACH_MS=0`(或任何不大于 `ENGINE_LINGER_MS` 的值)逐字回到以前的行为。
1722
+ `ENGINE_AUTO_EXIT` 关着时这一态与父活着的窗一样「永不」。
1723
+ - **交接车道(「Move to background and exit」;两口 `POST /v1/engine/handover` / `POST /v1/engine/attach`,能力位 `engine.handover`)**:
1724
+ 壳退出时想让后台 shell 接着跑完,就先 `POST /v1/engine/handover`,体恰为 `{ "reason": "background_exit" }`,然后自己退出、不停引擎;下一只壳起来时
1725
+ `POST /v1/engine/attach`(体 `{}` 或空)收回。附着租约因此有四条车道,一只派生函数,从上到下第一条成立者:**交接**(调过交接口、之后还没有 attach)>
1726
+ **孤儿**(父没了)> **还没被碰过** > **已被碰过**。交接车道的窗是 `ENGINE_HANDOVER_LINGER_MS`(缺省 30 min),**且只在这条车道里**引擎此刻一退就会被退出边界硬收的
1727
+ 后台 shell(在跑、整张注册表、不含 retain 行)算工作 —— 后台 `sleep 2400` 在跑时引擎不退,跑完再静默满 30 min 才退;别的车道照旧(后台 shell 不算)。判据、采样拍、出口(同一条排空腿 + 留痕)都只有一份。
1728
+ 这只数与 `/health.inflightBreakdown.sessionResident` 是两问:后者只数经过提交的会话的会话驻留行,交接数还含 run 腿上的行与没经过提交的会话的行,读得出时 ≥ 驻留项;
1729
+ 读不出(某行 env 的 retain 声明读抛)⇒ 按 1 计(不退)、一行 `warn` `engine_handover_shell_count_unreadable`(每次起服至多一行)。
1730
+ 交接口幂等(再调回同一个 `since`);attach 在不交接时照回此刻车道。交接位只由这两口改:壳退出、父死、首触都不动它。终端关掉的 SIGHUP 在交接车道里**不计时**
1731
+ (寿命只归附着租约;下一只壳 attach 之后 SIGHUP 的空闲窗照旧)。两口不可用 ⇒ 501 `capability.engine_handover_unavailable`:自退腿没装(两键都没设)、
1732
+ `REQUIRE_PRINCIPAL=true`,或正在停机;体不合形 ⇒ 400 `request.field_invalid`,什么都不改。**代价**:交接之后引擎多占端口 /
1733
+ 数据根锁 / 库连接到后台 shell 跑完再加交接窗;要短就调 `ENGINE_HANDOVER_LINGER_MS`(下界 60s —— 比壳重启窗还短的交接窗等于选了停引擎),要立刻停就 `SIGTERM`。
1621
1734
  - **实际等待 = `[窗, 窗 + 一拍)`**:最后一条租约灭在两拍之间时要到下一拍才被看见。方向是**保守**的
1622
1735
  (只会等更久)。**拍频由窗推导、不是第二个旋钮**:`min(15s, 生效得到的每一个窗)` —— 没装父监视的
1623
1736
  服务器形仍是 15s 一拍(一拍都没多跳),装了父监视的壳形按 2s 采样(否则「孤儿 2s 内退」这句承诺会被
@@ -1625,18 +1738,31 @@ ENGINE_ORPHAN_LINGER_MS=2000 # 可选,默认 2s(父没了之后的窗;见上一
1625
1738
  - 判死后走的是**和 `SIGTERM` 逐字同一条**优雅排空腿:拒新提交(503 + `Retry-After`)→ 候在途腿跑完 →
1626
1739
  停机。in-flight 的 run 照常结算 / park,**不是**裸退出;而且因为是受控退,`crash-last.json` 会被清掉
1627
1740
  ⇒ 下次启动**不会**把这次自退误读成崩溃。
1628
- - 日志:装配时一行 `engine_lease_armed`(带 `lingerMs` / `orphanLingerMs` / `tickMs`;`lingerMs: null`
1629
- = 这一档窗永不到期),自退时一行 `engine_lease_auto_exit`(带 `orphan` / `reason` / `attached` /
1741
+ - 日志:装配时一行 `engine_lease_armed`(带 `lingerMs` / `orphanLingerMs` / `firstAttachMs`(旋钮值)/ `firstAttachWindowMs`(生效值,
1742
+ 两者不同 = 旋钮被 `ENGINE_LINGER_MS` 托到了它)/ `handoverLingerMs`(`null` = 没装交接车道)/ `tickMs`;`lingerMs: null` / `firstAttachWindowMs: null`
1743
+ = 这一档窗永不到期),自退时一行 `engine_lease_auto_exit`(带 `lane`(那一拍的车道)/ `reason` / `attached` /
1630
1744
  `inflight` / `idleForMs` / `lingerMs`,孤儿还带 `orphanedForMs`),随后就是那条 `draining_started`。
1631
- `reason` 是闭集:`"idle"`(没人用了)/ `"reparented"` / `"esrch"`(后两者 = 父没了那一路,`orphan:true`)。
1745
+ `reason` 是闭集:`"idle"`(没人用了)/ `"reparented"` / `"esrch"`(后两者 = 父没了那一路,`lane:"orphan"`)。
1746
+ 车道经两口变化时一行 `engine_lane_changed`(带 `from` / `to` / `reason`(`"handover_requested"` | `"attached"`)/ `since`)。SIGHUP 空闲窗起表时那一行
1747
+ `sighup_idle_watch_armed` 带 `work`(此刻有没有工作,与附着租约同一只判据)/ `idleGraceMs`。
1632
1748
  同一份读数会写进 `<数据根>/exit-last.json`(见上一节)。
1633
1749
  - **租约窗可读(`GET /health` 的 `engineLease`,7.141.0 起)**:装了自退腿的引擎在 `/health`(无鉴权)上多一个对象键 `engineLease: { lingerMs, tickMs }` —— `lingerMs` = 自退腿会执行的**最短**静默窗(`ENGINE_LINGER_MS` 与 `ENGINE_ORPHAN_LINGER_MS`〔装了父监视才有〕取小),`tickMs` = 采样拍(自退发生在**生效窗**满之后、至多再晚一拍;父还活着时生效的是较长的父活着的窗,所以实际自退可以比 `lingerMs` 晚得多 —— `lingerMs` 是安全节拍的上界(触点比它密,在任何状态下都先于自退),不是自退时刻的预言)。壳持租约的触点节拍据此定:比 `lingerMs` 密,在任何状态下都留得住引擎(附着着 SSE 流时本来就留得住,约束的是没有 SSE 的纯轮询壳),不必再按交给 spawn 的那份推 —— 别处起的短窗引擎、父死之后只剩孤儿窗的引擎也读得到。**取最短、不取「此刻生效」**:此刻生效的窗在父死那一拍从长窗掉到孤儿窗,按旧读数定节拍的壳会在下一次读 `/health` 之前被拆台。两个数与上一条 `engine_lease_armed` 日志是同一组窗。没装自退腿(两键都没设)、或腿已因排空 / 自退触发而收摊 ⇒ 键缺席(不补 0)。
1750
+ **7.146.0 起同一格多一个 `firstAttach: { windowMs, since, attachedAt }`**:`windowMs` = 生效的首次附着窗(`max(ENGINE_LINGER_MS, ENGINE_FIRST_ATTACH_MS)`,
1751
+ 与 `engine_lease_armed.firstAttachWindowMs` 同数),`since` = 窗开始(装配那一拍,epoch ms),`attachedAt` = 首个非探针请求的时刻(epoch ms;`null` = 还没有过,
1752
+ 非 `null` ⇒ 这一窗已永久关闭)。父一直活着且一直没人碰时,`since + windowMs` 是「最早何时自退」的**下界**,不是预言(父死之后孤儿窗优先、可以更早退 —— 那一态看同一格的 `lingerMs`;期间的在飞会让它后移,另有至多一拍滞后;
1753
+ 两个时刻是装配那一拍的挂钟锚加单调差,系统改钟之后会与真实期限漂开)。在场 ⇔ `engineLease` 在场 ∧ `ENGINE_AUTO_EXIT` 开;缺席 = 老 worker
1754
+ 或只靠父监视(那一态永不到期)。一次性 `-p` 的壳据它处理起步段。
1755
+ 同一格还有 `lane`(此刻的车道,`engineLease` 在场即在场)与 `handover: { since, reason, lingerMs, backgroundShells }`(只在交接车道在场:
1756
+ 进入时刻、缘由、交接窗、引擎此刻一退就会被硬收的后台 shell 数;在飞数读同一份应答的 `inflightBreakdown`);装了交接车道时最短窗 `lingerMs` 把交接窗算进去(缺省 30 min,不改默认读数)。
1634
1757
  - **坏值拒启**:`ENGINE_LINGER_MS` 必须是落在 **`[1000, 86400000]`(1s…24h)** 的整数毫秒,**两端都承重**:
1635
1758
  下界防 `ENGINE_LINGER_MS=60`(以为单位是秒)—— 60ms 的窗等于把整条防误杀腿静默摘掉;上界防 `1e100`
1636
1759
  这类值 —— 它能过朴素的「是正整数吗」检查,而计时**永远追不上它**,于是配置看着好好的、自退其实整件
1637
1760
  失效。两端一律**拒启并指路**而不是夹取。本键**没有**「关掉」的取值 —— 关自退的唯一开关是
1638
1761
  `ENGINE_AUTO_EXIT=false`。校验**不看** `ENGINE_AUTO_EXIT` 的表态:否则手滑值可以在一台今天关着自退的
1639
1762
  机器上一直躺着,等哪天有人打开时当场生效。
1763
+ `ENGINE_FIRST_ATTACH_MS` 与它**同一只校验器、同一对上下界**,只多放行字面 `0`(= 不给额外宽限,生效窗落成 `ENGINE_LINGER_MS`,不会更早退;`-0` / `0.0` / `1e-400` 这类零形照常拒);
1764
+ `1..999`(秒当毫秒)与其余坏值同样拒启,同样不看 `ENGINE_AUTO_EXIT` / `SEMA_PARENT_PID` 的表态。
1765
+ `ENGINE_HANDOVER_LINGER_MS` 同一只校验器、同一个上界,下界 `60000`(没有「关」的取值;不交接的壳从不用它),解析同样无条件。
1640
1766
  - 🔴 **已知收窄(成文,现版不收)**:**parked-待赎回** 的 run 不在判据里,只看 running。数 parked 要发
1641
1767
  SQL,而这是一只每 15s 无限期跑下去的定时器 —— 不值得为它引入周期性 SQL 轮询。parked 行是 **durable**
1642
1768
  的,引擎退掉不丢:下次起来(壳的下一次 spawn)仍可赎回,伤害面是**赎回被推迟**而不是丢失。
@@ -1709,7 +1835,8 @@ invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并
1709
1835
  **「剥了什么」这一行**:起服(host 腿语言服务器管理器构造时)一行 `warn` `secret_env_scrubbed`,`site:"server.lsp.spawn-env"`,
1710
1836
  字段 `keys`(这次剥掉的键名)· `allowed`(当前放行名单)· `hint`(出路句,点名 `LSP_ENV_ALLOW=NAME,NAME`)· `summary`;**值永不进日志**。
1711
1837
  放回的键不算剥除(不在 `keys` 里、不进计数 `secret_env_scrubbed_total`);同一个键每进程只报一次;没有剥除 ⇒ 不发。
1712
- 其余剥除现场(模型可驱动的 host shell `server.remote-env-host.spawn-env`、三条 git 腿、local-docker / adb 两条 CLI 腿、settings hooks 的命令腿)没有部署级放行旋钮,它们的同名行照旧是 `info`、不带 `allowed` / `hint`。
1838
+ 其余剥除现场(三条 git 腿、local-docker / adb 两条 CLI 腿、settings hooks 的命令腿、本服务自己的 git worktree 基环境 `server.remote-env-host.git-base`〔它从不交给引擎〕)没有部署级放行旋钮,它们的同名行照旧是 `info`、不带 `allowed` / `hint`。
1839
+ **模型可驱动的 host shell(`site:"server.remote-env-host.spawn-env"`)不写这一行**:它扣了哪些变量名由引擎说,不再有第二个读者 —— 根腿第一次调 shell 工具时一条通告 `inherit_env.names_withheld`(`detail.names` 全部被扣的名、`detail.ownCredentials` 其中本部署自有凭据那一半,每会话每名字集一次〔引擎的去重记录有界,尽力而为〕;委派子代调 shell 不出这条;**只给名字、从不带值**),运维在服务日志的结构化 `engine_notice` 行里读同一条;计数 `secret_env_scrubbed_total{site="server.remote-env-host.spawn-env"}` 照记(不带名字)。按这条腿的 `secret_env_scrubbed` 行告警 / 过滤的运维脚本改读 `engine_notice` 行(`code` = `inherit_env.names_withheld`)。
1713
1840
  现场按**腿**命名(`server.<模块>.<腿>`),不按继承词:7.131.x 及以前 host shell 那条腿叫 `server.remote-env-host.inherit-scrub`,7.132.0 起改名(计数器 `secret_env_scrubbed_total{site}` 与日志字段 `site` 同改,无别名)。
1714
1841
 
1715
1842
  **每一条宿主子进程腿的 env 都经同一只组合(7.132.0 起射程写全)**:本服务在宿主上起的每一只子进程,env 都由引擎按这条腿的继承词组合,本服务自己的凭据(起服声明的名与值)任何词都不交。
@@ -1732,10 +1859,10 @@ invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并
1732
1859
  继承整份用户环境(CC 交互形,用户自己的凭据形变量在内)—— 🔴 **但不含本服务自己的凭据**(7.129.0 起,引擎 7.51.1):配置目录全部 `type:"secret"` 名
1733
1860
  (`SERVICE_AUTH_TOKEN(S)` / `APPROVAL_HMAC_KEYS` / `MODEL_API_KEY` …)与名单型旋钮名起服已向引擎声明为本进程自有凭据,生效配置里每条模型条目的 `apiKeyEnv` 也在名单里
1734
1861
  (中心热应用新增 / 改名条目时在模型目录换代前重声明,名单只随落地的世代增长 —— 改名后旧名照扣到重启,没落地的候选不留痕;条目的 `apiKeyEnv` 是引擎扣不住的名,或它点到的名正被某只名单型旋钮交给沙箱 / 语言服务器(见下文沙箱名单段)⇒ 该候选整拒),`"all"` = 整份环境**减**它们,任何继承词都不把它们
1735
- 交给模型 shell(能力位 `inherit_env.own_credentials_withheld` = 最近一次声明 ⊇ 当前生效配置应声明的集合;真要让模型 shell 持某枚令牌 ⇒ 另放一个不在配置目录、也不是任何模型条目 `apiKeyEnv` 的名,或经 `settings.env` 显式授予);这些变量此刻的**值**也交给引擎按值扣(同值换名的副本同样进不了模型 shell),引擎判某个值「不是凭据值」(空白 / 占位词 / 一个字符重复 / 不足 8 字符)⇒
1862
+ 交给模型 shell(能力位 `inherit_env.own_credentials_withheld` = 最近一次声明 ⊇ 当前生效配置应声明的集合;真要让模型 shell 持某枚令牌 ⇒ 放在引擎进程环境里一个不在配置目录、也不是任何模型条目 `apiKeyEnv` 的名下,配 `inheritEnv:"all"`;7.146.0 起请求 `settings.env` 不收凭据,见 9.5j);这些变量此刻的**值**也交给引擎按值扣(同值换名的副本同样进不了模型 shell),引擎判某个值「不是凭据值」(空白 / 占位词 / 一个字符重复 / 不足 8 字符)⇒
1736
1863
  那一个值不按值扣(名照扣、不拒启,能力位 `inherit_env.own_credential_values_withheld` 答 false),起服与模型目录换代时每个这样的变量出**一条** warn 结构化行 `own_credential_value_not_withheld { name, reason }`
1737
1864
  (`reason` 是引擎的拒句原文,从不回显值;起服第一步 logger 还没建时,这一行当拍以同一行形写 stderr,不缓冲;logger 接上之后——模型目录换代并进的条目的值被拒——这一行受 `LOG_LEVEL` 约束,`error` 级读不到,机读事实读上面那个能力位)—— 按事件名告警,给该变量一个真值即可。值是**网关密钥**(`MODEL_API_KEY` / `ANTHROPIC_API_KEY`,起服交给模型适配器的那一个)时,进程输出里在这一行之外还有**引擎自己**的一行裸文本(`a credential the engine was handed is …`):引擎在适配器构造口收下密钥的那一刻对同一个值说一次,这个收值口没有通告席,落引擎自己的 `console.warn`(引擎已知限制;本服务不是它的写者,也不改写它);给密钥一个真值两行一起消失。`"scrub"` / 缺席 = 上面的缺省剥除。🔴 **`"all"` 只在单用户部署兑现**:`REQUIRE_PRINCIPAL=true`
1738
- ⇒ 422 `request.host_posture_refused`(射程与写容纳面那条腿同一只谓词,`TASK_WRITE_FACE_ESCALATION` 不管它);非 host 车道带 `"all"` ⇒ 400。词表外的值(`"ALL"` · `""` · 名单 `["PATH"]` · 布尔 · `null` …)、非 host 车道带 `"all"`、部署不接 authorizer 时带 `"all"` 这三只都是 `400 request.field_invalid`,7.137.0 起体另带 `field: "inheritEnv"` 与 `reason`(`"unknown_word"` = 是字符串但不在词表;`"not_a_string"` = 根本不是字符串;`"lane_not_host"` = `"all"` 落在非 host 车道;`"no_authorizer_session"` = `"all"` 而部署不接 authorizer),壳按这两位认拒因、不解析句子(句子 / 码 / 状态不变;续跑腿同判,折叠 409 随体带同一组)。托管部署的 `422 request.host_posture_refused` 不在这组:它说的是「这台部署的姿态不给」,不是这个键写错,按 `errorCode` 认、不带这两位。部署方
1865
+ ⇒ 422 `request.host_posture_refused`(射程与写容纳面那条腿同一只谓词,`TASK_WRITE_FACE_ESCALATION` 不管它);非 host 车道带 `"all"` ⇒ 400。词表外的值(`"ALL"` · `""` · 名单 `["PATH"]` · 布尔 · `null` …)、非 host 车道带 `"all"` 这两类都是 `400 request.field_invalid`,7.137.0 起体另带 `field: "inheritEnv"` 与 `reason`(`"unknown_word"` = 是字符串但不在词表;`"not_a_string"` = 根本不是字符串;`"lane_not_host"` = `"all"` 落在非 host 车道;7.147.0 起不再有第四词 `"no_authorizer_session"` —— 一次提交的根会话恒由 authorizer 交出),壳按这两位认拒因、不解析句子(句子 / 码 / 状态不变;续跑腿同判,折叠 409 随体带同一组)。托管部署的 `422 request.host_posture_refused` 不在这组:它说的是「这台部署的姿态不给」,不是这个键写错,按 `errorCode` 认、不带这两位。部署方
1739
1866
  **没有**旋钮能替调用方打开它,也没有旋钮能在单用户部署上关掉它(单用户部署的用户把他自己的环境交给他自己驱动的 shell);起服一行 info
1740
1867
  `task_inherit_env_honored {honored}` 告诉你这台机器认不认。上面「部署级名单不给模型驱动 shell」那条不变 —— 两件事是两条轴(部署名单 vs 调用方 × 单用户射程)。
1741
1868
 
@@ -1745,8 +1872,8 @@ invalid_using_default` 警告 + 退回缺省值」这条臂随词表放宽一并
1745
1872
  `MAX_NEW_TOKENS` / `MAX_COMPLETION_TOKENS` / `MAX_BATCH_PREFILL_TOKENS` / `DB_FOREIGN_KEYS` / `JSON_SORT_KEYS` 需要点名放行,现在不用)。
1746
1873
  **精确名,不是形**:`USERS_MAX_NEW_TOKENS`、`MAX_NEW_TOKENS_2` 这类装饰过的变体照剥。引擎自己的「剥了什么」通告码
1747
1874
  `config.secret_env_scrubbed` 只在引擎**自己**剥的子进程上发;本服务九条宿主腿(host shell / project-memory / center-plugins / leader 三条 git 腿 / 语言服务器 /
1748
- bake-runner / local-docker 与 adb 两条 CLI 腿 / settings hooks 命令腿)都交本服务组合好的环境,所以运维面看的是上面这行
1749
- `secret_env_scrubbed`,不是那只通告码。这只通告码**每进程一次**、引擎里几处剥除共用一闩(工具 shell / 语言服务器 / 7.103.2 起还有 `runSpec` 的
1875
+ bake-runner / local-docker 与 adb 两条 CLI 腿 / settings hooks 命令腿)都交本服务组合好的环境,所以运维面看的不是那只通告码:除 host shell 外的各条腿看上面这行
1876
+ `secret_env_scrubbed`,host shell 腿看 `engine_notice` 行里的 `inherit_env.names_withheld`(上一段)。这只通告码**每进程一次**、引擎里几处剥除共用一闩(工具 shell / 语言服务器 / 7.103.2 起还有 `runSpec` 的
1750
1877
  oracle 门子进程,core 7.33.8):oracle 门若先于工具 shell 剥,通告只进 oracle 门那一处(`RunSpecOptions.onNotice`,没接就进 console),接在执行环境上的
1751
1878
  结构化通告收不到。本服务不调 `runSpec`,不受影响;嵌入引擎、自接 `runSpec` 的宿主给它交同一只 `onNotice`。
1752
1879
  **OTLP 头变量一族(7.103.2 起,core 7.33.7)**:`OTEL_EXPORTER_OTLP_HEADERS` 与按信号分的 `_TRACES_` / `_METRICS_` / `_LOGS_` / `_PROFILES_HEADERS`、
@@ -1821,6 +1948,25 @@ bake-runner 要用 ⇒ `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW` 点名放回。
1821
1948
  > 不带任何凭据的列表 `401 auth.unauthorized`、`/metrics` 族 `401`(要 `METRICS_TOKEN`)。仍成立的边界:没配 service token 的部署上,不带登录令牌的请求自报的主体头仍按身份读
1822
1949
  > (与 `REQUIRE_PRINCIPAL` 无 token 形同读)—— 要让「没令牌就没有身份」成立,配 `SERVICE_AUTH_TOKEN`(全局凭据门:每个请求要么带 service token、要么带登录令牌)。
1823
1950
 
1951
+ > **不报主体的 service token 是谁(`SERVICE_TOKEN_ACTS_FOR_DEPLOYMENT`,7.146.0+)**:出示这台部署的 service token 而**不带主体头**的请求,缺省(不设 = `true`)代部署本人说话 ——
1952
+ > fleet 读面(舰队流 / 会话列表 / 发件链接 / 名册 / trace / 用量)看全舰队、运维聚合口(`/metrics*`、`GET /v1/tasks/source-summary`)照放、删除 / fork / 同步对任何会话照开。
1953
+ > 设 `SERVICE_TOKEN_ACTS_FOR_DEPLOYMENT=false` ⇒ 它是**无身份调用方**,各面按既有的「过了凭据门、没有主体」那一臂答:fleet 读面与写档(删除 / fork / 同步)`401 auth.unauthorized`(identity 句)、
1954
+ > 运维聚合口 `401`(operator 句)、舰队流在多租户部署上 `401 auth.principal_required`、在单用户部署上落 `default` 那一族的行;无主的委派子代会话在单用户形(未开 `REQUIRE_PRINCIPAL`)上与别人的行同一个 `404`、在多租形上由会话读面的主体门先答 `401 auth.principal_required`(缺省形同样如此,本位不改这一面)。
1955
+ > 带主体头的请求两形都是那位主体(不变);建 run、run 面(`/v1/runs/:id` 族)、A2A `tasks/get` 不读本位;没配 service token 的部署上设 `false` 不改变任何东西。
1956
+ > 给谁:单 service token + 多主体的网关部署(网关替每个用户写主体头),关掉「漏带头 = 看全部」。**它不解决「网关不可信」**:持 service token 的一方在头里写谁就是谁 —— 鉴权桥不改这一点(桥只给带登录令牌的请求验身份,带 service token 的请求照旧信主体头);要防这一层,别把 service token 交给不可信的一方(不可信的调用方走鉴权桥的登录令牌),或开直连门(主体只认签名的 principal 令牌,自报的主体头不再是身份)。
1957
+ > 词表布尔(`true/false/1/0/yes/no/on/off`,词表外拒启),只有 env 一个座,起服读一次(改了要重启);全部副本设同一个值(滚动重启完成之前收窄不完整);
1958
+ > 起服打一行 `service_token_posture { actsForDeployment: false }`;`GET /health` 的 `auth.serviceTokenActsForDeployment` 报这台部署的答案(上面「自报位」)。
1959
+ > 开 `false` 前把运维脚本 / 面板按端点换凭据(「位关了」与「token 错了」答同一个 `401`,区分就是探测面):
1960
+ >
1961
+ > | 端点 | 开 `false` 之后用什么读 |
1962
+ > |---|---|
1963
+ > | trace 五口(`GET /v1/tasks[/:id/turns\|stream\|artifacts\|tool-results/:ref]`)/ 用量三口(`GET /v1/usage/summary\|series\|breakdown`)/ `GET /v1/tasks/source-summary` / 舰队流 | 只读 `TRACE_TOKEN`(全舰队只读,在全局凭据门之前判) |
1964
+ > | `/metrics*` | 只读 `METRICS_TOKEN` |
1965
+ > | `GET /v1/sessions` / `GET /v1/sendfile-links` / `GET /v1/agents/roster` | **没有全量替换**:配了 service token 时全局凭据门先拒只读 `TRACE_TOKEN`;带具名主体头,只看到那位主体的 |
1966
+ > | 他人会话的删除 / fork / `/sync/*` | **没有替换**(写面不认只读凭据);带属主的主体头 |
1967
+ >
1968
+ > 回到 `true`(或删掉这一行)重启即复原。
1969
+
1824
1970
  | env 键 | 缺省 | 说明 |
1825
1971
  |---|---|---|
1826
1972
  | `PRINCIPAL_HEADER` | `x-agent-principal` | 上面那个身份头的**名字**。🔴 **缺省值是跨仓 wire 常量,不是给部署方自定义的**:这个名字**不在任何 wire 面上广播**(`/v1/capabilities`、`/health` 都没有它),所以没有下游能在运行期问 server「你叫它什么」——SDK 有一个 `principalHeader` 逃生舱但 cli/client-core 都没接线,浏览器端 BFF 是逐字硬编码,`mcp-oa` 侧车自己读同名 env(各读各的)。本旋钮只服务于「入口网关已经把身份写进别的头名」这一种特殊部署(让 server 迁就既有网关),不是给部署方起新名字用的。**改名即断下游**,但后果分四形(逐条对过代码):`REQUIRE_PRINCIPAL=true` 下提交腿(`POST /v1/tasks|/v1/runs`)是 `401` + **粗码 `auth.unauthorized`**(authorizer 抛的无 code HttpError 走状态码兜底映射),属主寻址的读/动词面是 `401 auth.principal_required`(路由级细码);两者的文案都逐字回显**你配的头名**,那是现场唯一能指认改名的线索。`REQUIRE_PRINCIPAL` 未开时更坏:**新会话照常成功**、每一条被当成**匿名**(session 归属 / 记忆隔离 / 规则车道 / operator 判定静默走无身份分支,无任何错误码),而**接续一条已属主的会话**会 `401`(`session is principal-owned; missing principal header …`)。⚠️ **SSO 与 direct-door 两条身份来源不经过这个头**(`verifiedPrincipal`),改名对它们无影响 —— 混合形部署因此会出现「一半调用方有身份、一半静默变匿名」的混着长。改了后果自负,且必须同批把每一家下游改到同名。取值须是**单个**合法 header 名(带空格/逗号 ⇒ 启动期拒启:它会撕裂 CORS `allow-headers`)。成文契约见 [`docs/ASSISTANT-WIRE-CONTRACT.md` §0.5](docs/ASSISTANT-WIRE-CONTRACT.md) |
@@ -1830,7 +1976,7 @@ bake-runner 要用 ⇒ `LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW` 点名放回。
1830
1976
  > **`401 auth.unauthorized` 的 `error` 是一句出路(7.104.0 起;此前只有一个词 `unauthorized`)**,按这道门收什么分三句,码都是 `auth.unauthorized`、都**不含任何值**,
1831
1977
  > 同一道门上「没带」与「带错」逐字同一句(区分就是探测面):① 全局凭据门 —— 说「带这台部署的 service token 作 Bearer」,并指 sema-up 部署的
1832
1978
  > 令牌在 `~/.sema-up/<project>/.env` 的 `SERVICE_AUTH_TOKEN`;② 运维口(`/metrics*`、`GET /v1/tasks/source-summary`)—— 说要运维凭据
1833
- > (不带主体头的 service token —— 带主体头的 service token 请求代那位主体、不是运维凭据,7.134.0 起;指标口另认只读的 `METRICS_TOKEN`,`source-summary` 另认只读的 `TRACE_TOKEN`);③ 过了凭据门、但这一口要调用方身份(属主寻址读写、规则、记忆)—— 说要一个验过的
1979
+ > (不带主体头的 service token —— 在部署让它代自己说话时:`/health` 的 `auth.serviceTokenActsForDeployment` 为 `true`,7.146.0 起句子带这一条件;带主体头的 service token 请求代那位主体、不是运维凭据,7.134.0 起;指标口另认只读的 `METRICS_TOKEN`,`source-summary` 另认只读的 `TRACE_TOKEN`);③ 过了凭据门、但这一口要调用方身份(属主寻址读写、规则、记忆)—— 说要一个验过的
1834
1980
  > 调用方身份,**不**叫你再去带 service token。逐字句子见 `docs/ASSISTANT-WIRE-CONTRACT.md` 附录 A.1 码表。壳(sema CLI)接 sema-up 引擎的
1835
1981
  > 令牌交法见 `deploy/sema-up/README.md`「壳接入」。
1836
1982
 
@@ -1863,6 +2009,7 @@ PostgreSQL 硬上限 ⇒ 只有缩小),运维日志一行 `submit_ctx_row_too_la
1863
2009
  | `projectMemory` | — | 7.135.0+(能力位 `projectMemory`):`"disabled"` = 这一条 run 不读部署的项目上下文 —— 引擎不调部署的项目上下文座,项目指令文件(`CLAUDE.md` / `AGENTS.md` 链)与项目层都不进上下文,这条腿的 `wiring_manifest` 答 `projectMemory: "disabled"`、本服务的 `instructions` 段缺席;引擎自己的 git 状态、记忆、技能 / hooks / MCP 不受影响,委派与后台子代随父继承。唯一的词是 `"disabled"`(缺席 = 照常读);别的值 `400 request.field_invalid`(与 `fileHistory` 同一道拼写门)。没接项目上下文座的部署照样受理(清单答 `"absent"`) |
1864
2010
  | `outputStyle` / `settings.outputStyle` | — | 输出风格**两个座位、两件事**:风格的**正文**(指南全文)走 `settings.outputStyle`(折进系统提示的追加段);**每轮一句**走顶层 `outputStyle: { name, turnReminder? }`(7.121.0+,能力位 `outputStyle`)—— 原样交引擎,引擎每个回合用自己的标记帧说「`<name> output style is active. <turnReminder>`」(`turnReminder` 缺席用引擎缺省句),流上回声 `steering_injected` `source:"output_style"`。只在非缺省风格时声明顶层键;它的值形由引擎判(`name` 非空、≤256 字;`turnReminder` 在场即非空串;不许别的键),坏值这一轮终态 `failed`、码 `config.output_style_invalid` |
1865
2011
  | `model` | — | 本次用哪个模型:只能点服务端**已配置目录**里的名字 / 档位词 / 模型 id(目录见 `GET /v1/models`;请求体带不进新模型)。未知 ⇒ 400 `request.unknown_reference`(带 `available` 列表);目录里有、但本部署服务不了 ⇒ 400 `request.model_unavailable`;空串 / 非字符串 ⇒ 400 `request.field_invalid` |
2012
+ | `settings.autoCompactEnabled` | — | 7.146.0+(能力位 `taskSettings.autoCompactEnabled`;老 server 对这个键 `400 request.body_shape` —— 见位才发):**布尔**,这条 run 的自动压缩开关(CC settings 同名键),逐字交给引擎的自动压缩座 `compaction.autoCompact`。`false` = 关引擎自起的摘要压缩;显式 `true` = 开(压过部署席的关);缺席 = 部署席的缺省(本服务自己的装配不设部署席 ⇒ 开);非布尔 ⇒ `400 request.field_invalid`。逐字交给引擎,与顶层 `compaction.clampTolerance` 是同一个 `compaction` 对象里的两个成员(开关只有这一个座位,顶层 `compaction.autoCompact` 不被读)。壳的 `DISABLE_AUTO_COMPACT` / `DISABLE_COMPACT` 环境变量由壳折成这个布尔再发(`DISABLE_COMPACT` 多出的「手动也关」由壳自己不发 `/compact`),本服务不读环境变量。🔴 **`false` 关掉的是引擎自起的三遍**:窗阈值那一遍、服务商拒绝过长请求后的恢复压缩、任务尾摘要;对这条 run 的手动压缩(`POST /v1/runs/:id/compact`)**不受影响**(归引擎的另一只座 `manualCompact`,本服务不写它)—— 与参照实现同读;请求投影层的旧工具结果清除与媒体裁剪不受它管。关掉之后撞上窗口上限的 run:守卫线之上引擎先清旧工具结果、仍超线则只保留尾部(较早的消息从该次请求里省掉,会话历史不动);服务商仍 4xx 拒 ⇒ `terminal.kind:"failed"`,拒绝被归为超长输入时 `terminal.detail.errorKind:"input_too_long"`;代价落在发 `false` 的那条 run 自己(更长历史每回合重发,每任务预算上限照常封顶)。读法全文见契约 `settings.autoCompactEnabled` 一节 |
1866
2013
  | ~~tools~~ | 🚫 | **不是受理键**(传了 ⇒ 400 `request.body_shape`,`unknownKeys` 点名)——工具面走场景装配;按请求只能**减**:`allowTools`(7.126.0 起;引擎的可用性选择器,只留列出的,原样透传)/ `excludeTools` / `settings.permissions` 的 deny · ask(`allow` 7.115.0 起是 allow 规则:给出「有人表态」,回答没人表态的服务器工具 / A2A 调用;不加工具、不放掉任何一层自己的问) |
1867
2014
 
1868
2015
  **`TaskResult`(同步 / `done` 事件里拿到的;7.64.0 起终局是**一条** `terminal` 因由,不再有 `status`/`errorCode`/`errorMessage`/`blockedReason` 四个平面键):**
@@ -1880,8 +2027,16 @@ PostgreSQL 硬上限 ⇒ 只有缩小),运维日志一行 `submit_ctx_row_too_la
1880
2027
  | `sessionId` | 续聊用——下次带回 |
1881
2028
  | `terminal.kind` | `completed` / `blocked` / `failed` / `paused`(闭集;`timeout` 已于 core 5.8.0/server 6.0.0 退役——见 §9) |
1882
2029
  | `terminal.code` | `failed` 臂的程序化分支:如 `"conflict"`(乐观锁丢失,可重试) |
1883
- | `terminal.reason` | `blocked` 臂:为什么做不了(缺信息/权限) |
1884
- | `stats.toolCalls` | 工具调用的**尝试**次数,不是「真执行了几次」:模型每发出一次工具调用请求就计一次,计在工具查找与入参校验**之前** —— 请求了没挂载的工具(例:执行车道未点名时的 `Bash`)照样计 `1`;同一条 `Bash` 请求在真执行车道(文件落盘)与未点名车道(没挂、文件不存在)两臂同读 `1`。所以**不能**拿它判「任务真跑了工具」:判「真跑了没有」不能只看一个位:`tool_end.isError` 是逐调用的**工具结果错误标记**(工具信封报错才 `true`),`false` 只说结果没被标错 —— **不**说命令退出成功(Bash 跑完退出码非 0 照样 `false`,退出码看 `structured.exitCode`),`true` 也不证明工具体跑过;「没跑」的正证据是 `notExecuted`;逐腿「有没有手」看 `wiring_manifest.hands`,不看这个数。它是**本腿**计数(续跑腿只计本腿)。 |
2030
+ | `terminal.reason` | `blocked` 臂的**模型面**句:`terminal.detail` 在场时是引擎给模型的 reminder 框句(别渲给人),没有 `detail` 时是 agent 自己报的卡住原因(缺信息/权限,可渲);见表下「blocked 终局的用户面」 |
2031
+ | `terminal.detail` | `blocked` 臂的用户面 / 机读面(只在部署的 `userPromptSubmit` hook 拦下主提交时在场):`{ kind, cause, hookReason? }`;见表下「blocked 终局的用户面」 |
2032
+ | `stats.toolCalls` | 工具调用的**尝试**次数,不是「真执行了几次」:模型每发出一次工具调用请求就计一次,计在工具查找与入参校验**之前** —— 请求了没挂载的工具(例:执行车道未点名时的 `Bash`)照样计 `1`;同一条 `Bash` 请求在真执行车道(文件落盘)与未点名车道(没挂、文件不存在)两臂同读 `1`。所以**不能**拿它判「任务真跑了工具」:判「真跑了没有」不能只看一个位:`tool_end.isError` 是逐调用的**工具结果错误标记**(工具信封报错才 `true`;委派调用 `Agent` 的子代以 `failed` 收场也是 `true`,同步卡 / fork 卡与后台轮询同一判,以 `blocked` 截断收场的同步子代不是),`false` 只说结果没被标错 —— **不**说命令退出成功(Bash 跑完退出码非 0 照样 `false`,退出码看 `structured.exitCode`),`true` 也不证明工具体跑过;「没跑」的正证据是 `notExecuted`;逐腿「有没有手」看 `wiring_manifest.hands`,不看这个数。它是**本腿**计数(续跑腿只计本腿)。 |
2033
+
2034
+ **blocked 终局的用户面是 `terminal.detail`,不是 `terminal.reason`**:`reason` 是**模型面**句 —— `detail` 在场(部署的 `userPromptSubmit` hook 拦下了这次主提交)时,它是引擎给模型的 reminder 框句
2035
+ (`<system-reminder …>` 框 + 会话标记,hook 的话在框里),直接渲给人会把标记露在屏上;没有 `detail` 时它是 agent 自己用 `ReportBlocked` 报的卡住原因,可以渲。
2036
+ 用户面 / 机读面 = `detail { kind: "prompt_blocked_by_hook", cause, hookReason? }`:`cause` ∈ `block` / `crashed` / `timeout` / `cancelled`(hook 的四种收场;提示没有被提交,改了再发有意义),
2037
+ `hookReason` 只在 `cause:"block"` 且 hook 给了话时在场 —— hook 作者的原话(折一行、限 240 字、引擎信封标签去势,本服务与 `reason` 过同一只脱敏器),按不可信文本渲。
2038
+ **消费方渲 `detail`,不渲 `reason`**;`reason` 只在没有 `detail` 时才是可渲的句子。按 `detail.kind` 分支的消费方必须留缺省臂(新的 `kind` 随引擎 minor 加入)。
2039
+ 同一块出现在同步 200 体、流 `done.result`、`GET /v1/runs/:id` 的 `result` 与 `GET /v1/runs/:id/events` 的 `done` 行。
1885
2040
 
1886
2041
  > ⚠️ **任务成败以 `terminal.kind`/`terminal.code` 为准,勿以 HTTP 状态码判**:同步腿 200=提交受理成功(任务可能
1887
2042
  > `terminal.kind:"failed"`+`code`);`POST /v1/runs` 202=已受理(执行期结局落在终态 run 记录/`done` 帧)。执行期
@@ -1996,23 +2151,31 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
1996
2151
  > 本流上同样逐字(不过文本脱敏,卡上其余成员照常脱敏),积压判据把它与 `output` 块的字节同一帧一起计、降级时一并去掉;账本面(账本行与 `/events`、`GET /v1/tasks/:id/turns`、`GET /v1/tasks/:id/stream` 的回放)里卡上**没有** `file.base64`
1997
2152
  > (与引擎对超 48K 的卡自己略去该键的形一致,`originalSize` / `type` 仍在)。7.127.0 / 7.127.1 上这份字节(≤48K 字符)仍随账本行落库并回放,7.127.2 起不带。
1998
2153
  > 会话存档读面同律:`GET /v1/sessions/:id` 的 `messages[]` 里,工具结果消息的 `details` 就是这张卡 —— 媒体卡的 `file.base64` 逐字(不去键、不过文本脱敏),卡上其余成员与非媒体卡照常脱敏(7.128.0 起;此前这一位也被文本脱敏改写)。
2154
+ >
2155
+ > **后台子代的当前状态 `agentState`**(引擎 7.41.0 起过境;本服务零新键,读法成文)`{ status, readAt, row, lastActivityAt?, approval?: { state, checkpointId?, deadline? } }`(时刻 epoch ms;`row` 是 `"live_row"` 或 `"durable_row"`;`approval` 只在 `status` 为 `"parked"` 时在场)。
2156
+ > 带它的应答:本流 / 子代 tail 流 / 账本回放里 `tool_end.structured` 的两张卡 —— `AgentTranscript` 的步骤答话(`structured.type` 为 `agent-transcript`:`structured.agentState`,`steps[i].at` / `inFlight`)与 `ListAgents`(`list-agents`:每个后台子代行 `rows[i].agentState`);
2157
+ > 本服务对卡只换串叶里的凭据形、不枚举卡内的键,`agentState` 逐键原样过(卡在不在由引擎定:类型在卡集里、整卡序列化不超过 65,536 字符才有)。读口 `GET /v1/runs/:id/subagents/:handle/output` 与 `GET /v1/runs/:id/tasks/:tid/output` 的体里 `output.agentState` 与 `content` 里的 `state:` 行(别的副本写下的持久行同读:
2158
+ > 已结行 `lastActivityAt` 是落定时刻,停驻行是停驻时刻,别的副本还标 running 的行答 `not_local` 且无 `lastActivityAt`)。**不带它的应答**(缺席 = 引擎没给):拒答(引擎取得了状态的那几类,首行 `State: …` 在 `output` 文本上;未知或无权限的 id 的拒答两者都没有)、`ListAgents` 里本进程没有活句柄的名册行(`state:"unknown"`)、
2159
+ > 后台子代任务 poll 的 `details`(不进 `structured`;引擎的 `TaskOutput` 工具已退役,这份 poll 如今只有这两个读口)。读口不交检查点座 ⇒ **停驻行(行上有审批 token)的审批卡读 `unreadable`**(无 `checkpointId` / `deadline`;行上没有 token 的读 `missing`),等的那张卡看 `GET /v1/approvals` 的 `pending[]`。名册行 `GET /v1/agents/roster` **不带** `agentState`:行只带持久行自己的列(`updatedAt` 对仍标 running 的行是写者的租约续期、不是活动),活动时刻与审批态读该子代的 output 读口。
1999
2160
 
2000
2161
  ## 7. 其它接口(按需)
2001
2162
  | 端点 | 用途 |
2002
2163
  |---|---|
2003
2164
  | `GET /v1/sessions/<id>` | 审计回溯:当前上下文 + 摘要(owner 校验);消息里模型 / 工具产出的文本与 live 帧、账本帧同一只脱敏(密钥形换占位,其余字节不变);Read 图像 / PDF 的媒体卡(工具结果消息的 `details`)的 `file.base64` 是字节编码,逐字。**委派子代的转录**也走这一口:`<id>` = 子代帧 / fleet 子代行上的 `transcriptId`(未压缩过的子代首条用户消息是委派 prompt;子代跑过的工具是 assistant 消息里的 `toolCall` 段 + 同 `toolCallId` 的 `toolResult` 行;整段逐字条目走 `…/sync/entries`)。体顶层加性键 `agent: { id?, parentSessionId?, rootSessionId? }`(**7.133.0+**)说这是哪只子代:只投影会话行上存下来的委派元组,`id` 与 fleet 行的 `id`、`GET /v1/agents/roster` 的 `handle` 同一个串;键缺席 = 普通会话 / 老 server(`DB_BACKEND=local` 到 7.140.x 也是)/ 会话店不存元组(`DB_BACKEND=memory`;嵌入方自建、不声明委派分区的店),读作「没存」、回落 fleet 行 / 名册上的 id。子代会话的属主是引擎判的委派树属主(**7.144.0+**:没人派生的 run 读自己那一行的属主、按化身绑一次,原样带给每一层子代;三只持久会话店〔MySQL 协议 / PostgreSQL / local〕建行那一笔原样记引擎交来的属主,`null` = 根匿名;停驻挂点只比对宿主行属主与引擎交来的属主,不认领、不推断。**7.133.0–7.143.x** 是本服务在建行那一笔按根 / 父推断:根会话的行在 ⇒ 它的属主,根行已不在 ⇒ 它记录的父会话的,都不在 ⇒ 哨兵 `_orphan:<id>`;7.132.x 及更早的 SQL 后端上子代会话无主、谁都读得到)⇒ 用发起子代的那位主体读;别的主体与不存在的 id 同一个 `404 not_found.session`。属主为空的委派子代行(根会话本就无主,或升级前写下、还没回填)只有 fleet 调用方读得到(出示 service 凭据、不带主体头;dev-open 部署不钉人),任何具名主体、没有 service 凭据的无身份调用方同样 404(存量无主会话互认臂 —— 单用户形下 `default` 主对无主的普通会话放行,fork / 删除 / `/sync/*`(同步族自 7.134.0 起)同走这一只 —— 对这些 placed 行同样不放行,审批豁免、notify 同样不放行;fork / 删除 / `/sync/*` 的 fleet 调用方与此处同一个判据:出示 service 凭据、不带主体头,带主体头的 service 凭据请求是那位主体,7.134.0 起);SQL 后端两店每次起服由一次性迁移 M23 在它的窗内回填升级前写下的无主 placed 行(按 7.133.0 起的读法:根的属主,根行不在取记录的父的,都不在写 `_orphan:<id>`;起服读数行 `placedSessionOwnersRemaining` 非零 = 还有没定下的,再起直到为 0),local 后端没有回填臂(**7.145.0+** local 后端建子代行先记属主、再建转录存档,进程崩在两笔之间留下的是一只没有转录的属主格 —— 不是子代行,不再新生无主子代行;更早版本崩在两笔之间留下的无主子代行照旧无主、不回填,出路是取消 / 删掉那只后台子代)。属主 `_orphan:<id>` 由 7.133.0–7.143.x 的建行推断与 M23 回填写下(7.144.0 起本服务不再推哨兵,建行原样记引擎交来的属主 —— 已有的哨兵属主随引擎的树属主原样带到它下面新建的子代行上):任何主体都不匹配,只有 fleet-wide / operator 读得到,前缀永久保留。停驻时宿主行属主与引擎交来的不等 ⇒ 停驻否决(那张待批卡作废、那一只子代以失败收尾) |
2004
- | `GET /v1/approvals?owner=user:42` | 高危写审批(durable,需 `DURABLE_APPROVAL=true`):**operator** 看待办队列(可按 owner 过滤);非 operator 只看自己的。队列里某一行的存储体读不出来、重试不会好时,答 `500` + `errorCode:"approvals.read_corrupt"`(**7.134.0+**;与订阅流同码、同一只判官;运维处置那一行后再读,别原样重试);此刻读不出队列(连接 / 超时 / 店抖动)⇒ `503` + `errorCode:"approvals.read_unavailable"`(**7.135.0+**,此前 500 `internal.error`;稍后重发,这一答不表示队列为空)。`GET /v1/assistant/inbox` / `GET /v1/assistant/tasks` 读同一个队列,两种读失败同码同状态(**7.135.0+**)。一张停驻行的存储体读不出摘要(手改成 `{}`、整段缺 `state` 等)只坏它自己那一行(**7.145.0+**,MySQL 协议 / PostgreSQL 后端):收件箱与运维队列照列它、带引擎判官的 `rowVerdict`(只缺 `pendingAction` 的行判官答 `ok`,引擎侧已知缺口),任务视图照列那条 run,三个面都不再整张答 500 / 503;别的卡的决断照常,这张卡自己的决断答 409 带码拒;`DB_BACKEND=local` 上列表面本版仍整张 503(决断已逐行)。停驻的**技能卡**(对引擎 `Skill` 工具的调用)的行带 `boundGrantDigest` / `boundGrants`(批准它还授予的东西:技能名 + 两只工具清单;清单只在哈希等于摘要时才有,是部署文本,渲染要按上下文转义),分类器没给判词的卡带 `classifierNoRuling`(`error` / `timeout` / `parse_error`)—— **7.136.0+**,与 `GET /v1/assistant/inbox` 行逐字同值(此前只有收件箱行与活卡帧带);普通卡三位都缺席。`GET /v1/assistant/inbox` 行的 `objective` 是门所属 run 的**目标预览**(写入时已脱敏、截到约 120 字符,与 `GET /v1/assistant/tasks` 读同一列;委派子代的门读派它的那条 run;没有 run 账本 ⇒ `null`;**7.136.0+**,此前原样回显存着的任务目标,运维 `?owner=` 跨看也是原文)。停驻行带 `agent`(**7.145.0+**):行的会话在委派树里的位置 `{ id?, parentSessionId?, rootSessionId? }`,与 `GET /v1/sessions/<sessionId>` 体顶层的 `agent` 同一只读口、逐值同答 —— 后台委派子代停驻的行(`taskId` 为 `null`、`sessionId` 是子代自己的会话)凭它连回发起委派的会话(`rootSessionId` / `parentSessionId` = 本端会话 ⇒ 本端的卡);缺席 = 这台 server 没有说(宿主会话自己的停驻、同步委派的子代、`subagent_type: "fork"` 的后台子代、不存委派元组的店、那一读没读成 —— 只这一行缺键、列表照出),按原读法判,不读作「不是子代」 |
2165
+ | `GET /v1/approvals?owner=user:42` | 高危写审批(durable,需 `DURABLE_APPROVAL=true`):**operator** 看待办队列(可按 owner 过滤);非 operator 只看自己的。队列里某一行的存储体读不出来、重试不会好时,答 `500` + `errorCode:"approvals.read_corrupt"`(**7.134.0+**;与订阅流同码、同一只判官;运维处置那一行后再读,别原样重试);此刻读不出队列(连接 / 超时 / 店抖动)⇒ `503` + `errorCode:"approvals.read_unavailable"`(**7.135.0+**,此前 500 `internal.error`;稍后重发,这一答不表示队列为空)。`GET /v1/assistant/inbox` / `GET /v1/assistant/tasks` 读同一个队列,两种读失败同码同状态(**7.135.0+**)。一张停驻行的存储体读不出摘要(手改成 `{}`、整段缺 `state` 等)只坏它自己那一行(**7.145.0+**,MySQL 协议 / PostgreSQL 后端):收件箱与运维队列照列它、带引擎判官的 `rowVerdict`(只缺 `pendingAction` 的行判官答 `ok`,引擎侧已知缺口),任务视图照列那条 run,三个面都不再整张答 500 / 503;别的卡的决断照常,这张卡自己的决断答 409 带码拒,`error` 以行上 `rowVerdict.sentence` 开头(**7.146.0+**;此前存储体 `{}` 的卡被指向 plan_review 的口、存储体读不成检查点的卡只有店的诊断);`DB_BACKEND=local` 上列表面本版仍整张 503(决断已逐行)。停驻的**技能卡**(对引擎 `Skill` 工具的调用)的行带 `boundGrantDigest` / `boundGrants`(批准它还授予的东西:技能名 + 两只工具清单;清单只在哈希等于摘要时才有,是部署文本,渲染要按上下文转义),分类器没给判词的卡带 `classifierNoRuling`(`error` / `timeout` / `parse_error`)—— **7.136.0+**,与 `GET /v1/assistant/inbox` 行逐字同值(此前只有收件箱行与活卡帧带);普通卡三位都缺席。`GET /v1/assistant/inbox` 行的 `objective` 是门所属 run 的**目标预览**(写入时已脱敏、截到约 120 字符,与 `GET /v1/assistant/tasks` 读同一列;委派子代的门读派它的那条 run;没有 run 账本 ⇒ `null`;**7.136.0+**,此前原样回显存着的任务目标,运维 `?owner=` 跨看也是原文)。停驻行带 `agent`(**7.145.0+**):行的会话在委派树里的位置 `{ id?, parentSessionId?, rootSessionId? }`,与 `GET /v1/sessions/<sessionId>` 体顶层的 `agent` 同一只读口、逐值同答 —— 后台委派子代停驻的行(`taskId` 为 `null`、`sessionId` 是子代自己的会话)凭它连回发起委派的会话(`rootSessionId` / `parentSessionId` = 本端会话 ⇒ 本端的卡);缺席 = 这台 server 没有说(宿主会话自己的停驻、同步委派的子代、`subagent_type: "fork"` 的后台子代、不存委派元组的店、那一读没读成 —— 只这一行缺键、列表照出),按原读法判,不读作「不是子代」 |
2005
2166
  | `GET /v1/approvals/stream` | 同一队列的 SSE 推送(范围同上):首拍快照 → `pending` / `resolved` 增量(开了工具审批时另有 `live_pending` / `live_resolved`),空闲时心跳,15 分钟上限(`STREAM_MAX_DURATION` 帧后重连)。**心跳只表示流活着,不表示这一拍读过队列**:队列读不出时每次读失败发一帧 `error`(`approvals.read_unavailable`,流留着、下一拍再读,恢复后同一条流继续);某一行的存储体读不出来、重试不会好时发 `approvals.read_corrupt` 并收流(运维处置那一行后再重连)。15 分钟上限对读失败的拍同样算数。`pending` 帧带的行与 `GET /v1/approvals` 同一只投影(含上面的技能卡三位,**7.136.0+**;含 `agent`,新订阅首拍补给的帧与之后推出的帧同值,**7.145.0+**) |
2006
2167
  | `POST /v1/approvals/<sessionId>/decide` `{"decision":"approve"|"deny","reason":"…"}` | 批/否并恢复挂起任务(CAS,重复决议 409);**仅 operator**(非 operator → 403)。5.0.0 起旧轮询腿 `POST /v1/approvals/<id>` 已退役。停驻的后台子代**复活不了**(行上没有根会话锚;未起名的后台子代不在此列 —— 引擎 7.57.0 起每只子代都有名字,升级前的无名行读作派生名,照常复活)⇒ `deny` 作废这张卡:`200 {taskId, status:"failed", decision:"deny"}`,卡离开待决队列、子代终局 `failed`;`approve` 答 `409 decide.row_unrevivable`、拒句指路 deny(**7.136.0+**;此前两个决定都 409,卡处理不掉)。有根会话锚的停驻子代卡上,`decide` 与删根会话 / 卡作废并发:写口放行之后卡已被作废 ⇒ `422 parked_resume.startup_failed`(卡已作废、什么都没复活;与 `409 decide.gate_moved` 并列为并发终局码,调用方须同时处理 409 / 422) |
2007
2168
  | `GET /v1/tool-approvals/<approvalId>` | 按问读口(**7.133.0+**;与 `POST …/respond` 同门同身份,不是你的 ask 与查无同形 404;**7.136.0+** 两扇决议口 —— `POST …/respond` 与耐久决断口 `POST /v1/tasks/<taskId>/asks/<askId>/decision` —— 与本口同一只属主判据:有行的问读行上当下的属主,收编之后新属主三口都认、前属主三口都 404;本口在店读不出时只对本副本自己铸的活登记回落登记(登记属主认你 ⇒ `pending`),起服接管的登记不回落、答 404 —— 与同一刻的应答口同答,此前本副本上的活卡读的是铸卡那一刻记下的属主):一只审批问此刻在哪一态,`state` ∈ `pending` / `decided` / `parked` / `void`(闭集)。停驻卡被答过 ⇒ `decided`、停驻 run 被取消 ⇒ `void`(**7.134.0+**;7.133.x 恒答 `parked`);停驻行的门已结算却读不出可用的决议(含坏行:状态词 / 种类 / 当事方不在闭集、结算记录过不了引擎的不变量筛、缺必填成员、时刻 / 方向读不出)⇒ 404 `tool_approval.not_pending`,运维日志一行 error(`defect` 首条缺陷句 · `defectCount`)。停驻行的 `decided` 可回到 `parked`:续跑腿在被批准的动作开跑前失败时,引擎重开同一张门 —— 读口答 `parked` + `decidePath` 并另带 `reopened`(**7.135.0+**):`reason` 是引擎的重开因由词(`env_failed` 按原决议经 `decidePath` 再交一次 —— 本副本还留着首次受理回执时(缺省 1 小时内)`…/respond` 回放那张回执、不驱续跑;`tool_unavailable` / `constraint_rejected` 重新决;`config_refused` 改好配置再交或重新决),`decision` / `decidedAtMs` / `decisionNote?` 是重开之前的那次决议(读不出 ⇒ 只带 `reason`);再决之后回到 `decided`。续跑被取消 ⇒ 重开的门被收掉,读作 `void`。全文见契约 §4a-bis-read |
2008
2169
  | `GET /v1/agents/roster` | 后台 agent **名册**(7.29 起,需 durable background-agent store,否则 501 `capability.background_agent_store_required`):调用方自己 scope 的 `a…` 行,新→旧。行只带 `handle`/`name`/`agentType`/`status`/`spawnedAt`/`updatedAt`/`settledAt`(content-free;子代产物正文走 `GET /v1/runs/<id>/subagents/<handle>/output`)。查询:`?limit=`(1..100,默认 20)、`?before=`(上一页的 `nextBefore` 游标)、`?status=`(running\|parked\|completed\|failed\|killed)、`?session=`(按会话树窄化)、`?scope=`(**仅** fleet 调用方:不带主体头的 service 凭据 / 只读 `TRACE_TOKEN`(只在没配 service 凭据的部署上够得着这一口)/ dev-open;带主体头的调用方 —— 含带 service 凭据的 —— 锁定自己)。回体 `{agents,total,nextBefore?,truncated?}`;`truncated:true` = 租户行数超过名册窗(500),`total` 此时是窗内数。非属主的行**不出现也不计数**(无存在性 oracle) |
2009
2170
  | `GET /v1/capabilities/scenarios/<name>` | 单个场景的只读详情:工具面、提示词概览、**本部署现在跑不跑得动**、附件供给、**技能清单**(见下) |
2010
- | `GET /v1/capabilities/mcp[?scenario=<场景名>]` | **无需先跑 run 的逐台 MCP 状态**(现连、列工具、关掉;按调用方限速 + 30 秒复用)。答的是**某个场景**会挂哪些中心声明的 MCP 服务器:不带参数 = 这位主体 run 时的场景(**7.131.0+**:中心按主体指派的场景胜出,否则部署缺省场景;此前恒读部署缺省场景;指派悬空 ⇒ `503 state.scenario_assignment_unknown`,与提交任务时同一句;`GET /v1/sessions/:id/mcp` 与 `…/a2a` 两张会话面板同读这位主体 run 时的场景:a2a 面板悬空时同样整面 503,而 mcp 面板另有「这个会话上一次真挂上了什么」那一半(`lastLegMcp`),悬空时答 `200`、`lastLegMcp` 照答、`servers` 缺席、拒因在 `scenarioRefusal: { errorCode, error, detail }`(与状态面整面 503 的体是同一份成员,`detail` 是门拒的机读事实 `{ scenario, source }`;`servers` 缺席 = 场景这一半没答,`servers: []` = 该场景没配 MCP));`?scenario=<名>`(**7.130.0+**)= 点名场景,可见集与同场景任务 run 时是同一只解析、过同一只场景门 —— 不在册 `400 scenario_unknown`(附在册词表;空串 `?scenario=` 同此,与提交 `scenario: ""` 同答)、这位 principal 的场景白名单集外 `400 scenario_not_allowed`(附 allowlist),两者的体与提交任务时的拒逐字同形;重复给出参数 `400 request.field_invalid`(提交腿没有这一形)。场景门接缝 `scenarioGate` 是 `createHttpServer` 的必填件:自建 `ServiceDeps` 的嵌入方用包根导出的 `createScenarioGate` 铸它(`"peek"` 腿给读面),没递就是 tsc 红。老 server 忽略这个参数、回缺省场景那一份,按 `GET /v1/capabilities` 的 `version` 判。`REQUIRE_PRINCIPAL` 下无身份 `401`(先于场景门)。每一行(两条口同形)在 §G.7 的行形之外多两个可选键(**7.133.0+**):`toolNames`(这台会挂上的工具名,引擎命名空间形 `mcp__<server>__<tool>`,读数序)+ `toolNamesTruncated: true`(名单超过 200 只时只截表,`toolCount` 仍是真数;读数用 `toolCount`,别用 `toolNames.length`);该台读数里没有名单 ⇒ 两键缺席(不编 `[]`),没连上的台是在场的 `[]`;老 server 没有这两键,按缺席读。中心声明里有两台归一后挂在同一个工具命名空间前缀下(`my.srv` / `my_srv` 同挂 `mcp__my_srv__`)⇒ **7.143.0+** `409 config.mcp_server_name_collision` + 引擎原句(点名两个原拼写;此前无码 `500`),与同一份声明集在 `POST /v1/tasks` 上的答复同码、同状态、同句 —— 出路 = 改配置里的名字。自带 `.mcp.json` 的探测口是 `POST /v1/capabilities/mcp/probe` |
2171
+ | `GET /v1/capabilities/mcp[?scenario=<场景名>]` | **无需先跑 run 的逐台 MCP 状态**(现连、列工具、关掉;按调用方限速 + 30 秒复用)。答的是**某个场景**会挂哪些中心声明的 MCP 服务器:不带参数 = 这位主体 run 时的场景(**7.131.0+**:中心按主体指派的场景胜出,否则部署缺省场景;此前恒读部署缺省场景;指派悬空 ⇒ `503 state.scenario_assignment_unknown`,与提交任务时同一句;**7.147.0+** 部署接了中心而中心答不出这位主体的裁定(不可达 / 超时 / 答复读不出 / 指派两键形不对)⇒ 点名与不点名同答 `503 state.scenario_assignment_unavailable`(体无 `detail`、带 `permanence: "transient"`),与提交任务时同一句,不再按「无治理」读部署缺省场景;`GET /v1/sessions/:id/mcp` 与 `…/a2a` 两张会话面板同读这位主体 run 时的场景:a2a 面板悬空时同样整面 503,而 mcp 面板另有「这个会话上一次真挂上了什么」那一半(`lastLegMcp`),悬空时答 `200`、`lastLegMcp` 照答、`servers` 缺席、拒因在 `scenarioRefusal: { errorCode, error, detail }`(与状态面整面 503 的体是同一份成员,`detail` 是门拒的机读事实 `{ scenario, source }`;`servers` 缺席 = 场景这一半没答,`servers: []` = 该场景没配 MCP));`?scenario=<名>`(**7.130.0+**)= 点名场景,可见集与同场景任务 run 时是同一只解析、过同一只场景门 —— 不在册 `400 scenario_unknown`(附在册词表;空串 `?scenario=` 同此,与提交 `scenario: ""` 同答)、这位 principal 的场景白名单集外 `400 scenario_not_allowed`(附 allowlist),两者的体与提交任务时的拒逐字同形;重复给出参数 `400 request.field_invalid`(提交腿没有这一形)。场景门接缝 `scenarioGate` 是 `createHttpServer` 的必填件:自建 `ServiceDeps` 的嵌入方用包根导出的 `createScenarioGate` 铸它(`"peek"` 腿给读面),没递就是 tsc 红。老 server 忽略这个参数、回缺省场景那一份,按 `GET /v1/capabilities` 的 `version` 判。`REQUIRE_PRINCIPAL` 下无身份 `401`(先于场景门)。每一行(两条口同形)在 §G.7 的行形之外多两个可选键(**7.133.0+**):`toolNames`(这台会挂上的工具名,引擎命名空间形 `mcp__<server>__<tool>`,读数序)+ `toolNamesTruncated: true`(名单超过 200 只时只截表,`toolCount` 仍是真数;读数用 `toolCount`,别用 `toolNames.length`);该台读数里没有名单 ⇒ 两键缺席(不编 `[]`),没连上的台是在场的 `[]`;老 server 没有这两键,按缺席读。中心声明里有两台归一后挂在同一个工具命名空间前缀下(`my.srv` / `my_srv` 同挂 `mcp__my_srv__`)⇒ **7.143.0+** `409 config.mcp_server_name_collision` + 引擎原句(点名两个原拼写;此前无码 `500`),与同一份声明集在 `POST /v1/tasks` 上的答复同码、同状态、同句 —— 出路 = 改配置里的名字。自带 `.mcp.json` 的探测口是 `POST /v1/capabilities/mcp/probe` |
2011
2172
  | `POST /v1/admin/drain` `{"actionId":"…","worker":"…","deadlineAt":…}` | **排空口**(7.121.0 起体 = settings-schema `DrainRequest`;operator-only —— principal 须在 `OPERATOR_PRINCIPALS` 内,或单用户部署声明了 `OPERATOR_SINGLE_USER=true`;两条出路都没有〔名单为空且没声明〕⇒ 501)。体 `{ actionId, worker, deadlineAt, scope?, expectedGeneration?, instanceId?, term? }`:`actionId` 幂等键(人手形自取,如 `manual:<uuid>`)、`worker` 本引擎的 worker 名(`SEMA_WEB_ADMIN_WORKER`)、`deadlineAt` 到期自停的 epoch ms、`scope` 本引擎的租户 scope(`FLEET_SCOPE`;不带 = 缺省 scope `global`,与没设 `FLEET_SCOPE` 的引擎相符 —— 多租引擎要带它自己的)、`expectedGeneration` fleet 成员的代际(`FLEET_GENERATION`;非成员不带)、`instanceId` 本进程身份(`GET /health` 读;不带则不比)。身份逐字段比(`worker` / `expectedGeneration` 缺席对缺席),不符 ⇒ `409 conflict.fleet_identity`(什么都不变);首次 ⇒ `200 {actionId, since, deadlineAt, state:"draining"}` 并**进入排空**(拒新提交、本副本可抢占的在飞腿当拍让出、活的审批卡改路耐久停驻、在飞归零或到期限即停);再来 ⇒ `200 … state:"already_draining"` 回显第一次的 `since` / `deadlineAt`,不重置。体坏 ⇒ `400 request.field_invalid`;`deadlineAt` 离本引擎此刻超过 `FLEET_LEASE_OUTAGE_MAX_MS`(缺省 24 h,一台实例活着而不服务的最长时长;7.122.0 起)⇒ 同码 400(什么都不变;拒句点名字段与上界的值;只在这一发会进入排空时判 —— 已在排空或硬停已在进行时任何重发都回 `already_draining`),已过的期限照收(进入即到期);嵌入宿主(无收尾链)⇒ `501 capability.drain_state_required`。没有 worker 身份的引擎(没配 `SEMA_WEB_ADMIN_WORKER`)恒 409 —— 用 SIGTERM。经本口进的排空之后,平台随后发的第一发 SIGTERM 被吸收、不缩短期限(第二发判据见「排空期的第二发信号」)。7.120.x 的 `{"reason":"…"}` 声明形与 `/health.drainReason` 已退役。 |
2012
2173
  | `GET /v1/config/catalog` | **配置目录自描述**(operator-only —— `explicitOperatorOk`:没有运维名册〔`OPERATOR_PRINCIPALS` 为空且没有单用户声明 `OPERATOR_SINGLE_USER=true`〕⇒ 501 `capability.operator_principals_required`,有名册而调用方不是运维 ⇒ 403)。逐 env 键一行:`{name, domain, type, semantics, writeLanes, effectiveLane, envHeld, envTiming, staticDefault?/derivedDefaultNote?, effectiveValue, valueClass, danger, center?{domain,path,precedence,timing,restartSlice?,managedNow,effectiveNow}}`(🔴 7.106.0 起 `center.domainExists` 删除:座位在加载期对契约 `DOMAIN_SCHEMAS` 核过域 + 路径,核不过即拒启 ⇒ 有 `center` 块 ⇔ 中心可写;readFace 四键 / `PERMISSIONS_DISABLE_AUTO_MODE` / `MODEL_DEGRADE_ON` / `_REACTIVE` 七行的座位随之删除);顶层带 `restartSlices`(重启片闭集)与 `centerDomains`(settings-schema 现行域表)。**两轴不混**:`writeLanes`=谁能写,`effectiveLane`=现在谁在生效(env-wins 族 env 占位 ⇒ center 腿 `effectiveNow:false`)。secret 类值**永不回传**(只回在场位;opaque 回在场位+长度;URL 剥 userinfo)。完备性由仓内 AST 双向对账门执法(分母=全 `src/` 生产 env 读取点)。旋钮 `CONFIG_CATALOG_ENABLED=false`(合规部署)⇒ 501 `feature.config_catalog_disabled`(路径恒挂载)+ 能力位 `configCatalog:false`。响应 `Cache-Control: no-store`。**7.57.0 起**:`enum` 行的 `effectiveValue` **恒落在该行自己的 `enumValues` 闭集内**(仓内机器门执法)——此前 `SESSION_BACKEND` 回的是**内部标签** `tidb`(自 5.0.0 起是**退役公名**,写回去 boot 拒),现归一成公名 `mysql`;`MODEL_DEFAULT_THINKING` 补上一直被解析器接受、却漏在目录闭集外的 `xhigh`,并改为回**实算值**(`off`/未设 ⇒ `null`)而不是 env 原文。 |
2013
2174
  | `POST /v1/admin/config/refresh` | **配置手动刷新**(operator-only,与上一行同门:没有运维名册〔`OPERATOR_PRINCIPALS` 为空且没有单用户声明 `OPERATOR_SINGLE_USER=true`〕⇒ 501;名单非空而调用方不在名单上 ⇒ 403)。改完中心配置 / `config.d` 之后不想等 60s 轮询就调它:触发**一次既有**的 refresh 拍(在飞则汇入那一拍,绝不并发双拍),返回时该拍已落地。200 体 `{triggered:true,targetVersion,appliedVersion}` —— 两个世代号与 `/health` 的 `configTargetVersion`/`configAppliedVersion` 同源。候选被判官整拒(坏域 / 坏 prompts / 带事实的退役域 —— 7.126.0 起 / 本机 `config.d` 里读不出的文档,门控域在内)是这一拍的结论,不是失败:照样 200,`targetVersion ≠ appliedVersion`,拒因在 `config_candidate_rejected` 日志行;拍真的失败(配置源不可达 / 读不了 / 应用抛错)⇒ `502 internal.config_refresh_failed`。本部署没有配置管道(纯 env worker:既没配 `CONFIG_CENTER`、也不是 `CONFIG_PROVIDER=local`)⇒ 409 `config.refresh_unavailable` 指路,不会回一个「触发了但什么都没发生」的 200。 |
2175
+ | `POST /v1/adoption/<adoptionId>/sessions` `{"sessions":[{"clientSessionId":"…","entries":[…]}]}` | **收编批量导入**(**7.146.0+**;能力位 `adoptionImport`,按调用方):收编的**源主体本人**把本机会话导进那条收编的目的主体名下(operator 不能代,请求体不能点名目的地);导进来的是只读镜像,在云端接着跑先 fork。`adoptionId` 从 `GET /v1/capabilities` 的 `adoptionImport.adoptionId` 读(只对源主体本人在场,另带两只帽 `maxSessions` / `maxSessionBytes`)。云端会话 id 由 `(adoptionId, clientSessionId)` 确定性派生 ⇒ 重投安全。200 + 逐元素结局 `imported` / `already_imported` / `rejected{code: too_large \| invalid_envelope \| duplicate_client_session_id}`;信封恰好两键,多一个键(如 `cwd`)⇒ 这个元素 `invalid_envelope`。整请求拒:401 / 501 `capability.adoption_store_required` / 404 `not_found.adoption` / 403 `auth.adoption_source_only` / 409 `adoption.not_adopted` / 400(体形、元素数超帽)。帽 = `ADOPTION_IMPORT_MAX_SESSIONS`(缺省 100)/ `ADOPTION_IMPORT_MAX_SESSION_BYTES`(缺省 6 MiB)。契约 §26 |
2014
2176
  | `GET /health` | 健康(无需鉴权)。**7.67.0+** 恒带 `startedAt`(引擎进程起点 epoch ms,一次铸;与 `pid` 并列的换代锚——值变了就是换了一条命)。7.37.0+ 配置管道在场时带世代账键:`configTargetVersion`(本副本**最后见到**的配置版本,身份标)、`configAppliedVersion`(最后一次真落地的版本)、`configTargetOrdinal`/`configAppliedOrdinal`(7.38.0-rc.2 起,**收货序数**——`CONFIG_PROVIDER=local` 下 version 是内容哈希不承诺序,「改了没生效」看序数对不对齐)、`configApplyStaleMs`(target≠applied **持续**时长;收敛时键缺席)—— 编排器据此摘掉「配置持续落后」的副本 |
2015
2177
  | `GET /v1/sessions/:id/background` · `POST /v1/sessions/:id/background/stop` `{"includeRetained":true\|false}` | **会话后台任务的读口与停止口**(**7.102.0+**;能力位 `background.listFace` / `background.exitFaces`,老 server 404)。读口列**本会话**、**本副本**上的后台任务行(`{ rows: [{ id, description?, type, status, createdAt, sessionScoped, retained?, sessionId? }] }`,`retained:true` = 声明了保留的活服务,正常退出不会停它;`sessionId` = 后台 agent 行的子代会话 id,绑定之后才在);停止口「软 → 等一次 `HOST_BG_KILL_GRACE_MS` → 硬」收掉本会话的后台 shell(SIGTERM 处理器先有一次宽限跑完,之后组 SIGKILL,屏蔽 SIGTERM 的循环也活不过去;有后台行时应答慢一次宽限)并逐字返回受据 `{ receipts: [{ id, outcome }] }`:`killed` / `already_gone` / `retained_skipped`(`includeRetained:false` 留下的活服务)/ `no_pgid`(远端 / 沙箱车道没有本机进程组可发,已派普通 kill,「可能还在跑」;host 车道的 `already_gone` 只由观察得出 —— 引擎已收割组长或内核答组已空,7.103.0 起;darwin 上梯子那一枪先落地、引擎还没收割的那一拍如实答 `no_pgid`,判「收掉没有」读进程)。`includeRetained` 必填:`true` = 连活服务一起停(壳的「Exit and stop tasks」),`false` = 活服务照留(`sema kill`)。鉴权 = 会话属主或显式 operator(未知与他人同 404);停止口在无凭据写门内(没配 service 凭证且未开 `ALLOW_UNAUTHED_WRITES` ⇒ 503 `auth.service_token_required`)。`DELETE /v1/sessions/:id` 走同一只收敛(`includeRetained:false`);run 取消(Esc)**不**收后台 shell。契约 §16 |
2178
+ | `POST /v1/sessions/:id/rewind-files` `{"to":"<提问的 taskId>","dryRun":true\|false}` | **run 外只恢复代码**(**7.147.0+**;能力位 `sessionRewindFiles` / `sessionRewindFilesPreview`,老 server 404):不跑模型、不分叉对话,把这只会话被代理改过的文件收敛到那条提问开始时的样子(`to` 与提交体 `rewindFilesTo` 同一个手柄,会话视图消息行的 `messages[].taskId`)。会话属主或运维可调;只在 `DB_BACKEND=local` 且工作区活过一次执行的部署上开(mysql / pg 多副本共用会话店、`local-docker` / `e2b` / k8s / 多租 host ⇒ 能力位假、口 501);同会话有 run 在跑或停在审批上 ⇒ `409 conflict.session_active_run`(带 `activeTaskId`,等它结束或取消再发);引擎进程内仍有在途工作 ⇒ `409 rewind.session_busy`;那条提问没有可用的文件边界 ⇒ `422 rewind_snapshot.unresolvable`;未知手柄 ⇒ `404 rewind_files_to.unknown_event`;host 车道上 server 重启之后、这只会话下一条带 `cwd` 的提交之前,或登记的目录已被移走 ⇒ `409 conflict.session_workspace_unknown`(本进程不知道它的工作目录,先发一条带 `cwd` 的提交再回退);只读镜像会话 ⇒ `409 conflict.session_mirror`。取执行环境时不物化会话附件(预览与真回退都不改写 `attachments/`)。`dryRun:true` ⇒ `{ dryRun:true, preview }`:`preview: null` = 这台给不出统计(`sessionRewindFilesPreview` 为假,今天 SQL 后端如此),**不是**「什么都不会变」;`dryRun` 缺席 / `false` ⇒ `{ dryRun:false, restore }` 台账,`outcome` 是 `complete` 或 `partial`(`partial` 带每一个被拒 / 失败的文件,原样再发会继续收敛)。没有 `acceptPartialRestore`(带了 ⇒ 400 `request.body_shape`)。不计费;无凭证部署上与提交面同一道门 |
2016
2179
  | `GET /readyz` | **就绪探针**(**7.102.0+**,无需鉴权;7.121.0 起是唯一的探针路径)。就绪 ⇒ `200 {"ready":true}`;未就绪 ⇒ `503 {"ready":false,"reasons":[…]}` + `Retry-After: 5`,`reasons` ⊆ {`draining`, `model_roster_pending`}(按优先序)。与 `/health` 的 `ready` / `readyReason` / `reasons` 同一只判;库连不上**不算**未就绪(`storeLive` 只在 `/health` 披露)。给只认状态码的**摘流**探针用(k8s `readinessProbe.httpGet`、LB 健康检查、镜像 `HEALTHCHECK`);存活探针用 TCP 探端口,不打 HTTP,见 §0「探针」 |
2017
2180
  | `GET /metrics` | Prometheus 指标(有 token 时需带) |
2018
2181
 
@@ -2036,7 +2199,11 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2036
2199
  > **名单为空 = 没有人是 operator**(每个调用方只看自己的待批 —— 不带主体的调用方看到的是匿名卡〔不带主体提交的任务停下的那几张,即决断口对它开放的那几张〕,`?owner=` 不生效;7.133.0 之前审批四个读面在名单为空时把每个调用方当 operator,那条读法已删)。
2037
2200
  > **单用户部署当自己的 operator**:`OPERATOR_SINGLE_USER=true` = 「这台部署的每个调用方都是 operator」,对全部运维面生效;只在 `REQUIRE_PRINCIPAL` 未开且 `OPERATOR_PRINCIPALS` 为空时成立(与任一同在 ⇒ 拒启);
2038
2201
  > 没配服务凭据的部署上能连上端口的都是 operator;不带主体的 operator 没有身份可记,记「谁改的」的写面(`PUT /v1/admin/memory-optout[/:principal]`)要带主体(否则 401)。
2039
- > 单用户 + 名单空 + 没声明 + 有审批底座 ⇒ 起服 warn `operator_undeclared_single_user`(不拒启)。
2202
+ > 部署形不是输入:单用户部署上 `GET /v1/diagnostics/wiring` 与 `GET /v1/outcomes` 也只认运维,具名名单在单用户部署上同样生效。
2203
+ > 🔴 **单用户部署必须声明谁是运维**:`REQUIRE_PRINCIPAL` 未开、`OPERATOR_PRINCIPALS` 为空、`OPERATOR_SINGLE_USER` 不是 `true` ⇒ 装配运维面时拒(服务进程起服拒;直接调 `createHttpServer` 的嵌入方同拒;不变量 `operator-declaration-required`),
2204
+ > stderr 一行拒句点名两条出路 —— `OPERATOR_SINGLE_USER=true`(每个调用方都是运维)或 `OPERATOR_PRINCIPALS=<主体,…>`(只有点名的主体);不止一个人调用的 worker 是多租,设 `REQUIRE_PRINCIPAL=true`。
2205
+ > 没有旋钮、没有宽限、没有缺省;`OPERATOR_SINGLE_USER=false` 与没写同判。`sema-up --users=single` 与 chart(`server.requirePrincipal` 未开)渲 `OPERATOR_SINGLE_USER=true`。
2206
+ > 不挂 HTTP 面的 `run-local` 不判这一条;运维面 501 `capability.operator_principals_required` 从此只出现在「多租 + 空名单」上。
2040
2207
  > ⚠️ **多租户形拒启**:`DURABLE_APPROVAL=true` + `REQUIRE_PRINCIPAL=true` 而 `OPERATOR_PRINCIPALS` 空 ⇒ 进程启动失败并点名修法(空名单下没人能当这台多租 worker 审批队列的 operator)。设名单,或确属单租户则不设 `REQUIRE_PRINCIPAL`。
2041
2208
  > 🔒 **停驻任务续跑上下文里的凭据是密封的(7.134.0 起)**:任务停驻时存下的提交体里,请求自带的凭据(`mcpServers[].transport.headers` / `.env` 的值、`a2aPeers[].headers` 的值、`settings.env` 的值、
2042
2209
  > `settings.webSearch.apiKey`、http hook 条目的静态头 `settings.hooks.<事件>[].hooks[].headers` 的值、command hook 条目的插件选项 `settings.hooks.<事件>[].hooks[].plugin.options` 的值〔7.135.0 起〕、子进程的命令行 —— stdio 传输的 `mcpServers[].transport.command` / `.args` 与 command hook 条目的 `settings.hooks.<事件>[].hooks[].command` / `.args`〔7.136.0 起;`args` 整只数组封成一个值〕)只以封签落盘(对着这一域自己的保管钥匙 `<数据根>/sealed-key/checkpoint-ctx-keys/` 最新那把公钥封、
@@ -2048,10 +2215,10 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2048
2215
  >
2049
2216
  > **部署治理三声明(7.21.0 起)**——三根都是**配置期**旋钮,「谁能改它们」≡「谁能改
2050
2217
  > 部署配置」,不引入新鉴权面;三根**都不设 = 现行为逐字不变**,坏值一律**拒启**(安全轴上的旋钮不静默失效)。
2051
- > - `COMPLIANCE_PROFILE=hipaa|zdr`(+ 可选 `COMPLIANCE_ADDITIONAL_DENIES=mcp_servers,workflows,web_fetch,org_memory_mount,a2a_peers`,
2218
+ > - `COMPLIANCE_PROFILE=hipaa|zdr`(+ 可选 `COMPLIANCE_ADDITIONAL_DENIES=mcp_servers,workflows,web_fetch,org_memory_mount,a2a_peers,memory_consolidation`,
2052
2219
  > **只能在档位内建 floor 上加禁,不能减**):合规档位否决。档位在场时被禁能力不挂载,任务**显式请求**被禁能力
2053
- > ⇒ 响亮拒(prepare 期终态 `config.compliance_denied`)。hipaa 内建禁 `web_fetch`+`org_memory_mount`,zdr 内建禁
2054
- > `org_memory_mount`。⚠️ 档位是**部署级**(对本部署所有 principal 同一份)——per-principal 档位需要中心侧下发面,
2220
+ > ⇒ 响亮拒(prepare 期终态 `config.compliance_denied`)。hipaa 内建禁 `web_fetch`+`org_memory_mount`+`memory_consolidation`,zdr 内建禁
2221
+ > `org_memory_mount`+`memory_consolidation`(`memory_consolidation` 自 7.146.0 / 引擎 7.63.0 起:否决的是记忆整理,不是任务面 —— 判在整理阀门上,见 `MEMORY_CONSOLIDATION_DRIVER`)。⚠️ 档位是**部署级**(对本部署所有 principal 同一份)——per-principal 档位需要中心侧下发面,
2055
2222
  > 那一面尚未建;拼错档位名或能力名一律拒启(拼错=「标着 hipaa 却根本没在执行」)。
2056
2223
  > 🔴 **装配相容性拒启**(不是配置洁癖,是保护):被禁能力若在本部署的装配里**恒在场**,引擎会**整拒**
2057
2224
  > 每一条腿(不是静默不挂载),所以这两形在启动期就拒:①禁 `mcp_servers` 而部署自己配了 MCP 服务器;
@@ -2064,7 +2231,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2064
2231
  > 治理拍),锁上会让**每一条**任务在引擎门口被拒,而那把锁本就无物可守(策略已是部署所有、请求放松不了);
2065
2232
  > ②`mcp` + 部署自己配了 MCP 服务器——部署的服务器走同一个字段,锁上同样每条腿必拒。两者都在**启动期**点名拒,
2066
2233
  > 不留给运维在每条任务上猜。
2067
- > 同一条判据也管 A2A:`LOCKED_CONFIG_KEYS` 含 `a2a`,或合规档位禁 `a2a_peers`,而部署自己配了 A2A 对端(中心 `a2a` 域)⇒ 同样在启动期点名拒。
2234
+ > 同一条判据也管 A2A:`LOCKED_CONFIG_KEYS` 含 `a2a`,或合规档位禁 `a2a_peers`,而部署自己配了 A2A 对端(中心 `a2a` 域,或本地 `config.d/a2a.json`,见 9.5i-bis)⇒ 同样在启动期点名拒。
2068
2235
  > - `RETENTION_MAX_AGE_DAYS=<非负数>`:托管留存**视界**(天;`0` 合法=立即可删)。坏值(非数/负数/设了但为空)
2069
2236
  > 一律拒启——删数据的旋钮不许 NaN 流通。**它只是"保留多久"这句声明**;真正按它删数据的是下面两根。
2070
2237
  > - `RETENTION_SWEEP_INTERVAL_SEC=<非负整数>`(默认 `0` = **关**;上限 `2147483` 秒 ≈ 24.8 天):
@@ -2089,7 +2256,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2089
2256
  > - 能力位 `capabilities.retention` = `{mode, maxAgeDays}`(lane 开着)或 `null`(关着)。
2090
2257
  > - **子代转录(placed 分区)的留存天数没有独立旋钮座**(设计判定 2026-08-30):placed 转录的
2091
2258
  > 删除权**独家**归联合 reap(`reapDurableAgents`:行赢删才 release 转录;上面这条 lane 的候选谓词显式排除
2092
- > placed,见 `plugins/retention-store-sql.ts` 第五腿)——生效视界 = `BG_AGENT_RETENTION_MS`(缺省 7 天,
2259
+ > placed,见 `stores-sql/retention-store-sql.ts` 第五腿)——生效视界 = `BG_AGENT_RETENTION_MS`(缺省 7 天,
2093
2260
  > 毫秒形,同时是 agent 行的留存旋钮;分区孤儿腿的 `olderThanMs` 同源)。core 的
2094
2261
  > `SUBAGENT_TRANSCRIPT_RETENTION_DAYS_DEFAULT`(30 天)是共享词表常量,**本服务未按它跑**;天数形专用
2095
2262
  > 旋钮要等 core 在 reap policy 开出转录独立年龄座后再铸(届时名 `SUBAGENT_TRANSCRIPT_RETENTION_DAYS`)。
@@ -2105,7 +2272,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2105
2272
  > - **未设 + 没有便宜模型**(`MODEL_CHEAP_ID` 未设时 summarize 落主模型)⇒ **关** + 起服一行 warn
2106
2273
  > `memory_consolidation_default_off_no_cheap_model`(点名 `MODEL_CHEAP_ID`;显式写 `off` 即静音)。理由:一轮整库
2107
2274
  > 折叠读 ~1e5 prompt tokens,落主模型就是按最贵的价记账。
2108
- > - **未设 + 结构上跑不起来**(下面 ①②③ 三形:多租户 / `MEMORY_ENGINE=off` / pg·mysql 记忆后端 / `MEMORY_PROVENANCE=off`)
2275
+ > - **未设 + 结构上跑不起来**(下面 ①②③⑤ 四形:多租户 / `MEMORY_ENGINE=off` / pg·mysql 记忆后端 / `MEMORY_PROVENANCE=off` / 合规档否决整理)
2109
2276
  > ⇒ **恒关、零提示**(配不配便宜模型它都开不了;缺省不造新拒启)。
2110
2277
  > - **显式 `on` / `off` 恒赢**。显式 `on` 而座落主模型 ⇒ 照开,起服一行 warn `memory_consolidation_on_main_model`
2111
2278
  > 说清按主模型计费(部署方显式承担)。
@@ -2118,13 +2285,15 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2118
2285
  > 便宜模型在场 ⇒ 派生开,不再误拒)。
2119
2286
  > ⚠️ **闭集两词,别的一律拒启**——本旋钮是新的,不继承 `MEMORY_ENGINE` 的 `true/1/yes` 放宽:
2120
2287
  > 一个被静默读成 `off` 的开词买到的是「运维以为在折叠、其实一次都没跑过」,而这条面的失败恰恰看不出来。
2121
- > 🔴 **`on` 而这台机器结构上跑不了 ⇒ 拒启**(四形各有点名文案):①没接记忆引擎(`MEMORY_ENGINE=off`,
2122
- > 或多租户形——记忆引擎只在单用户部署上装配);②记忆后端不自带引擎控制面归属(`MEMORY_ENGINE_BACKEND=file`
2123
- > 有,`pg`/`tidb` 没有 —— core 会把控制面落到副本本地盘,而一条 run 的续跑账死在 pod 重建上等于下次整库
2124
- > 重新蒸馏、真金白银);③`MEMORY_PROVENANCE=off`(折叠法必须铸得出 origin 标记,否则产物会不带标记提交);
2288
+ > 🔴 **`on` 而这台机器结构上跑不了 ⇒ 拒启**(五形各有点名文案):①没接记忆引擎(`MEMORY_ENGINE=off`,
2289
+ > 或多租户形——记忆引擎只在单用户部署上装配);②整理状态不随记忆后端走(引擎对这只后端答 `"replica-local"`:
2290
+ > `MEMORY_ENGINE_BACKEND=file` 答 `"backend"`,`pg` / `mysql`(MySQL 协议;TiDB 走这一词)答 `"replica-local"` —— 状态会落到副本本地盘,而一条 run 的续跑账死在
2291
+ > pod 重建上等于下次整库重新蒸馏、真金白银);③`MEMORY_PROVENANCE=off`(折叠法必须铸得出 origin 标记,否则产物会不带标记提交);
2125
2292
  > ④**模型座位解析不出来**——既没有显式 chat 席,也没有 `roles.consolidate` / `roles.summarize`(含档位表的
2126
2293
  > `flash` 绑定)。core **刻意不回落主模型**(整库蒸馏不该悄悄骑最贵的席位),本服务照抄那条判断,不在下游
2127
- > 补一个兜底席。
2294
+ > 补一个兜底席;⑤**合规档否决记忆整理**(7.146.0 起):`COMPLIANCE_PROFILE=hipaa|zdr`(内建否决含 `memory_consolidation`),或
2295
+ > `COMPLIANCE_ADDITIONAL_DENIES` 含该词 —— 拒句点名三只旋钮;未设 `MEMORY_CONSOLIDATION_DRIVER` ⇒ 缺省派生关,手动整理口答
2296
+ > `501 feature.memory_consolidation_disabled`(句子说阀门关,不点名合规档)。档位否决时阀门结构上开不起来,引擎那一只 `409 memory.consolidation_refused` 在本服务的装配下不可达。
2128
2297
  > - `MEMORY_AUTO_CONSOLIDATION=on|off`(**未设 = 读引擎缺省**,见下):记忆**自动整理**的部署席
2129
2298
  > (core 7.21.0 的 `RunnerDeps.autoRunOnRecommendation` 席)。武装 ⇒ **每个产生了整理建议的任务
2130
2299
  > 之后**,为该 scope 起**一次**整库整理跑 —— 🔴 **记忆内容自动外流到配置的整理模型**。
@@ -2193,8 +2362,8 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2193
2362
  > 🔴 因此:**没配 `SERVICE_AUTH_TOKEN` 且未开 `ALLOW_UNAUTHED_WRITES` 的 worker 上这一口恒 503**,
2194
2363
  > 而能力位仍报 true(本族共有缺口,附录 B.1 已如实登记)。启动日志为此打一条
2195
2364
  > `memory_consolidation_run_credential_gated`;状态读那一口不受影响(它不烧模型)。
2196
- > 🔴 另一条启动告警:没有运维名册(`OPERATOR_PRINCIPALS` 为空且没有单用户声明 `OPERATOR_SINGLE_USER=true`)时两口恒 501 `capability.operator_principals_required`(没有运维名册 = 没有任何人是 operator),
2197
- > 日志里是 `memory_consolidation_valve_unreachable`。
2365
+ > 谁是运维:阀门开得起来的只有单用户 + file 记忆后端的部署,而单用户部署起服就得声明运维(`OPERATOR_SINGLE_USER=true` 或具名 `OPERATOR_PRINCIPALS`,
2366
+ > 见本文 `OPERATOR_SINGLE_USER` 段);具名名单外的调用方答 403 `auth.operator_only`。
2198
2367
  > - `GET /v1/admin/memory/consolidation[?scope=]` —— 状态投影(阀门/座位/scope 表/上一轮的账)。
2199
2368
  > - 能力位 `capabilities.memoryConsolidationDriver`(阀门上了场 ∧ 有运维名册:名单非空或单用户声明)。
2200
2369
  > - 逐键语义与停因闭集见 `docs/ASSISTANT-WIRE-CONTRACT.md` §10。
@@ -2368,6 +2537,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2368
2537
 
2369
2538
  **A. OA 系统(服务到服务后端)**
2370
2539
  - OA 后端持有 `SERVICE_AUTH_TOKEN`,每个请求按当前终端用户设 `x-agent-principal: user:<id>`。带了主体头的请求只看得到、动得到那位用户的(7.134.0 起 fleet 面同读);后台自己的全舰队运维视图**不带主体头**(或用只读 `TRACE_TOKEN`)。
2540
+ 部署设了 `SERVICE_TOKEN_ACTS_FOR_DEPLOYMENT=false` 时不带主体头的 service token 不再是全舰队视图(按端点换凭据,见 §「不报主体的 service token 是谁」)。
2371
2541
  - 短问答 → `/v1/tasks`(同步);长任务/要进度条 → `/v1/runs` + `/events`(异步)。
2372
2542
  - 续聊:把上次 `sessionId` 存在 OA 会话里,下次带回。
2373
2543
 
@@ -2385,7 +2555,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2385
2555
  | 你看到 | 含义 | 怎么办 |
2386
2556
  |---|---|---|
2387
2557
  | HTTP `200` + `terminal.kind:"completed"` | 成功 | 取 `result` |
2388
- | `terminal.kind:"blocked"` | agent 主动报卡住 | 看 `terminal.reason`,补信息再发 |
2558
+ | `terminal.kind:"blocked"` | agent 主动报卡住(`terminal.detail` 缺席),或部署的 `userPromptSubmit` hook 拦下了这次主提交(`terminal.detail` 在场) | 有 `terminal.detail` 就渲它(`cause` / `hookReason`),没有才看 `terminal.reason`;补信息 / 改提示再发 |
2389
2559
  | HTTP `200` + `terminal.kind:"paused"` + 顶层 `status:"suspended"\|"needs_review"`(同步 `POST /v1/tasks`) | run **停在门上**(工具审批 / 计划审阅 / 资源分片),没跑完。🔴 **7.104.0 起**这张体就是那份结果本身(`terminal.gate` 说停在哪扇门、`checkpointId` 是对账位、`result`/`stats` 是停下来那一刻的读数),另叠 `taskId`/`sessionId`/`status` 三键 poll 回执 —— 修前只有这三键。恢复凭据**从不**在体上 | 按 `taskId` 轮询 `GET /v1/runs/:taskId`(park 中那一行同样带这份 `result`);审批走 `/v1/approvals`。判读用 `terminal.kind`,旧的 `status` 读法照旧成立。契约 §5.0c |
2390
2560
  | `terminal:{kind:"failed", code:"conflict"}` | 同一会话的两条写同时落在同一处(真并发:两处同时写一只会话),按叶的条件写只成一条;输的那一条以本码结束,撞上之后的内容没写进会话。多副本按任意路由轮流服务同一会话**不会**产生它(会话暖缓存命中前先核店里的当前叶,见 `SESSION_CACHE_TTL_SEC`) | 直接重试(重发接在会话当前的叶之后) |
2391
2561
  | `terminal:{kind:"failed", code:"limits.max_walltime_exceeded"}` | 墙钟到限(6.0.0 起 `status:"timeout"` 退役;7.64.0 起平面 `status` 键退役)。🔴 **7.71.0 / 引擎 7.13.0 收窄**:上限若在最后一轮 turn **已经给出干净答案之后**才响,run 记 `completed`、答案就是 `result`,**不铸本码**——那次停机的唯一痕迹是 `engine_notice` 的 `task.interrupt_unconsumed{origin:"walltime"}`(附录 D.3)。本码此后只在**工作被真切断**时出现。⚠️ 按码计数的运维面要把 `origin ∈ walltime\|turns` 的那一帧计入才守恒(server 侧对偶计量 `limit_ceiling_after_answer_total{origin}`,与 `budget_exceeded_total{code}` **相加**才是撞顶总量) | 拆小任务 / 提高 `limits.maxWalltimeMs`(毫秒) |
@@ -2394,14 +2564,18 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2394
2564
  | HTTP `403` / `404`(session/run) | 不是该 principal 的资源 | 用正确身份 |
2395
2565
  | HTTP `409`(`/v1/runs`) | 同 session 已有活跃 run | 等它完成 / 用返回的 `activeTaskId` |
2396
2566
  | HTTP `409` + `errorCode:"conflict.idempotency_payload_mismatch"`(7.126.0 起;`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`) | `Idempotency-Key` 已绑定在一条你读得到的 run 上,而绑定它的那次请求的体与这次不同(规范化:按键排序、去掉 `taskId`;契约 §0.7「同一请求 = 同一份体」)—— 不回放、不新建、那一条不动,体不回显键与那一行;流式腿在开流之后才撞上时,是终帧 `done.result` 带同一 `errorCode` 的 `failed`。A2A 不会遇到 | 重试就重发同一份体;不同的请求换一把新键。与 `conflict.run_exists` 不同:后者是那一行你读不到、或带的 `taskId` 不是那一行 |
2567
+ | HTTP `409` + `errorCode:"conflict.session_id_retired"`(7.146.0 起;`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs` 体里的 `sessionId`;续跑 / 唤醒 / 停驻赎回上是 `409 resume_blocked_by_policy` + `blockedBy:"conflict.session_id_retired"`;A2A 入口照它既有的拒形,JSON-RPC `-32602`) | 这只会话 id 属于一只**在本服务进程里被删掉**的会话,而这只会话现在的属主不是它删前的属主(无主与无主算同一位;同一只 id 在本进程被不同的属主各删过一次 ⇒ 谁都不是)—— 什么都没跑。新提交被拒时这只 id 已登记在你名下(一只空会话)。删前属主本人照常;判定只在删掉它的那个服务进程里、进程重启即消 | **换一个新的会话 id**(不要原样重发);想要好记的名字,自己记 name → uuid 的映射 |
2397
2568
  | HTTP `429` | 限流 | 看 `Retry-After` 退避 |
2398
- | HTTP `409` + `errorCode:"conflict.session_archive_stamped"`(7.144.0 起;`DB_BACKEND=local`;`POST /v1/sessions/:id/fork` 与会话同步导入的提交 `POST …/sync/import/:stagingId/entries`;7.145.0 起第一阶段 `POST …/sync/import` 对按 id 分类出 fork / stale 且没带同意的推送也直接答它,此前先答通用 `conflict`;7.141.0–7.143.0 答的码是 `conflict`) | 这只会话的存档仍带更早版本写下的属主戳(一次性迁移改写不了的,或跳过带迁移的版本升上来的数据根;起服 warn `local_archive_kept_stamped` 点名):不在它上面造新存档、不原地换它的日志(干净追加 / 载荷分叉 / fork / stale 各形的推送同答),什么都没写;读 / 续跑 / 拉取照常,「id 与内容都相同」的重推照常落定 | 按体里的固定句办:删掉这只会话(`DELETE /v1/sessions/:id`)再推。与暂存提交时属主变了的 `errorCode:"conflict"`(出路相反:从第一阶段重来)按码分开 |
2569
+ | 任一 4xx / 5xx 的 `errorCode`(7.146.0 起查契约附录 A 的**码族**列) | 目录每个码恰属五族之一:`transient`(原样重发等一等可能成)· `deterministic`(重发改不了判,别自动重发)· `store_judged`(逐次由店判,读体键 `permanence`)· `pending_decision`(卡仍待决,读体键 `retriable`:它说「卡还在」,不说「重发会成功」)· `retention_expired`(留存已被收走,新开) | 「要不要重试」「要不要重读待决列表」「要不要新开」按这一列读,不自己抄码表(契约文档附录 A.1g;MIGRATION 第 17 条)。表外码(core 原码镜像)没有族,照旧看状态与前缀 |
2570
+ | HTTP `429` + `errorCode:"limit.rate_exceeded"` + `reason:"run_concurrency_cap"` | 本副本在飞 run 已到并发帽(`RUN_MAX_CONCURRENT` 全局,或 `RUN_MAX_CONCURRENT_PER_PRINCIPAL` 按提交主体;缺省关;新提交的四个口,幂等重放不吃) | 看 `Retry-After`(5 秒)/ 体 `retryAfterSec` 退避后重投;或等在飞的 run 收尾 / 调高帽 |
2571
+ | HTTP `409` + `errorCode:"conflict.session_archive_stamped"`(7.144.0 起;`DB_BACKEND=local`;`POST /v1/sessions/:id/fork` 与会话同步导入的提交 `POST …/sync/import/:stagingId/entries`;7.145.0 起第一阶段 `POST …/sync/import` 对按 id 分类出 fork / stale 且没带同意的推送也直接答它,此前先答通用 `conflict`;7.147.0 起干跑 `POST …/sync/plan` 对这两形同样答它〔此刻读,提交里持锁的那一问仍是权威〕,此前答 `200` + `relation`;7.141.0–7.143.0 答的码是 `conflict`) | 这只会话的存档仍带更早版本写下的属主戳(一次性迁移改写不了的,或跳过带迁移的版本升上来的数据根;起服 warn `local_archive_kept_stamped` 点名):不在它上面造新存档、不原地换它的日志(干净追加 / 载荷分叉 / fork / stale 各形的推送同答),什么都没写;读 / 续跑 / 拉取照常,「id 与内容都相同」的重推照常落定 | 按体里的固定句办:删掉这只会话(`DELETE /v1/sessions/:id`)再推。与暂存提交时属主变了的 `errorCode:"conflict"`(出路相反:从第一阶段重来)按码分开 |
2399
2572
  | HTTP `429` + `errorCode:"limit.cost_quota_exceeded"` | per-principal 累计成本配额越顶(`MAX_PRINCIPAL_COST_USD`;**进场门**,不打断在跑的 run) | 看 `Retry-After` / 体 `retryAfterSec` 退避(体 `windowResetsAt` = 当代窗滚过的时刻);窗滚过或调高上限后放行 |
2400
2573
  | HTTP `400` + `errorCode:"request.id_invalid"` | 调用方给的 id 形不对。**会话 id 的形门只有一只** = 任意版本 canonical uuid(壳预设的 v4 也过门):会话口 `:id`(fork / 抹除 / 会话策略 / 2c 同步)与提交口(`POST /v1/tasks` / `/v1/tasks/stream` / `/v1/runs` 体里的 `sessionId`,A2A 的 `contextId` 同)同判,提交口的句子在句首之后点名「改用 uuid、自己记映射」;`GET /v1/sendfile-links?before=` 游标与 `body.taskId` 幂等键要求 uuidv7(时间序 / 幂等键契约) | 按 `errorCode` 分支;句子各自点名要求的形(`must be a canonical uuid` / `must be a uuidv7 …`),只作展示、别锚文案 |
2401
2574
  | HTTP `400` + `errorCode:"request.invalid_json"` + `reason:"not_json"` \| `"not_object"`(`reason` 7.133.0 起) | 请求体不是合法 JSON(`not_json`;所有读体的口同一个体 —— 含 `notify` / `wake` / 权限规则这几口,它们 7.132.x 及以前答 `request.body_shape`,其余多数口答 `request.rejected`),或合法 JSON 却不是这一口要的对象(`not_object`,`POST /v1/runs/:id/compact`)。读体超 8 MiB 是 `413 request.payload_too_large`(`field:"body"` / `reason:"too_large"` / `cap`,没有 `count`),不是本码 | 修请求体;按 `errorCode` + `reason` 分支,句子只作展示 |
2402
2575
  | HTTP `400` + `errorCode:"request.field_invalid"`(`PATCH /v1/sessions/:id` / 2c 导入的 `title`) | 标题不合形:1–120 个 Unicode 字符、**首尾不得有空白**(服务端不代为 trim,先于 C0 判)、不得含 C0 控制字符、**至少有一个可见字符**(只由零宽 / 方向控制符等不可见码点构成的串被拒,句子 `title must contain at least one visible character`;夹在可见字符之间的照收);`title: null` 清回自动标题;空串是 `request.body_shape` | 客户端送出前自行 trim、拒空显示的标题;按 `errorCode` 分支,句子只作展示 |
2403
2576
  | HTTP `400` + `errorCode:"request.field_invalid"`(撞 413 的帽见 `request.payload_too_large`)+ `reason:"over_cap"` / `"too_long"` / `"bad_entry"`(7.132.0 起:工具名清单、子代定义、两张宿主路径清单;7.133.0 起提交体上其余的帽同样带:`jobId` · `systemPrompt` · `appendSystemPrompt` · `settings.outputStyle` · `settings.env` · `clientContext.*` · `sandboxImageProfile` · `outputSchema` · `resumeAt` · 顶层技能 `content` / `baseDir`;子代定义里技能自己的 `content` / `baseDir` 同样带,坐标 `agents[i].skills[j].content`,句子仍是形句) | 撞了帽:`field` = 撞帽那一处的体内坐标(`skills[1].allowedTools` · `agents[0].denyTools[3]` · `agents` · `additionalDirectories` · `systemPrompt` · `settings.env.<变量名>` …),`count` = 送来的条数(`over_cap`)或字符数(`too_long`),`cap` = 帽;`bad_entry` = 清单第 `index` 条坏了(`field` = 那张清单)。7.132.0 起覆盖的面句子头一句恒为 `<field> has <count> entries\|characters — at most <cap> are accepted`;7.133.0 起补上的面句子不变、只加这一组键。同一个字段的形错(不是串、空串)只有句子 | 按 `field` 改那一处(合并 / 拆小条目、缩短文字、修那一条路径);句子只作展示 |
2404
2577
  | HTTP `400` + `errorCode:"request.field_conflict"`(7.133.0 起带 `detail`;`requireExistingSession` 缺 `sessionId` 一处是 422) | 请求里的字段互斥或互为前置(`verify` × `cascade`、`capabilitiesNeeded` 没有 `sandboxImageProfile`、`disableSkills` 同体带技能、决定口的 `remember` / `answer` / `editedPlan` 错门 …)。体带 `detail: { kind, fields }`:`kind` 是闭集词(`cascade_verify` · `image_profile` · `resume_anchor` · `system_prompt_fold` · `file_history_restore` · `skills_disabled` · `existing_session` · `scratchpad_root` · `ask_answer` · `policy_person_ask` · `plan_edit` · `plan_mode_after` · `bound_call_door` · `remember_decision`,全表见契约附录 A),`fields` = 冲突涉及的请求体坐标(句子主语在前;互斥的类只点请求里真带了的键,「X 要求 Y」的类 Y 是缺席的前置、也在里面);`system_prompt_fold` 另带 `count` / `cap`(折叠后的字符数与帽) | 按 `detail.kind` 分支(例:`cascade_verify` ⇒ 去掉 `verify` / `cascade` 重提),按 `detail.fields` 定位到那几个键;句子只作展示、别锚文案 |
2578
+ | HTTP `400` + `errorCode:"request.env_credential_refused"`(7.146.0 起) | 请求 `settings.env` 里有凭据:名字是本引擎的凭据变量(配置目录 `secret` 行 ∪ 模型条目 `apiKeyEnv`)或值是秘密形。体带 `field: "settings.env"` 与 `entries: [{ key, match }]`,值不回显。把这些条目挪进引擎进程的环境后重发(见 9.5j);停驻任务窗关后的续跑答 409 `resume_blocked_by_policy`(`blockedBy` = 本码) |
2405
2579
  | HTTP `400` + `errorCode:"config.tool_face_control"`(7.126.0 起;流式 / 后台腿的引擎侧拒是 `failed` 终局带同码,受理口拒三条腿都是提交时 400) | 请求的 `allowTools` / `excludeTools` 里有一个**读成空**的条目:任何工具名都不可能有的拼法(空白、括号不配、空名)、`allowTools` 写了 `mcp__*`(引擎拒)、`excludeTools` 够着了这一腿依赖的协议工具(`*` / 前缀 glob / 直接点名都算,原因词 `protocol_denied`;`excludeTools` 写 `*` 本身不拒 —— 7.137.0 起限制侧名内的 `*` 是整名 glob,卸每一只工具),或(7.131.0 起,受理口拒)**近似名**(大小写孪生 `BASH`;放行侧 `allowTools` 与限制侧 `excludeTools` 都拒 —— 想放的什么也没放 / 想禁的照用)与**限制侧的退役名**(`excludeTools: ["Task"]`)。放行侧 `allowTools` 的**内容形**(`Agent(Explore)`)与没有后继的退役名(`MultiEdit`)照 7.130.0 受理、交引擎既有通告(运维行 `runner_config_advisory`);**有后继的退役名**(`Task` · `bash`)7.132.0 起由引擎开跑前拒(`config.tool_face_control`,同步腿 400)。**真未知名**(拼写合法、这台部署没有工具叫它,懒挂载的合法形)7.129.0 起照跑:条目原样留在表里,运维行告知一次。选择器只取工具名那一段判名字(限制侧 `Bash(echo $HOME)` 不拒),`allowTools:["*"]` 与限制侧 `mcp__*` / `a2a__*`(整个协议)合法。清单超 1000 条 ⇒ 整只 `400 request.field_invalid`(`count` / `cap`),帽内拒句只点名前 5 条 + `; and K more`。拒时:主模型零调用、标题器零调用(7.127.0 起;7.126.x 上标题器仍会为这次提交调一次模型)。这次提交留下的会话无标题、零消息 | 改成句子里 `write` 后的现名(或删掉条目);只列这台部署挂得出的工具名 |
2406
2580
  | HTTP `400` + `errorCode:"config.agent.unknown_tool"`(7.131.0 起) | 请求 `agents[i].denyTools`(限制侧)里有读成空的条目:近似名(`BASH`)、退役名(`bash`)、任何工具名都不可能有的拼法;`agents[i].allowTools`(放行侧)里的**近似名**。开跑前拒、主模型零调用、一个 token 没烧,句子点名条目、原因词与现名。限制侧 `Tool(content)` 的内容不读(读成整只工具)、名内的 `*` 是整名 glob(7.137.0 起,孤 `*` = 每一只工具)、限制侧整个协议的拒 `mcp__*` / `a2a__*`、真未知名(`NotMounted`,照跑、告运维一次)不拒;放行侧的内容形(`Agent(Explore)`)与没有后继的退役名(`MultiEdit`)照 7.130.0 受理、交引擎既有通告,选择器清单里**有后继的退役名**(`Task` · `bash`)7.132.0 起由引擎开跑前拒;文法读不出的放行条目授不出去 = 收窄而不是放行:规则表(`permissions.allow` / 技能 `allowedTools`)上受理口不拒、引擎开跑时告运维;选择器清单(根 / 子代 `allowTools`)上受理口不拒、引擎开跑前自己拒(子代 ⇒ `config.agent.unknown_tool`、根 ⇒ `config.tool_face_control`,同步腿都是 `400`〔7.132.0 起;此前子代这一形答 HTTP 200 + failed 终局体〕;异步 / 活流腿 HTTP 2xx、同码落在终态 run 行) | 改成句子里 `write` 后的现名,或删掉条目 |
2407
2581
  | HTTP `400` + `errorCode:"prompt.assembled_role_refused"`(7.132.0 起;流式 / 后台腿是 `failed` 终局带同码) | 请求的 `systemPrompt` / `appendSystemPrompt`(含 `settings.outputStyle`)逐字含引擎提示里的一段锁定文本(宪法、安全段、`# Harness` 这样的标题行)—— 一份已组装的提示。开跑前拒、零调用,句子点名字段与命中段 | 只写角色层:删掉抄进来的引擎段(引擎自己组宪法与行为段;追加段恒排在宪法之后) |
@@ -2419,7 +2593,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
2419
2593
  | 流里一帧 `engine_notice` `code:"route.finish_reason_unknown"`,`detail:{ finishReason, sessionId, runId }`(7.126.0 起) | 提供方用一个引擎读不懂的结束原因结束了这一轮:回答按模型留下的样子保留,但可能被提供方截短了 | 告诉用户「这一段回答可能不完整」;同一个词同一 run 只说一次。运维按 `finishReason` 查是哪家网关 / 模型发的 |
2420
2594
  | 流里一帧 `engine_notice` `code:"route.stream_end_ignored"`,`detail:{ statement, word, frames, sessionId, runId }`(7.126.0 起) | 提供方已经结束了这一轮,它的流却又发来了东西(又一个结束 / 一个错误 / 内容);第一个结束算数,之后的没进这一轮 | 一般无需处置;频繁出现 ⇒ 运维查那条网关的流式实现(`statement` / `word` 说的是迟到的第一帧) |
2421
2595
 
2422
- 会话流 `engine_notice` 帧的**全部**可投码 = 随包 `dist/trace/engine-notice-wire.d.ts` 里的 `ENGINE_NOTICE_WIRE_CODES`(码表本身,只列码名;每一帧自带 `message` 与 `detail`);逐版增删见 `wire-changes/server@<版本>.json` 的 `closedSetMembers` 里 `set` 为 `engineNotice` 的那一项(该版没有增删就没有这一项);本表只列运维最常问的几条。其中 `delegation.ask_unresolvable` = 一只调用被最终拒而**没有任何人裁决过它**(`detail.cause` / `parkLaneExisted` / `remedies` 分开读),委派子代上的这一条今天不上父会话流。
2596
+ 会话流 `engine_notice` 帧的**全部**可投码 = 随包 `dist/trace/engine-notice-wire.d.ts` 里的 `ENGINE_NOTICE_WIRE_CODES`(码表本身,只列码名;每一帧自带 `message` 与 `detail`);逐版增删见 `wire-changes/server@<版本>.json` 的 `closedSetMembers` 里 `set` 为 `engineNotice` 的那一项(该版没有增删就没有这一项);本表只列运维最常问的几条。其中 `delegation.ask_unresolvable` = 一只调用被最终拒而**没有任何人裁决过它**(`detail.cause` / `parkLaneExisted` / `remedies` 分开读),委派子代上的这一条投委派树根会话(父会话的流),被拒那只调用所在的子代会话在 `detail.sourceSessionId`、代理 id 在 `detail.agentId`;判「是谁的调用被拒」读 `detail.sourceSessionId ?? detail.sessionId`。
2423
2597
 
2424
2598
  **机器码**:每个 4xx/5xx 响应体都带一个 `errorCode`(与人类文案 `error` 并列),这是**唯一**该拿来做
2425
2599
  程序分支的字段——**别锚 `error` 文案**。前缀族固定:`auth.` / `request.` / `not_found.` / `conflict.` /
@@ -2593,7 +2767,7 @@ k8s 车道每条 run 一只新 pod(pod 寿命 = 泄漏护栏,不跨 run 复用)
2593
2767
  **不回落**成把值写进 pod spec。它与 `SANDBOX_PKG_SOURCE` 推导出的包源变量在装配处分座一次,**同名时名单赢**(派生的那条不出现;部署方的刻意覆盖);
2594
2768
  适配器不把值拼进执行命令串、不写进 `/workspace`,因而不随跨轮保留 / 挂起的快照进对象存储;不进事件日志(起服行只报键名)。
2595
2769
  pod 里的进程读得到这些值 —— 那正是转发的目的;进程自己若把值写进 `/workspace`,它就会随快照走。
2596
- - **RBAC**:沙箱命名空间里服务账号要有 `secrets` 的 `create`(Role 规则 `- apiGroups: [""] resources: [secrets] verbs: [create]`;随包 `deploy/sema-up` 的 Role 已带)。
2770
+ - **RBAC**:沙箱命名空间里服务账号要有 `secrets` 的 `create`(Role 规则 `- apiGroups: [""] resources: [secrets] verbs: [create]`;随包 chart 在 `sandbox.sandboxEnv` 非空时带这一条〔同一个值同时写引擎的 `K8S_SANDBOX_ENV`〕,cluster 静态清单里它是注释块、给 server 设了 `K8S_SANDBOX_ENV` 时取消注释)。
2597
2771
  它恰是服务为 env Secret 真发的调用;不要给 `get`(服务从不读回,给了就读得到命名空间里每一只 Secret)与 `delete`(Secret 随属主 pod 由集群回收)。租约 Secret 的 `patch` 只在 §9.3h 的平面打开时要。
2598
2772
  名单非空时起服各问一次 SelfSubjectAccessReview:答了「不许」⇒ **拒启**,拒句带 `[sandbox-secret-rbac]`、点名缺的动词与这条规则;问不到(API 连不上 / 审查本身被拒)⇒ 照常起服,
2599
2773
  一行 `sandbox_secret_seat_unverified` + fail-open 计数 `server.sandbox-secret.seat-unverified` —— 那时若 Role 真缺,第一个要转发的 run 在连接时被拒、它的 pod 被删。
@@ -2641,7 +2815,7 @@ k8s 车道每条 run 一只新 pod(pod 寿命 = 泄漏护栏,不跨 run 复用)
2641
2815
  | `WORKSPACE_CACHE_SIZE` | `20Gi` | 卷的申请量(k8s quantity 形,如 `20Gi` / `500Mi`;形不合 ⇒ 拒启) |
2642
2816
  | `WORKSPACE_CACHE_MOUNT` | `/cache` | 卷在 pod 里的挂载点(绝对路径;与工作区挂载 `K8S_MOUNT_PATH` 相同 / 互相嵌套 ⇒ 拒启;落在保留的镜像目录闭集 —— `/bin /boot /dev /etc /home/sandbox /lib /lib64 /opt/sema /proc /root /run /sbin /sys /tmp /usr /var` —— 上 / 之下 / 之上 ⇒ 拒启并点名那一项:卷挂在那里会遮住镜像在那条路径下放的东西) |
2643
2817
 
2644
- **RBAC**:沙箱 Role 要有 `persistentvolumeclaims` 的 `get` / `create` / `delete` / `deletecollection`(回收按标签删 = `deletecollection`;chart 与 cluster 静态清单 7.126.0 起已带)。
2818
+ **RBAC**:沙箱 Role 要有 `persistentvolumeclaims` 的 `get` / `create` / `delete` / `deletecollection`(回收按标签删 = `deletecollection`;随包 chart 在 `sandbox.workspaceCacheVolume: true` 时带〔同一个值同时写引擎的 `WORKSPACE_CACHE=volume`〕,cluster 静态清单里它们是注释块、给 server 设了 `WORKSPACE_CACHE=volume` 时取消注释;没开缓存卷的部署 Role 里没有这四个动词)。
2645
2819
  **能力位**:`GET /v1/capabilities.workspaceSync.cache = { mount, size }`,present-iff k8s 车道 ∧ `WORKSPACE_CACHE=volume`(与快照存储无关:没配存储而开了卷的部署,
2646
2820
  `workspaceSync` 只有 `cache` 一个成员 —— 读跨轮保留一律读 `persist === "space"`)。
2647
2821
 
@@ -3034,10 +3208,10 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3034
3208
  `http` 条目 header **值**里的 `$NAME` / `${NAME}` 按条目的 `allowedEnvVars` 展开,读法是契约包 settings-schema 的 `interpolateHookEnvVars`(壳与本服务同调,同一份钩子定义到端点的字节相同):
3035
3209
  - 名字只认大写 `[A-Z_][A-Z0-9_]*`;小写名(`$my_token`)是普通文字,原样发出。
3036
3210
  - 列在 `allowedEnvVars` 里 ⇒ 展开成钩子环境里的值(钩子环境 = 净化后的进程环境 allowlist + 同请求 `settings.env`;服务进程的密钥不在其中);钩子环境里没有这个名字 ⇒ 空串。
3037
- - 名单外的引用 ⇒ 空串;凭据名表里的名字(契约包 `HOOK_ENV_CREDENTIAL_NAMES`:`CLAUDE_CODE_OAUTH_TOKEN` 一类 27 个名字 × 裸名与 `INPUT_` 前缀;**不含** `ANTHROPIC_API_KEY` / `MODEL_API_KEY` 这类模型凭据名)一律空串,点进名单也一样。本服务自己的模型凭据不靠这张表护:它们从不进钩子环境(钩子环境 = 净化后的进程环境 allowlist + 同请求 `settings.env`,服务进程的密钥名在扣留名单里),所以 `${ANTHROPIC_API_KEY}` 在本服务上展开成空串是因为钩子环境里没有它,不是因为它在凭据名表里;调用方自己在 `settings.env` 里给的同名变量会照常展开。
3211
+ - 名单外的引用 ⇒ 空串;凭据名表里的名字(契约包 `HOOK_ENV_CREDENTIAL_NAMES`:`CLAUDE_CODE_OAUTH_TOKEN` 一类 27 个名字 × 裸名与 `INPUT_` 前缀;**不含** `ANTHROPIC_API_KEY` / `MODEL_API_KEY` 这类模型凭据名)一律空串,点进名单也一样。本服务自己的模型凭据不靠这张表护:它们从不进钩子环境(钩子环境 = 净化后的进程环境 allowlist + 同请求 `settings.env`,服务进程的密钥名在扣留名单里),所以 `${ANTHROPIC_API_KEY}` 在本服务上展开成空串是因为钩子环境里没有它,不是因为它在凭据名表里;7.146.0 起调用方在 `settings.env` 里给这类名字(或任何秘密形的值)新提交即 `400 request.env_credential_refused`(9.5j)。
3038
3212
  - 每个 header 值剥 CR / LF / NUL;header 名不展开。
3039
3213
  - 置空(名单外 / 凭据名)时日志一行 `hook_http_header_env_blanked`(名字与原因,不带值)。
3040
- 例:`"headers": { "Authorization": "Bearer ${HOOK_TOKEN}" }`、`"allowedEnvVars": ["HOOK_TOKEN"]`,并在请求 `settings.env` 里给 `HOOK_TOKEN`。7.125.x 及更早的读法不同(升级见 MIGRATION)。
3214
+ 例:`"headers": { "Authorization": "Bearer ${HOOK_TOKEN}" }`、`"allowedEnvVars": ["HOOK_TOKEN"]`,并在请求 `settings.env` 里给 `HOOK_TOKEN`(7.146.0 起值是秘密形的 token 不能经 `settings.env` 给,今天没有替代通道 —— 9.5j 已知限制)。7.125.x 及更早的读法不同(升级见 MIGRATION)。
3041
3215
  **两张名单 `settings.allowedHttpHookUrls` / `settings.httpHookAllowedEnvVars`(7.142.0 起;CC settings 同名键)**:读法与判决都在契约包 settings-schema,本服务只在 http 条目**拨出之前**问一次(能力位 `taskSettings.httpHookAllowlists`,单用户车道为真):
3042
3216
  - `allowedHttpHookUrls`(URL 模式的串数组,例 `["https://hooks.example.com/*"]`)在场 ⇒ 条目 `url` 必须命中其中一条才发出;不命中 / 名单是 `[]` / 名单不是串数组 ⇒ **一个字节都不发**。非托管组:这次 hook 算非阻断故障(fleet 流 `hook_notice` `url_not_allowed`,运维 warn `hook_http_withheld`);
3043
3217
  托管组(`source: managedPolicy` / `managedPlugin:*`):失效关闭,`PreToolUse` 在没有否决时改问人。句子点名 `settings.hooks.<事件>` 与规则(`url` / `empty` / `malformed`)。出路:把地址的模式加进名单,或改 hook 的 `url`。
@@ -3047,14 +3221,18 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3047
3221
 
3048
3222
  ### 9.5c-bis `settings.hooks` 的执行模型与时限(7.132.0 起)
3049
3223
 
3050
- - 被采纳的 `settings.hooks`(单用户车道)在引擎里是**一张**扩展注册,名字 `settings.hooks`(`wiring_manifest.mods[]` 里看得到它;
3051
- 审批卡 `allowRuleShadowed` 的 `hook` 成员、钩子失败时模型读到的拒句都点名它)。本部署自己另挂两张只观察的注册:恒挂的拒绝计量
3052
- `server.permission-denied-meter`,`TOOL_TRACE=true` 时的 `server.tool-trace`(品牌部署另有一张提示装配站上的 `server.brand-identity`,只答品牌行 overlay,不在工具门上)。观察者的判词不采纳,链里唯一的答者是请求的钩子。
3224
+ - 被采纳的 `settings.hooks`(单用户车道)在引擎里是**每条车道一张**扩展注册:非托管车道 `settings.hooks`;有托管组(`source` 为 `managedPolicy` / `managedPlugin:*`,见 9.5c-quater)时
3225
+ 另一张托管车道 `settings.hooks.managed`,排在前面(7.146.0 起;此前恒一张)。`wiring_manifest.mods[]` 里看得到它们;审批卡 `allowRuleShadowed` 的 `hook` 成员、问因项、钩子失败时模型读到的拒句都点名作答 / 失败的那一张。本部署自己另挂两张只观察的注册:恒挂的拒绝计量
3226
+ `server.permission-denied-meter`,`TOOL_TRACE=true` 时的 `server.tool-trace`(品牌部署另有一张提示装配站上的 `server.brand-identity`,只答品牌行 overlay,不在工具门上)。观察者的判词不采纳;本服务自铸的注册在工具门(`classic.*` 站)上没有链成员(品牌行那一张在提示装配站 `prompt.compose` 上是链成员,不碰工具门;运维声明的 mod 包可以有,见 9.5c-quater 末条)。
3053
3227
  - 每个事件的语义与此前相同:`Stop` 条目在主任务结束与委派子代结束时都跑;`PermissionDenied` / `SubagentStart` / `SubagentStop` / 两个 model-switch 事件属壳,服务端不跑。
3054
- - **`HOOKS_TIMEOUT_MS`** = 这张注册的 `budgetMs`。引擎对它的定义是 "Each handler's own time" —— 每个 handler 自己的时间(一个事件跑一遍本请求的钩子条目;
3228
+ - **`HOOKS_TIMEOUT_MS`** = 每张车道注册的 `budgetMs`(两张各一份同值;一个事件最坏 2 倍)。引擎对它的定义是 "Each handler's own time" —— 每个 handler 自己的时间(一个事件跑一遍这条车道的钩子条目;
3055
3229
  引擎的钟,handler 等 `next` / `$` 调用的那段暂停),**不是**钩子子进程的墙钟。**未设 = 600000 ms**(= 单条命令超时的上限;引擎的钟在 handler 起跑时就开始走,
3056
3230
  命令的 `timeout` 钟要等 shell 解析和起进程之后,所以 `timeout` 配到 600 s 的命令跑满时,预算会先到);`0` 按字面(零预算)。超预算:`PreToolUse` 上该调用被拒(fail-closed,拒句点名 `extension "settings.hooks"`)、
3057
- `UserPromptSubmit` 上该提交被拒,其余事件跳过这一轮钩子。子进程自己的墙钟另有两只,照旧:单条命令的 `timeout`(上限 600 s)与一个事件 120 s 的总墙钟(到点不再起下一条)。
3231
+ `UserPromptSubmit` 上该提交被拒,其余事件跳过这一轮这条车道的钩子。托管车道是外层(先等非托管答完,等的那段引擎停它的表):非托管条目吃光的只是非托管那张的预算,
3232
+ 其余事件上托管钩子照跑;托管车道自己的预算用尽 ⇒ 撞上的托管条目发 `hook_notice` / `hook_managed_unrun{reason:"budget_exhausted"}`(`detail` 点名引擎预算;每个事件恰一帧,任务取消不记)。
3233
+ `Stop` / `SubagentStop` 上每条车道的 `continue:false` 只撤本车道的推回,别的车道与部署座链成员的推回照收(两车道意见相左 ⇒ 这一轮不收场);
3234
+ 观察站(`PostCompact` / `StopFailure`)上托管车道先跑、非托管后跑。
3235
+ 子进程自己的墙钟另有两只,照旧:单条命令的 `timeout`(上限 600 s)与每条车道一个事件 120 s 的总墙钟(到点不再起下一条)。
3058
3236
  - **预算到、或任务被取消 ⇒ 这个 handler 里在跑的命令钩子被叫停**(7.133.0 起;此前它要跑到自己的 `timeout` 才停,取消之后还可能接着写文件):进程组先收 SIGTERM,
3059
3237
  1 秒到点组长还在就补一发组 SIGKILL(组长在宽限内退了就不再发 —— 退出的进程号可能已被回收;代价:组长一退,组里屏蔽 SIGTERM 且不攥管道的后代不再被追杀),
3060
3238
  进程组长退出才算收走;这个 handler 里还没起的后续条目不再起。结局不变(上一条的 fail-closed 拒 / 跳过),运维日志多一行
@@ -3064,11 +3242,53 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3064
3242
  ### 9.5c-ter `settings.hooks` 的键:写了就要被读,否则整份新提交 400(7.135.0 起)
3065
3243
 
3066
3244
  - 一只匹配组 / 一个条目上出现 hooks 读者不读的键 ⇒ 新提交(`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`)`400 request.body_shape`,体里 `unknownKeys` 逐个点名全坐标
3067
- (如 `settings.hooks.PreToolUse.0.hooks.0.onFailure`),与 `settings` 子树、请求体顶层的未知键同一种拒法。读不读以读者为准:组上是 `matcher` / `hooks` / `source`,
3068
- command 条目上是契约字段加 `plugin` / `args`,其余条目类型上是各自的契约字段(`plugin` / `args` 写在 http / prompt / agent 条目上同样被拒)。
3069
- - 常见撞上的形:参照实现 2.1.296 新增的 `onFailure`(本服务不执行这个键 —— 7.134.x 及更早它被悄悄剥掉,写了 `"block"` 的守卫起不来时照放行)、拼错的 `timout` / `matchr`。
3070
- 出路:删掉那个键,或改成读者读的拼法;拒体里的坐标就是要动的那一处。
3071
- - 停驻任务续跑时不判这一条(存量请求里的这些键照旧被剥掉、其余钩子照采纳)。
3245
+ (如 `settings.hooks.Stop.0.hooks.0.onFailure`),与 `settings` 子树、请求体顶层的未知键同一种拒法。读不读以读者为准:组上是 `matcher` / `hooks` / `source`,
3246
+ command 条目上是契约字段(含 `args` / `onFailure`)加 `plugin`,http 条目上是契约字段(含 `onFailure`),其余条目类型上是各自的契约字段
3247
+ (`plugin` / `args` 写在 http / prompt / agent 条目上、`onFailure` 写在 prompt / agent 条目上同样被拒)。每一型的契约字段读能力位 `settingsContract.hookEntryFields`。
3248
+ - 常见撞上的形:写错条目类型的键(`plugin` / `args` 写在 http / prompt / agent 上、`onFailure` 写在 prompt / agent 上、`continueOnBlock` 写在 prompt 之外、
3249
+ `rewakeMessage` / `rewakeSummary` 写在 command 之外、`cloud` 写在 command / http 之外)、拼错的 `timout` / `matchr`。
3250
+ 出路:删掉那个键,或改成读者读的拼法;拒体里的坐标就是要动的那一处。(7.135.0–7.146.x 上 command / http 条目的 `onFailure` 也在这里被拒;7.147.0 起它是契约字段、本服务执行它,见 9.5c-quinquies。)
3251
+ - 参照实现的四个键在各自的归属型上受理(7.147.0 起;此前在这里被拒):command 的 `rewakeMessage` / `rewakeSummary` / `cloud`、http 的 `cloud`、prompt 的 `continueOnBlock` ——
3252
+ **本服务如实带、不执行**:唤醒文本不加前缀、没有摘要行、`cloud` 写什么都照跑、`continueOnBlock` 不改 `Stop` 拦下的结局。`rewakeMessage` / `rewakeSummary` 要非空串、
3253
+ `continueOnBlock` 要布尔,值坏 ⇒ 与其它契约字段写坏同一种拒(守卫事件整份拒);`cloud` 任意值。
3254
+ - 停驻任务续跑时不判这一条(存量请求里读者不携带的键照旧被剥掉、其余钩子照采纳)。
3255
+
3256
+ ### 9.5c-quater `settings.hooks` 的托管标签:调用方自报,服务端只校闭集、不验身份,失败方向只收紧(7.133.0 起)
3257
+
3258
+ - **托管标签是调用方自报的,服务端只校闭集、不验身份**:组上的 `source` —— 托管 = `managedPolicy` / `managedPlugin:*`,非托管 = `user` / `project` / `local` / `flag` / `plugin:*` 与缺席 ——
3259
+ 由请求体里的调用方写,服务端只判它在不在闭集里(集外词 / 形错 ⇒ `400 request.field_invalid`,`field: "settings.hooks"`),**不验身份**,也不按调用方的档位剥掉、改写或拒绝托管词。
3260
+ 本服务没有用户 / 项目 / 本地 / 托管层的设置文件:请求体 `settings` 是启动方(壳 / 嵌入方)端合并之后交来的那一份,所以「托管」今天的唯一来源就是壳 / 嵌入方的自报;
3261
+ `settings.hooks` 只在单用户车道被采纳(多租户车道整只忽略),能自报的就是持有这台部署凭据的调用方(壳 / 嵌入方)。不要把它读成「组织策略来源」的证明。
3262
+ 标签只改两件事,失败方向**只收紧**(比的是下方 —— 同座非托管车道、运维 handler 与常规门:托管答案不比下方宽;与不带标签的同一条钩子逐员比则不全同,见本节末条):① 位置 —— 非托管相 → 托管插件相 → 托管策略相(终判相),托管条目在非托管改写之后的最终输入上判,托管相自己改写了输入则整相按新输入重判一遍;
3263
+ 所以托管条目的 `allow` 绑定的是最终要执行的那份输入(一条非托管 `allow` 若排在另一条非托管改写之前,会因输入被改而作废;托管的不会 —— 它是改写之后才判的,效果等同于调用方把同一条钩子不带标签写在数组末尾);
3264
+ ② 失败方向 —— 托管条目没跑成、或判词不可信,`PreToolUse` 折 `ask`(非托管相里没跑成的条目照旧只是无意见;判词是闭集外的词则两相都折 `ask`);引擎 handler 预算、事件墙钟、prompt 条数、上下文份额各车道独立计量(每条车道一张注册)。
3265
+ **`allow` 不看相**:放行只看「判词是 `allow` ∧ 判词可信 ∧ 它判过的就是最终输入」,出口对象不带车道 —— 同一条钩子、同一个判词、同一份输入,自报 `managedPolicy` 拿不到任何额外放行(任务座之下有部署座链成员的站上同样如此 —— 见本节末条)。
3266
+ 所以调用方给自己的钩子写 `source: "managedPolicy"` 不是抬权口,而是「请按失效关闭跑这一组、并让它最后判」的开关;不要这层严格,去掉 `source` 即可(缺席 = 非托管)。
3267
+ 过渡(7.146.0 起,至引擎为钩子提供「请求收场」席位止):同一请求同时带托管与非托管的 Stop 钩组、且两车道意见相左(一边 `continue:false`、一边 `block`)时,`continue:false` 只撤本车道的推回,另一车道的 `block` 作数 —— 这是托管标签在 Stop 上会改变的第三件事。目标形 = 任一 `continue:false` 即请求钩子整体无意见(7.145.0 形,与 CC 一致),席位到货当版恢复并删本句。
3268
+ - **任务座之下有部署座链成员的站上,托管条目只许收紧**(7.146.0 起):运维声明的 mod 包(`MODS_ROOT` / `MODS_PACKAGES`)在 `classic.PreToolUse` 上以 `append` 档(排在请求钩子所在的任务座之后)挂了链成员
3269
+ (观察者不算;读引擎的准入记录)⇒ 托管车道照旧最后判,但那一站上只许收紧 —— 律是**托管答案不比下方(运维 handler 与常规门)宽**:否决 / 问(含托管条目没跑成折的问)/ 上下文照收;托管的 `allow` ⇒ 这次调用拒(与不带标签的 `allow` 被引擎座信任律拒同一个结局,不落到常规门);
3270
+ 托管的 `updatedInput`(带 `ask` 的也算)给不进运维 handler(它只判过改写之前的那一份)⇒ 这次调用拒(失效关闭);运维 handler 要真人批的问,托管作答之后照旧要真人批。
3271
+ 否则自报的托管组会等运维 handler 判完原输入、再在它之上放行或改写 —— 改写后的输入运维 handler 从没见过。
3272
+ 运维 handler 在这一次调用的链上时,与不带标签的同一条钩子逐员比:放行 / 改写 / 否决同一个结局(与 7.145.x 同);问与只给上下文两员与不带标签不同 —— 不带标签的这两种答复内层挂、跳过下方没判过的运维 handler,被引擎座信任律拒(7.145.x 上带标签的同形同样被拒);
3273
+ 带标签的在运维 handler 判过之后作数:问照问(要人批),只给上下文照收、调用落到常规门(与不挂这条钩子同一个结局)。两员都不越过运维 handler 与常规门,所以覆盖站上的标签换不来越过下方的放行。
3274
+ 运维 mod 带匹配器而这一次调用不中时(见下一句),带标签的放行 / 改写照样拒、不带标签的作数(链上没有运维 handler);问与只给上下文两形下两者同结局 —— 只多收紧。
3275
+ 卡面:托管条目在下方的问(运维 handler 或同座非托管车道;不论下方的问带哪个成因词)之上也问 ⇒ 问句先列下方那一句、再列托管那一句(`… (a managed PreToolUse hook also asks: …)`);卡上的注册名与问因项(`askReasons`)由引擎按作答的那一张注册铸,托管车道作答时只点名 `settings.hooks.managed`(已知限制)。
3276
+ 挂法不随站点变:同座的非托管车道与运维 handler 恒先判、否决恒跑 —— 运维 mod 只管某些工具(带匹配器)时,别的工具的调用上同样如此,那些调用上托管条目也按这一条收紧(准入记录不带匹配器;只多收紧,不跳过否决)。
3277
+ 不带托管标签的组照旧:运维 handler 在这一次调用的链上时,它们的非否决答复被引擎的座信任律拒。
3278
+ 只挂观察者的部署(本服务自铸的拒绝计量 / 工具追踪)、链成员只在任务座之上(`prepend` 档,或同档 `user` —— 同档里部署座在前)的部署不收紧:那里运维 handler 已先判、并在答回来的结果上再判。
3279
+
3280
+ ### 9.5c-quinquies `onFailure`:钩子自己坏了时,放行还是拦下(7.147.0 起)
3281
+
3282
+ - 写在 command / http 条目上,两个词:`"continue"`(缺席同义)= 钩子坏了只报一笔故障、动作照常;`"block"` = 钩子坏了**按退出码 2 算** —— 它守的那个动作被拦下。
3283
+ 拦下的形就是这个事件上 exit 2 的形:`PreToolUse` 工具调用被拒、`UserPromptSubmit` 这次提交被拒、`PreCompact` 这次压缩不做;`PostToolUse` / `PostToolUseFailure` / `PostToolBatch`
3284
+ 上工具已经跑过,拦不了,那句话作为反馈交给模型。拦下的理由是一句 `<事件> hook did not reach a verdict (<成因>); blocking because onFailure is "block"`,成因词与故障帧的 `reason` 相同。
3285
+ - **「坏了」怎么算**(本服务比参照实现宽,失效关闭):起不来、超时、退出码非 0 非 2(http:非 2xx)、输出读不成判词、`PreToolUse` 的 `permissionDecision` 不是 allow / deny / ask 之一;
3286
+ 以及本服务自己的门让它没跑成 —— URL 名单拦下、shell 形写了 powershell、`if` 条件评不出、组的 matcher 编不过、这个事件的墙钟用完(墙钟用完之后同一车道里排在后面、被选中的 `block` 条目同样拦下,不看它排第几)。
3287
+ 不算坏:`once` 已经跑过一次、`if` 不匹配、matcher 没选中(本就不该跑)。
3288
+ - **不生效**:`async` / `asyncRewake` 的条目(后台跑,本就不拦);`Stop` 事件(拦停止会让该停的停不下);观察站 `StopFailure` / `PostCompact`(那里没有可拦的动作)。
3289
+ - 运维要看懂一次拦下:故障帧 / 运维日志照旧点名是哪一条、什么成因(`hook_non_blocking_failure` 帧的名字是历史名,`block` 条目的那一次随后就被拦下);改条目、改 URL 名单,或把这一条改回 `continue`。
3290
+ - 停驻任务:升级前存下的请求里写了 `onFailure: "block"` 的守卫,续跑时开始按 block 执行(7.146.x 及更早这个键被读者剥掉);存量里写的是两词之外的值 ⇒ 这条车道采纳请求体 hooks(单用户车道)时**拒续跑**
3291
+ `409 resume_blocked_by_policy`(`blockedBy: "request.field_invalid"`,`field: "settings.hooks"`;卡 / 任务仍待决;7.146.x 及以前是整份 hooks 不采纳照跑)—— 见 MIGRATION。
3072
3292
 
3073
3293
  ### 9.5d MCP 服务器 http 传输的身份头:`principalHeader` 与 `assertionHeader` 二选一(请求体与部署声明同一种读法)
3074
3294
 
@@ -3083,7 +3303,7 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3083
3303
  条目的受理键 = `name` · `transport` · `allowTools` · `toolAxes` · `toolFaces` · `contentOrigin` · `elicitation` · `source` · `startupTimeoutMs`(7.132.0 起)· `sessionMetaKey`(7.138.0 起)(`transport` 的子键:`kind` · `command` · `args` · `env`(stdio),`kind` · `url` · `headers` · `principalHeader` · `assertionHeader`(http))。**新提交上每个键只有一只判官,判官在受理口,写坏的答案只有一种**(下面第一条:400)。`startupTimeoutMs`(这一只 server 自己的启动界,毫秒整数,范围与引擎 env 旋钮 `MCP_TIMEOUT` 同一段)7.134.0 起同样在受理口判 —— 判官是引擎包根导出的 `mcpStartupTimeoutMs`(启动界的唯一读者):数字串 / 小数 / 越界 / `null` ⇒ `400 request.field_invalid`,原因句是引擎的那一句(7.132.0–7.133.x:受理口不判、请求照常 200,引擎拨号时只拒这一只 server);耐久 resume 重放存量请求不判(原样交引擎,坏值在拨号时只拒这一只 server —— `config.mcp_startup_timeout_invalid`,兄弟照挂)。`sessionMetaKey`(声明了它的服务器在每次 `tools/call` 的 `_meta` 下收这条腿的会话 id)7.138.0 起同样在受理口判 —— 判官是引擎包根导出的 `judgeSessionMetaKey`(键名格式、保留前缀、上界都是它的):坏形 ⇒ `400 request.field_invalid`,原因句是引擎的那一句;耐久 resume 重放存量请求时坏形让这一条 server 整条不进。
3084
3304
  - **新提交**(`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`):键在场却判不出形 ⇒ `400 request.field_invalid`,拒句形如 `mcpServers[2].allowTools (server "crm") is not a usable declaration — must be an array of strings (omit the key to leave the server's tools unrestricted) — got a string of 4 characters. …`:点名 `mcpServers[i].<键路径>`、server 名与该键判官的原因句,**整份请求不收** —— 不挂一半、不丢声明、不默认。原因句不回显声明的值,有几处例外,回显只有一只口径(剥控制与双向字符、截到 64 字、超出加 `…`):server 名(含写坏的 `name` 值,另报送来的原长度)、`contentOrigin` 词表外的值;`toolAxes` / `toolFaces` 的原因句是引擎写的(7.134.0 起 `toolFaces` 的判官也是引擎包根导出的 `readToolFaces`):点名成员与闭词表本身,调用方写的串(工具名这类键、闭词表拒的那个词)一律只给记号与码位数(`[redacted] (N characters)`),整句详情由引擎截到 240 字、本服务再剥控制与双向字符 —— 不回显显示文本、不回显合规的列表;`transport.url` 的值从不回显(带凭据的 url 被拒时,原因句只点名命中的那一段与凭据名表里的那个词)。可选键**缺席**永远合法(真「没声明」),必填的 `name` / `transport` 缺席同样 400;在场的 `null` 不是缺席。与单用户 / 多租户无关。列表本身同一条律(7.132.0 起,先于条目判):`mcpServers` 在场却不是数组、超过 32 条(整份拒,不挂前 32 条)、某一条不是对象 ⇒ `400 request.field_invalid`,拒句点名 `mcpServers` 或 `mcpServers[i]` 与形的种类;条目层或 `transport` 层出现词表外的键 ⇒ `400 request.body_shape`,拒体带 `unknownKeys`(全路径、至多 20 条)。`mcpServers` 缺席或为 `null` = 没声明,照常受理。
3085
3305
  - **`transport.url` 不得带凭据(7.135.0 起)**:http 传输的 `url` 带 userinfo(`https://user:pass@host/mcp`,只有用户名也算),或带名字在凭据名表里的 query / fragment 参数(`?api_key=` · `?key=` · `?token=` · `#access_token=` …;表 = 本服务日志脱敏器认作凭据名的那一张,全表见契约附录 A `request.field_invalid` 行;名字大小写不敏感,按解一次百分号编码读)⇒ 新提交 `400 request.field_invalid`,体另带 `field: "mcpServers[i].transport.url"` 与 `reason`(`url_userinfo` | `url_credential_param`),原因句点名出路:**凭据改写进 `transport.headers`**(如 `"Authorization": "Bearer …"`)。为什么:任务停驻时提交体整份进续跑上下文,`transport.headers` 的值在那里只以封签存在,`url` 照原样存(日志与读面同理)。值从不回显;解析不了的 url 不在这一问里判(拨号时失败)。7.134.x 及以前照收、原样落停驻上下文;带 userinfo 的 url 本就连不上(fetch 对带凭据的 URL 构造即抛),带名表参数的升级后要改。同一条规则在 A2A 对端 / http 钩子 / 搜索后端 / 图片 URL 上的几座见 9.5g。
3086
- - **耐久 resume 重放存量请求**:不拒(拒会把升级前 park 的任务永久卡死;`transport.url` 带凭据的存量条目照挂、url 逐字交引擎)。写坏的 `name` · `transport` 的 `kind`/`command`/`url` · `allowTools` · `toolAxes` · `toolFaces` · `sessionMetaKey` ⇒ 这一条 server 整条不进,运维 warn `mcp_injection_dropped`(`reason: malformed_entry`,`field` 点名)并投用户面通告 `mcp.injection_dropped`(其中 `toolAxes` 在一次性迁移 M19 的窗开着时先按升级前读法重读一次,过了就挂,见上文 `toolAxes` 一节);写坏的 `contentOrigin` · `elicitation` · `source` · `transport` 的 `args`/`principalHeader` 与词表外的键 ⇒ 丢掉那一个键、server 照挂;`transport` 的 `env`/`headers`(名 → 串记录)对 JSON 存体按 7.131.x 的读法交:有值不是串的成员 ⇒ 整键不交,只有 `__proto__` 名的成员坏 ⇒ 剔掉它、其余照交;server 照挂,一行运维 warn `mcp_transport_members_dropped` 点名(见 9.5c-bis 同段);条目不是对象 ⇒ 这一条不进(`malformed_entry`);超过 32 条 ⇒ 第 33 条起只记 `over_cap`;整只不是数组 ⇒ 整只忽略 + 运维 warn `mcp_injection_ignored`。
3306
+ - **耐久 resume 重放存量请求**:写坏的条目整条不进或丢键照挂,不拒(拒会把升级前 park 的任务永久卡死;`transport.url` 带凭据的存量条目照挂、url 逐字交引擎)—— 唯一的拒是 `transport` 里交不出去的成员(放行变宽的那一形,见本条后半)。写坏的 `name` · `transport` 的 `kind`/`command`/`url` · `allowTools` · `toolAxes` · `toolFaces` · `sessionMetaKey` ⇒ 这一条 server 整条不进,运维 warn `mcp_injection_dropped`(`reason: malformed_entry`,`field` 点名)并投用户面通告 `mcp.injection_dropped`(其中 `toolAxes` 在一次性迁移 M19 的窗开着时先按升级前读法重读一次,过了就挂,见上文 `toolAxes` 一节);写坏的 `contentOrigin` · `elicitation` · `source` 与词表外的键 ⇒ 丢掉那一个键、server 照挂;`transport` 的 `args` / `principalHeader` 写坏,或 `env` / `headers`(名 → 串记录)里有按 7.131.x 读法交不出去的成员(值不是串的成员 ⇒ 整键,`__proto__` 名的成员)⇒ 本部署兑现请求 MCP 时**拒续跑** `409 resume_blocked_by_policy`(点名成员坐标,见 9.5c-bis 同段;7.146.x 及以前剔掉照挂、一行 `mcp_transport_members_dropped`);条目不是对象 ⇒ 这一条不进(`malformed_entry`);超过 32 条 ⇒ 第 33 条起只记 `over_cap`;整只不是数组 ⇒ 整只忽略 + 运维 warn `mcp_injection_ignored`。
3087
3307
  - `POST /v1/capabilities/mcp/probe` 问的是新提交那**同一只判官**:上面每一形同码同句(含 `unknownKeys`,与 url 带凭据那一形的 `field` / `reason`),拒之前零拨号;判官受理的申报,探测口拨的就是一条任务提交会交给引擎的那一份(判官的合法归一照收)。它多出的两形:本口的体就是这份列表,`mcpServers` 缺席或为 `null` ⇒ `400 request.body_shape`(缺必需字段);声明了 `transport.assertionHeader` 的条目 ⇒ `400 request.field_invalid`(见 9.5d:本版承载、不发送,新提交照挂,探测不拨一份少了断言头的配置 —— 同一只谓词,后果按门分)。7.131.x 上本口另有一只判法(申报原文与读器产物逐字比),列表不是数组 / 条目不是对象答 `request.body_shape`。
3088
3308
  - **`transport.kind` 的词表可读(7.141.0 起)**:`GET /v1/capabilities` 的 `mcpTransportKinds`(`["stdio","http"]`,书写序,每种部署形都在场、不随部署形变)= 请求面读器与引擎今天能拨的传输种类。客户端在投影 `mcpServers` 条目之前据此判目标传输读不读得了(只拒那一台、其余照投),不必靠整次提交挨 400 去试。**位不是放行单**:位外的词(引擎的 `host`、别家的 `sse` / `sdk` / `websocket`)在新提交与探测口上照旧整次 400;本部署收不收自带的 `mcpServers` 读 `mcpInjection`,「端到端可达」没有任何位承诺。键缺席 = 老 server,读作 `["stdio","http"]`;位里出现不认识的词按「不支持该种类」处理。
3089
3309
  - **`toolFaces` 的判官是引擎的 `readToolFaces`(7.134.0 起;此前本服务自带一只只判 JSON 形的读器)**:它在门口就判声明本身能说的一切 —— 成员与类型、五张闭词表、上界、跨成员律 ——
@@ -3127,7 +3347,7 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3127
3347
  | `settings.webSearch.endpoint` | 同段的 `apiKey`(brave / tavily 发成请求头;searxng 腿不发鉴权头,要凭据的实例在部署面声明 `WEB_SEARCH_ENDPOINT`) |
3128
3348
  | `images[i].url`(`{ url }` 臂;7.136.0 起) | 没有封签的头位 —— 图片字节改走内联 `{ data, mimeType }`(7.136.0 起) |
3129
3349
 
3130
- - **判据**:URL 带 userinfo(`https://user:pass@host/…`,只有用户名也算),或带名字在凭据名表里的 query / fragment 参数(`?api_key=` · `?key=` · `?token=` · `#access_token=` …;表 = 本服务日志脱敏器认作凭据名的那一张,全表见契约附录 A `request.field_invalid` 行;名字大小写不敏感,解一次百分号编码读,整名相等)。
3350
+ - **判据**:URL 带 userinfo(`https://user:pass@host/…`,只有用户名也算),或带名字在凭据名表里的 query / fragment 参数(`?api_key=` · `?key=` · `?token=` · `#access_token=` …;表 = 本服务日志脱敏器认作凭据名的那一张,全表见契约附录 A `request.field_invalid` 行;名字大小写不敏感,解一次百分号编码读;整名在表上,或(7.147.0 起)末尾收在强档词上的复合名 —— `db_password` · `PGPASSWORD` · `AWS_SECRET_ACCESS_KEY` 中,只看末尾,`password_hash` / `tokens` 不中)。
3131
3351
  - **答案**:新提交(`POST /v1/tasks` · `/v1/tasks/stream` · `/v1/runs`)`400 request.field_invalid`,体另带 `field`(上表的体内全坐标)与 `reason`(`url_userinfo` | `url_credential_param`);句子点名坐标、命中的那一段与表里的词,**不回显 URL 的值**,并指出路。MCP 座另有探测口(`POST /v1/capabilities/mcp/probe`,同码同句同机读位)。
3132
3352
  - **A2A 座**并进 `a2aPeers` 的条目判官表(7.136.0 起,与 `mcpServers` 同一副拒绝骨架,见下):`url` / `cardUrl` 先判是 http(s) 串、再判凭据;`a2aPeers` 超过 32 条(挂载上限)整份 `400 request.field_invalid`(`field:"a2aPeers"` · `reason:"over_cap"` · `count` · `cap`),
3133
3353
  判官要么看过每一条对端的 `url` / `cardUrl`,要么这份体不收。7.134.x 及以前超过 32 条是 200、第 33 条起丢弃(运维 warn 点名条数)。**搜索后端座**:userinfo 形此前已是 400(句子逐字不变,本版起体另带 `field` / `reason`),凭据名参数是本版新拒的。
@@ -3177,6 +3397,53 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3177
3397
  仍会被座拒(`credential_unavailable` + 一行 `peer_seat_refused`,`reason: handle_not_held`)的窄形:被取消的父腿里一次工具调用起的会话级子代,在那条腿已放之后才取得持有(取消的孤儿窗);
3178
3398
  一条永不落定的准备腿永不放(条目随之常驻,`peer_registry_holders` 高于基线不回落即此形)。
3179
3399
 
3400
+ ### 9.5i-bis 部署声明的 A2A 对端:`config.d/a2a.json`(7.147.0 起)
3401
+
3402
+ 本地配置源(`CONFIG_PROVIDER=local`、`run-local`)从 `config.d/a2a.json` 读这台部署常驻的出站 A2A 对端,键与中心发布的 `a2a` 域同形(契约包 settings-schema 18.0.0 起 `a2a` 是便携域):两条腿过同一只读口、读成同一个声明,同一只解析器拨号。7.146.x 及以前本地腿不读这份文件,只有中心 `/effective.a2a` 能声明部署级对端。
3403
+
3404
+ ```json
3405
+ {
3406
+ "peers": [
3407
+ {
3408
+ "name": "planner",
3409
+ "url": "https://planner.example.com/a2a",
3410
+ "cardUrl": "https://planner.example.com/.well-known/agent-card.json",
3411
+ "headerRefs": { "Authorization": "PLANNER_A2A_TOKEN" },
3412
+ "allowSkills": ["plan", "review"]
3413
+ }
3414
+ ]
3415
+ }
3416
+ ```
3417
+
3418
+ - `name`(必填):对端的身份,线上工具名 `a2a__<name>__<skill>` 的命名空间段;非空、≤ 128 字符、首尾无空白,不截断、不 trim。两条对端重名 ⇒ 读口把整个 `a2a` 域判坏并点名(与其它域整域读坏同一处置)。
3419
+ - `url`(必填):对端服务地址,卡的 well-known 路径从它解析。带 userinfo(`https://u:p@peer/`)、带 fragment、或 query 值像密钥 ⇒ 这一条不进(读口点名,兄弟照拨);凭据走 `headerRefs`。
3420
+ - `cardUrl`(可缺席):对端卡不在 well-known 路径时写;写了就只用它。
3421
+ - `headerRefs`(可缺席):「头名 → 本服务环境变量名」。文件里只写变量名,值在起服时从本服务的环境读(与 `mcp.json` 的 `headerRefs` 同一种读法)。任一引用的变量缺席 ⇒ 这一条整条不拨、一行 warn `web_admin_a2a_env_missing`,兄弟照拨;记录里任一成员写坏(头名不是合法头名、值不是串)⇒ 这一条整条不进(读口点名),不会只带一半凭据拨号。
3422
+ - `principalHeader`(可缺席):把任务的终端用户主体注入到哪个头名(值由运行器填,模型读不到也改不了)。
3423
+ - `allowSkills`(可缺席):只挂对端卡上这几只技能。缺席与空表同读 = 挂卡上全部技能;不想拨这个对端,写 `enabled: false` 或删掉这一条。
3424
+ - `scenarios`(缺省 `[]` = 所有场景)· `enabled`(缺省 `true`)。
3425
+ - 不收 `toolAxes`:远端技能的风险轴只能由调用方在请求里声明(9.5h),部署面不替调用方担保;写了是词表外的键,读口点名。
3426
+ - **生效**:重启生效(与 `mcp.json` 同一条线)。文件改了 ⇒ `/health` 报 `restartRequired`、`restart.reasons` 含 `a2a`。重启信号只看真会改变拨号的写法:没写文件、`peers` 为空、只剩 `enabled: false` 的对端是同一个状态(这台部署没有部署级对端,不签重启);`cardUrl` / `principalHeader` 写空串、`allowSkills` / `headerRefs` 写空,与不写同读。
3427
+ - **从中心腿迁来**:中心发的 `a2a` 域照旧生效,现在也过同一只读口 —— 上面「这一条不进」的几种形,7.146.x 原样透传给引擎,7.147.0 起在读口点名丢掉;条目没写 `enabled` / `scenarios` 时读口补缺省,对拨号同义。
3428
+ - **已知限制:对端拨号面今天没有主机底线**:启用的对端按 `url` / `cardUrl` 取卡、按卡里写的服务端点发送,**不过任何部署主机底线** —— 本服务今天不给引擎接它(`RunnerDeps.hostFloor` 没有写点)。`url` / `cardUrl` 是运维写的,可指向任意主机(含内网与回环);服务端点是对端的卡写的:**不带头的对端**(这次发送不带任何声明的头:没写 `headerRefs`,也不注入主体头)的服务端点可指向任意主机,遇重定向照跟(引擎设跳数上限,没有主机底线);带头的对端另受引擎的端点绑定 —— 服务端点必须落在声明的 `url` / `cardUrl` 所在的源上,且不跟重定向。取卡发生在对端被用到时:每次任务准备、会话 A2A 面板(`GET /v1/sessions/:id/a2a`)缓存缺失 / 过期(5 秒)时;起服本身不取卡。**多租户部署同样挂**:多租户闸只关请求体自带的 `a2aPeers`(9.5h),部署基线照挂。
3429
+ **补偿**:只写你信任的对端 —— 不带头的对端,它的卡能让引擎去敲它写下的任何地址;起服前自审 `config.d/a2a.json`;多租户 / 公共部署不要接不受信对端。出路 = 部署主机底线接线(尚未到货),到货后本条改写。
3430
+
3431
+ ### 9.5j 请求的 `settings.env` 不收凭据(7.146.0 起,新提交 400)
3432
+
3433
+ **律**:凭据进引擎只经引擎进程启动时的环境,从不经请求。`settings.env` 来自一份模型读得到的设置文件,而本服务把它交给会话的 shell(单用户 host / device 车道原样叠进模型 shell 的环境 ——
3434
+ 调用方显式授予的变量不按本服务的自有凭据表扣)与 settings hooks(命令钩子的环境、http 钩子的 `$NAME` 插值),所以这一块不许有凭据。
3435
+ - **判据一只**,读会交到子进程的那几条(非串值、超帽的键 / 值本来就到不了子进程):名字是本引擎的凭据变量 —— 配置目录(`GET /v1/config/catalog`)里 `secret` 型的全部行
3436
+ (`MODEL_API_KEY` / `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` / `SERVICE_AUTH_TOKEN(S)` …)∪ 生效配置每条模型条目的 `apiKeyEnv`,比名不分大小写(这一层落到 host 与 device 车道,Windows 上 `model_api_key` 就是同一个变量)——
3437
+ **或**值是秘密形(契约包 settings-schema 的 `containsSecretToken`:`sk-…`、`ghp_…`、`AKIA…`、`Bearer …`、JWT、私钥头 …;worker `env` 与各自由文本写口同一只扫描器)。
3438
+ - **命中** ⇒ `400 request.env_credential_refused`,体带 `field: "settings.env"` 与 `entries: [{ key, match }]`(按体内序列出每一条;`match` = `"name"` / `"value_shape"`,两者都中报 `"name"`),**值从不回显**;
3439
+ 不剥、不降级(剥掉 = 调用方以为交出去的东西静默没到)。不分车道:`settings.env` 被忽略的多租户 / 云车道同拒(凭据同样不该随请求到引擎)。三处同一只判据:提交受理门、装配(嵌入方直调同拒)、停驻任务续跑前的预判。
3440
+ - **出路**:把凭据挪进引擎进程的环境 —— 壳起的本机引擎由壳的凭据文件带入(还把凭据写进 `settings.json` 的老壳先升级),服务形部署放进进程环境 / 编排器的 secret 注入。
3441
+ 要让模型 shell 持一枚令牌(`GH_TOKEN` 一类):放在引擎进程环境里一个不在配置目录、也不是任何模型条目 `apiKeyEnv` 的名下,配请求级 `inheritEnv:"all"`(单用户 host 车道)。
3442
+ 值形扫描是粗判:一串不是凭据、却被判成秘密形的值同样要挪出 `settings.env`(改名不救值形)。
3443
+ - **已知限制**:http 钩子 header 的 `$NAME` 插值只读钩子环境(十二名基础变量 + `settings.env`,9.5c)—— 秘密形的 webhook token 今天在本服务的钩子上**没有替代通道**;不像秘密的 token 照旧经 `settings.env` 给。
3444
+ - **续跑**:升级前受理、停驻的任务,存体里带这类条目时,一次性迁移 M38 的窗(本版发布日 + 30 天;窗口见登记表)内续跑把它们从 shell / hooks 的环境里剥掉、任务照续
3445
+ (运维 warn `settings_env_credentials_dropped`:`migration` / `sessionId` / `dropped: { name, value_shape }` —— 只计数,不带键名与值);窗关后续跑口答 409 `resume_blocked_by_policy`(`blockedBy: "request.env_credential_refused"`,同一组机读位),出路是取消那条 run 后挪走凭据重提。
3446
+
3180
3447
  ### 9.6 trace 脱敏的射程:与出站请求体无关
3181
3448
 
3182
3449
  🔴 **脱敏 ≠ 出站控制**:trace 脱敏(账本 / SSE / turns / 审批预览里的 `«redacted»` 标记)只作用于
@@ -3203,11 +3470,13 @@ WebSearch 工具的 `tool_result` 错误文本,回到模型的对话里,**不是
3203
3470
 
3204
3471
  **日志不是原形面**:运维结构化日志里 `engine_notice` 行的 `message` 与会话流上同一条通知的 `message` 过同一只脱敏(认出凭据形才换成 `«redacted…»`,没有凭据形的句子逐字节原样;会话流行另有 1000 字上限,日志行不截),`detail` 里的规则文本(`permission.allowed_tool_not_granted` 的 `entries[].entry`、`permission.rule_row_dropped` 的 `rows[].rule`)是条目的脱敏原值、`detail` 其余字段逐字 —— 同一条规则文本在 `message` 与 `detail` 里的脱敏是同一只脱敏器;已知边界:引擎把条目里的零宽字符写成 `\u200b` 转义,夹零宽字符的凭据在 `message` 里(日志行与会话流行同)认不出,机读位的规则文本是在原串上认的、不受此限。
3205
3472
 
3206
- **自由文本的强锚读法**(7.143.0 起;此前下面 ① ② 两形漏遮,见 MIGRATION「自由文本脱敏的名字面」):① 复合凭据名(`api_key` / `apikey` / `api-key` / `access_token` / `refresh_token` / `id_token` / `private_key` / `x-amz-signature` / `x-amz-security-token` / `x-amz-credential`)与 `password` / `secret` / `client_secret` 同档 ——在自由文本 `名=值` / `名: 值` 的**声明形**(名字带引号、值带引号,或赋值符两侧零空白)上是名字锚,值是以字母数字起头的字面量时遮(`$VAR` / `%s` / `<占位>` / 表达式 / `None` 一类不算值;`passwd` / `pwd` 任何赋值形都是锚);赋值符两侧带空白的不带引号形(`api_key = abc`)不是声明形、不算锚。`token` / `key` / `auth` / `sig` / `signature` / `sas` 在自由文本里是常用词,只在 JSON 字段与 URL 参数位上是名字锚,自由文本里只有值自身形状被别的臂认出(`ghp_…` / `sk-…` / `AKIA…` 这类)时才遮。代价:关键字参数透传(`f(api_key=api_key)`)的变量名、源码里对象字面量的值(`apiKey: "…"`,名字大小写不敏感)也换成记号;这些名字后面本来由值自身形状认出的值(`API_KEY=sk-…`)记号是强锚值的 `«redacted»`,不再是 `«redacted:<种类>»`(与 `password=` 同读)。
3473
+ **自由文本的强锚读法**(7.143.0 起;此前下面 ① ② 两形漏遮,见 MIGRATION「自由文本脱敏的名字面」):① 复合凭据名(`api_key` / `apikey` / `api-key` / `access_token` / `refresh_token` / `id_token` / `private_key` / `x-amz-signature` / `x-amz-security-token` / `x-amz-credential`)与 `password` / `secret` / `client_secret` 同档 ——在自由文本 `名=值` / `名: 值` 的**声明形**(名字带引号、值带引号、赋值符两侧零空白,或 7.146.0 起的**行首键形**:YAML / INI / compose 的配置行 —— 名字在行首〔可带缩进与 `- ` 列表项记号〕、赋值符右边至少一个空白、不带引号的值是那一行余下的全部〔之后只许行尾或 ` #` 注释〕)上是名字锚,值是以字母数字起头的字面量时遮(`$VAR` / `%s` / `<占位>` / 表达式 / `None` 一类不算值;行首键形上值是类型名〔`str` / `string` / `SecretStr` …〕也不算值;`passwd` / `pwd` 任何赋值形都是锚);行中赋值符两侧带空白的不带引号形(`… api_key = abc`)与值之后行上还有东西的行(`password: str = ""` / ` password: pw,`)不是声明形、不算锚。`token` 只在**赋值那一半的零空白形**上是名字锚(`f(token=tok1)` / `curl -d 'token=…'` / `TOKEN=…`:`=` 两侧零空白、名字不带引号;值文法同上),冒号形(`token: …` / `{token:12345}`)与带空白的赋值(`token = …`)里它是常用词;`key` / `auth` / `sig` / `signature` / `sas` 在自由文本里是常用词,只在 JSON 字段与 URL 参数位上是名字锚,自由文本里只有值自身形状被别的臂认出(`ghp_…` / `sk-…` / `AKIA…` 这类)时才遮。代价:关键字参数透传(`f(api_key=api_key)` / `requests.get(url, token=token)`)的变量名、源码里对象字面量的值(`apiKey: "…"`,名字大小写不敏感)、行首键形同形的文书示例与代码行(`password: xxx` / 对象字面末项 ` password: pw` / `password = user_input` / `Password: required`)也换成记号;这些名字后面本来由值自身形状认出的值(`API_KEY=sk-…`)记号是强锚值的 `«redacted»`,不再是 `«redacted:<种类>»`(与 `password=` 同读)。
3207
3474
  ② 名字的左界止于前一枚已认出的凭据(或可读的结构块)的终点:强锚名紧贴在前一枚凭据的尾巴上(`-----END PRIVATE KEY-----password=…`;PGP 收尾栅栏、PuTTY 的 `Private-MAC:` 行、AWS 键 id 同形,中间无分隔)时**首遍即遮**,再过一遍读数不变 ——所以各出口同读,不看出口过几遍脱敏:流式 `done` 帧、同步体、运维日志行、活流 `text_delta` / `reasoning_delta` 逐块帧与 `text_end` / `reasoning_end`(活流与 `done` 帧同一只脱敏器、同一遍,逐块拼接 = 段末帧,契约 §4a-nov-bis)、`GET /v1/runs/:id` 的 run 行、`GET /v1/runs/:id/events` 回放里的 `text` / `reasoning` / `done` / `tool_end` 行。
3208
3475
  ③ 不带引号的值里面的赋值照问:`api_key=abc,password="my secret value"` 里两个值都遮(此前外层不带引号的值把里面的赋值藏住,里面那一只不认)。
3209
3476
  **升级前已落库的行**:回放读面对 run 行与 `text` / `reasoning` / `done` 行再过一遍脱敏 ⇒ 按本读法遮;`tool_end` 行回放读面不再过(只剥媒体字节)⇒ 升级前写下的 ① ② 两形照旧明文,升级后新写的行照遮。
3210
- **仍不遮的一形**:名字整个落在前一枚已认出凭据的值里 —— 以字母数字收尾的令牌后面紧贴强锚名(`ghp_…password=<值>`,中间无分隔;令牌的形状把名字吞了进去)—— 令牌照遮、`<值>` 明文,各出口同读;中间有分隔(空白 / `,` / `;` / 换行)照遮。名字按整名认:前面与字母数字以 `_` / `-` 连写的(`DB_PASSWORD=` / `foo-api_key=`)是另一个名字、不算锚;只有收尾栅栏、没有起始栅栏的 `…KEY-----password=` 同理(孤立的收尾栅栏不是凭据,不立名界)。要靠脱敏挡住的凭据别以这一形写出(MIGRATION「自由文本脱敏的名字面」)。
3477
+ **名字判定读契约**(7.147.0 起):「是不是凭据名」整段交契约包的名字判官 —— 整名在凭据名表上,或末段 / 尾串收在强档词上:以 `password` / `passwd` / `pwd` / `secret` / `apikey` 收尾(整段或与前缀粘连)的复合名(`DB_PASSWORD=` / `PGPASSWORD=` / `dbPassword=` / `--db-password=` / compose 的 `POSTGRES_PASSWORD: <值>`)与三条尾串(`SECRET_KEY` / `AWS_SECRET_ACCESS_KEY` 读作 `secret`;`x-api-key` / `OPENAI_API_KEY` 读作 `api_key`;`github_access_token` 读作 `access_token`)都是凭据名,锚强度按它读作的那个表词;只看末尾(`password_hash=` / `secretName:` / `pwd_dir=` / `api_key_count=` / `token_count=` 不是)。名字长度不封顶。四个读者同读:自由文本(属性路径只把最后一段交判官:`spring.datasource.password=`)、JSON 键与 URL 参数名(整段交判官;URL 参数名按收方的解析读:`+` 是空格、百分号解一次 —— `?db.password=` / `?x+password=` 同遮)、诊断面。受理口的 URL 凭据判官读同一只判官,两边对同一个名字同答。所以此前「名字整个落在前一枚已认出凭据的值里」那一形(`ghp_…password=<值>`,令牌的形状把名字吞了进去)也遮:名字是整段,收在强档词上。代价:以强档词收尾、值不是凭据的名字(`show_password=1` / `is_secret=true`)的值也换成记号;单行出门的运维日志里,行尾凭据值之后被剥掉换行而拼上来的下一行首词随值一起遮。**仍不遮的一形**:引号里以 `$` 起头的值(`DB_PASSWORD='$ecret12'`)读作插值,不是字面量 —— 要靠脱敏挡住的凭据别以这一形写出。
3478
+
3479
+ **错误文本的出口契约**(归一化视图 / 遮蔽记号闭形)写在源码仓内的助手 wire 契约 `ASSISTANT-WIRE-CONTRACT.md` 里(不随 npm 包出)。
3211
3480
 
3212
3481
  ### 9.7 bake-runner(构建宿主守护进程)的 env(7.103.0 起成文)
3213
3482
 
@@ -3707,6 +3976,12 @@ UPDATE session_policy_rev_mark SET rev = 0, gen = gen + 1 WHERE policy_key = '<w
3707
3976
  - **路径 2 · npm 包**(只有 npm 访问权;包是公开的,匿名可装):`npm install @sema-agent/server@<版本>`。基镜像 `node:24-slim`(包的 `engines` 是 `^20.19.0 || >=22.12.0`);
3708
3977
  入口 `node node_modules/@sema-agent/server/dist/main.js`。包的 `bin` 里没有 HTTP 服务(`run-local` 是单机一次性 runner、`sema-up` 是部署引导脚本),要起服务必须直接跑这个 `dist/main.js`。
3709
3978
 
3979
+ **随包部署事实(7.146.0 起)**:包里的 `deploy/facts/` 是引擎自己的部署要求的机器可读形,给安装器(chart / 清单 / 安装脚本)读 —— `lanes.json`(每条执行车道的租户档、隔离位、必需键;行集 = 所依 settings-schema 的全部车道词)、
3980
+ `sandbox-rbac.json`(k8s 沙箱服务账号的 RBAC 规则:何时授、引擎真调还是兼容保留)、`sealed-key.json`(钥匙保管目录的安装契约:显式绝对 `SEALED_KEY_DIR`,整个目录跨重启持久、各副本共享、引擎可写),
3981
+ 各配一份同包 `*.schema.json`,另有形变登记 `CHANGES.json`。取用 = npm 子路径 `@sema-agent/server/deploy/facts/<文件>`(子路径 = 包内文件路径:`require.resolve` 得到它,tarball 里是 `package/deploy/facts/<文件>`);
3982
+ 读方用同包 Schema 校验、不认识的 `schemaVersion` 整份拒读、同一版本内的未知键忽略。加性形变只有「新增可选属性」一种,其余形变都抬 `schemaVersion` 并登记;规则与本版还没覆盖的要求见包内 `deploy/facts/README.md`。
3983
+ **硬断预告(安装器退役)**:server 包里的 `deploy/sema-up/` 安装器、`bin.sema-up`(`npx -p @sema-agent/server sema-up`)与 npm 子路径 `./deploy/sema-up/lanes.json` 将在 sema-deploy 的安装器(读本包 `deploy/facts/`)与 cli 都已发出、且对绑定好的这组发行物跑完接管验收之后的下一个 server minor 退役(最早 7.148.0;按触发、不按日历;删除,零别名、零转发)。现在就改读:车道、沙箱 RBAC、钥匙保管目录的事实读 npm 子路径 `@sema-agent/server/deploy/facts/<文件>`,安装器取 sema-deploy 的发行物。7.146.0 里旧入口一字未变。
3984
+
3710
3985
  **构建(二选一)**
3711
3986
 
3712
3987
  路径 1: