foxclaw 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/index.js +1 -8
  2. package/package.json +1 -1
  3. package/python/.env.example +8 -0
  4. package/python/.github/workflows/ci.yml +47 -2
  5. package/python/CHANGELOG.md +57 -0
  6. package/python/CHANGELOG_CN.md +57 -0
  7. package/python/README.md +11 -2
  8. package/python/README_CN.md +32 -20
  9. package/python/bench/bm25_audit.py +1 -14
  10. package/python/bench/bm25_regression.py +0 -1
  11. package/python/bench/bm25_stress.py +0 -4
  12. package/python/bench/cd_audit.py +0 -23
  13. package/python/bench/chaos.py +6 -8
  14. package/python/bench/e2e_real_models.py +0 -1
  15. package/python/bench/llm_retry_test.py +2 -9
  16. package/python/bench/load_test.py +5 -8
  17. package/python/bench/vector_ivf_upgrade.py +1 -7
  18. package/python/docs/RELEASE.md +4 -3
  19. package/python/docs/RUN_CN.md +2 -2
  20. package/python/evals/governance/cases.py +0 -37
  21. package/python/evals/governance/run_evals.py +5 -37
  22. package/python/examples/governance_middleware_demo.py +1 -6
  23. package/python/examples/langgraph_governance_demo.py +7 -11
  24. package/python/foxclaw/__init__.py +34 -152
  25. package/python/foxclaw/agents/base.py +65 -136
  26. package/python/foxclaw/agents/impl/_helpers.py +2 -10
  27. package/python/foxclaw/agents/impl/finance.py +4 -21
  28. package/python/foxclaw/agents/impl/office.py +7 -24
  29. package/python/foxclaw/agents/impl/supervisor.py +12 -79
  30. package/python/foxclaw/agents/mixins/_context.py +0 -23
  31. package/python/foxclaw/agents/mixins/augment.py +10 -16
  32. package/python/foxclaw/agents/mixins/llm.py +7 -6
  33. package/python/foxclaw/agents/mixins/memory.py +7 -1
  34. package/python/foxclaw/agents/mixins/preference.py +58 -8
  35. package/python/foxclaw/agents/mixins/prompt.py +0 -6
  36. package/python/foxclaw/agents/mixins/skillbox.py +6 -6
  37. package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
  38. package/python/foxclaw/api/__init__.py +18 -77
  39. package/python/foxclaw/api/_state.py +43 -0
  40. package/python/foxclaw/api/{app.py → bootstrap.py} +5 -32
  41. package/python/foxclaw/api/middleware.py +5 -28
  42. package/python/foxclaw/api/routes/ahs.py +60 -73
  43. package/python/foxclaw/api/routes/attachments.py +5 -6
  44. package/python/foxclaw/api/routes/browser.py +7 -11
  45. package/python/foxclaw/api/routes/domain.py +18 -26
  46. package/python/foxclaw/api/routes/knowledge.py +3 -21
  47. package/python/foxclaw/api/routes/llm_admin.py +21 -33
  48. package/python/foxclaw/api/routes/memory.py +28 -12
  49. package/python/foxclaw/api/routes/orchestration.py +3 -5
  50. package/python/foxclaw/api/routes/recovery.py +4 -3
  51. package/python/foxclaw/api/routes/review.py +2 -11
  52. package/python/foxclaw/api/routes/search.py +3 -7
  53. package/python/foxclaw/api/routes/system.py +33 -49
  54. package/python/foxclaw/api/routes/task.py +31 -49
  55. package/python/foxclaw/api/routes/ws.py +2 -6
  56. package/python/foxclaw/api/spine.py +1 -4
  57. package/python/foxclaw/browser/action/action_executor.py +1 -5
  58. package/python/foxclaw/browser/action/action_types.py +0 -1
  59. package/python/foxclaw/browser/action/url_policy.py +1 -5
  60. package/python/foxclaw/browser/adapter/web_adapter.py +1 -1
  61. package/python/foxclaw/browser/driver/playwright_driver.py +20 -16
  62. package/python/foxclaw/browser/perception/dom_extractor.py +0 -1
  63. package/python/foxclaw/browser/perception/element_indexer.py +1 -2
  64. package/python/foxclaw/browser/service.py +7 -11
  65. package/python/foxclaw/capability/embedding_service.py +4 -6
  66. package/python/foxclaw/channels/__init__.py +9 -11
  67. package/python/foxclaw/channels/discord.py +4 -13
  68. package/python/foxclaw/channels/dispatcher.py +2 -27
  69. package/python/foxclaw/channels/feishu.py +10 -20
  70. package/python/foxclaw/channels/openclaw_bridge.py +4 -9
  71. package/python/foxclaw/channels/signing.py +9 -9
  72. package/python/foxclaw/channels/telegram.py +3 -4
  73. package/python/foxclaw/channels/weixin.py +7 -17
  74. package/python/foxclaw/cli.py +42 -13
  75. package/python/foxclaw/cognitive/reasoner.py +9 -10
  76. package/python/foxclaw/cognitive/state.py +18 -24
  77. package/python/foxclaw/core/audit_chain.py +1 -3
  78. package/python/foxclaw/core/audit_sink.py +3 -3
  79. package/python/foxclaw/core/compaction.py +8 -26
  80. package/python/foxclaw/core/config.py +2 -2
  81. package/python/foxclaw/core/confirm_nonce.py +6 -12
  82. package/python/foxclaw/core/envfile.py +2 -2
  83. package/python/foxclaw/core/failure_policy.py +17 -26
  84. package/python/foxclaw/core/gateway.py +6 -10
  85. package/python/foxclaw/core/governance_audit.py +3 -7
  86. package/python/foxclaw/core/idempotency.py +15 -22
  87. package/python/foxclaw/core/injection_guard.py +10 -29
  88. package/python/foxclaw/core/memory_adapter.py +30 -57
  89. package/python/foxclaw/core/metrics.py +4 -5
  90. package/python/foxclaw/core/model_router.py +36 -148
  91. package/python/foxclaw/core/paths.py +1 -1
  92. package/python/foxclaw/core/recovery.py +21 -34
  93. package/python/foxclaw/core/scheduler.py +23 -26
  94. package/python/foxclaw/core/secrets.py +0 -3
  95. package/python/foxclaw/core/sessions.py +4 -6
  96. package/python/foxclaw/core/settings.py +16 -32
  97. package/python/foxclaw/core/shared_write.py +11 -23
  98. package/python/foxclaw/core/steering_queue.py +12 -11
  99. package/python/foxclaw/core/system_bus.py +1 -1
  100. package/python/foxclaw/core/thought_chain.py +30 -32
  101. package/python/foxclaw/core/token_meter.py +9 -13
  102. package/python/foxclaw/core/tools.py +4 -12
  103. package/python/foxclaw/core/tracing.py +25 -40
  104. package/python/foxclaw/core/truth_pipeline.py +5 -9
  105. package/python/foxclaw/core/update_check.py +149 -0
  106. package/python/foxclaw/core/vec_recall_adapter.py +11 -37
  107. package/python/foxclaw/core/verify.py +6 -14
  108. package/python/foxclaw/core/web_search.py +4 -28
  109. package/python/foxclaw/finance/evidence.py +14 -18
  110. package/python/foxclaw/finance/fx.py +1 -3
  111. package/python/foxclaw/finance/graph.py +9 -18
  112. package/python/foxclaw/finance/models.py +20 -24
  113. package/python/foxclaw/finance/nlparse.py +12 -72
  114. package/python/foxclaw/finance/policy.py +8 -36
  115. package/python/foxclaw/finance/reasoner.py +3 -11
  116. package/python/foxclaw/finance/reasoning.py +13 -34
  117. package/python/foxclaw/finance/reconcile.py +5 -10
  118. package/python/foxclaw/finance/runtime.py +19 -46
  119. package/python/foxclaw/governance/middleware.py +13 -21
  120. package/python/foxclaw/harness/_base.py +2 -8
  121. package/python/foxclaw/harness/_policy.py +19 -38
  122. package/python/foxclaw/harness/drift.py +1 -13
  123. package/python/foxclaw/harness/event_bus.py +2 -5
  124. package/python/foxclaw/harness/harness.py +9 -97
  125. package/python/foxclaw/harness/loop_detection.py +11 -18
  126. package/python/foxclaw/itinerary/amap_client.py +2 -5
  127. package/python/foxclaw/itinerary/models.py +7 -7
  128. package/python/foxclaw/itinerary/reasoner.py +1 -6
  129. package/python/foxclaw/itinerary/reasoning.py +15 -21
  130. package/python/foxclaw/itinerary/runtime.py +8 -12
  131. package/python/foxclaw/kernel/capabilities.py +5 -6
  132. package/python/foxclaw/kernel/policy_gate.py +4 -10
  133. package/python/foxclaw/kernel/process_manager.py +17 -36
  134. package/python/foxclaw/kernel/scheduler.py +3 -9
  135. package/python/foxclaw/kernel/sentinel.py +13 -23
  136. package/python/foxclaw/kernel/sentinel_client.py +12 -15
  137. package/python/foxclaw/kernel/spine.py +9 -9
  138. package/python/foxclaw/knowledge/__init__.py +4 -20
  139. package/python/foxclaw/knowledge/truth_crystallizer.py +5 -8
  140. package/python/foxclaw/learning/knowledge_graph.py +4 -9
  141. package/python/foxclaw/learning/models.py +13 -13
  142. package/python/foxclaw/learning/reasoner.py +1 -4
  143. package/python/foxclaw/learning/runtime.py +19 -31
  144. package/python/foxclaw/learning/spaced_repetition.py +2 -7
  145. package/python/foxclaw/mcp_server.py +2 -3
  146. package/python/foxclaw/orchestration/__init__.py +16 -33
  147. package/python/foxclaw/pipeline/cognitive_pipeline.py +45 -97
  148. package/python/foxclaw/pipeline/stage_meta.py +21 -0
  149. package/python/foxclaw/pipeline/supervisor_stages.py +13 -42
  150. package/python/foxclaw/planning/llm_planner.py +3 -3
  151. package/python/foxclaw/planning/planner.py +6 -11
  152. package/python/foxclaw/search/agent_port.py +7 -11
  153. package/python/foxclaw/search/evidence.py +0 -4
  154. package/python/foxclaw/search/internal_search.py +3 -5
  155. package/python/foxclaw/search/models.py +11 -13
  156. package/python/foxclaw/search/planner.py +3 -3
  157. package/python/foxclaw/search/retriever.py +1 -1
  158. package/python/foxclaw/search/runtime.py +1 -8
  159. package/python/foxclaw/skills/__init__.py +46 -100
  160. package/python/foxclaw/skills/archive.py +111 -0
  161. package/python/foxclaw/skills/fetch.py +226 -0
  162. package/python/foxclaw/skills/hooks_spec.py +21 -25
  163. package/python/foxclaw/skills/install_cmd.py +216 -0
  164. package/python/foxclaw/skills/lifecycle.py +16 -42
  165. package/python/foxclaw/skills/markdown_skill.py +10 -9
  166. package/python/foxclaw/skills/mcp_client.py +5 -11
  167. package/python/foxclaw/skills/mcp_store.py +0 -3
  168. package/python/foxclaw/skills/permission.py +13 -48
  169. package/python/foxclaw/skills/pipeline.py +21 -53
  170. package/python/foxclaw/skills/policy.py +4 -10
  171. package/python/foxclaw/skills/review.py +3 -3
  172. package/python/frontend/deck/auth.js +0 -7
  173. package/python/frontend/deck/chat_model_picker.js +0 -54
  174. package/python/frontend/deck/domain.js +0 -20
  175. package/python/frontend/deck/main.js +0 -144
  176. package/python/frontend/deck/markdown.js +0 -4
  177. package/python/frontend/deck/settings.js +1 -18
  178. package/python/frontend/deck/state.js +0 -1
  179. package/python/frontend/deck/util.js +0 -2
  180. package/python/frontend/deck/visuals.js +0 -22
  181. package/python/frontend/planet/planet.js +0 -201
  182. package/python/frontend/v3/assets/index-CQSBdxk4.js +34 -0
  183. package/python/frontend/v3/assets/index-D5nIsjms.css +1 -0
  184. package/python/frontend/v3/index.html +3 -3
  185. package/python/frontend/widget_runtime.js +0 -43
  186. package/python/frontend/widgets.js +0 -57
  187. package/python/frontend_src/index.html +1 -1
  188. package/python/frontend_src/package-lock.json +2 -2
  189. package/python/frontend_src/package.json +1 -1
  190. package/python/frontend_src/src/App.vue +3 -7
  191. package/python/frontend_src/src/api.js +0 -7
  192. package/python/frontend_src/src/components/ChatDock.vue +27 -39
  193. package/python/frontend_src/src/components/DocViewer.vue +0 -9
  194. package/python/frontend_src/src/components/Inspector.vue +0 -6
  195. package/python/frontend_src/src/components/LeftRail.vue +60 -39
  196. package/python/frontend_src/src/components/MenuPopover.vue +9 -11
  197. package/python/frontend_src/src/components/MessageStream.vue +18 -30
  198. package/python/frontend_src/src/components/ModelPicker.vue +0 -5
  199. package/python/frontend_src/src/components/QuickIngest.vue +0 -6
  200. package/python/frontend_src/src/components/Stage.vue +71 -25
  201. package/python/frontend_src/src/components/ToolMenu.vue +0 -3
  202. package/python/frontend_src/src/components/TopBar.vue +38 -10
  203. package/python/frontend_src/src/components/panels/ActivityPanel.vue +0 -2
  204. package/python/frontend_src/src/components/panels/AgentsPanel.vue +2 -5
  205. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +0 -16
  206. package/python/frontend_src/src/components/panels/MemoryPanel.vue +0 -1
  207. package/python/frontend_src/src/components/panels/PanelBox.vue +6 -7
  208. package/python/frontend_src/src/components/panels/ReviewPanel.vue +0 -16
  209. package/python/frontend_src/src/components/panels/SettingsPanel.vue +142 -38
  210. package/python/frontend_src/src/components/panels/TasksPanel.vue +0 -1
  211. package/python/frontend_src/src/components/panels/ToolsPanel.vue +125 -19
  212. package/python/frontend_src/src/lib/fmt.js +0 -4
  213. package/python/frontend_src/src/lib/i18n.js +53 -0
  214. package/python/frontend_src/src/lib/locales/en.js +173 -0
  215. package/python/frontend_src/src/lib/locales/zh.js +174 -0
  216. package/python/frontend_src/src/lib/md.js +0 -14
  217. package/python/frontend_src/src/main.js +2 -0
  218. package/python/frontend_src/src/stores/app.js +17 -32
  219. package/python/frontend_src/src/stores/chat.js +0 -34
  220. package/python/frontend_src/src/stores/model.js +0 -8
  221. package/python/frontend_src/src/stores/panels.js +0 -41
  222. package/python/frontend_src/src/stores/profile.js +31 -0
  223. package/python/frontend_src/src/stores/system.js +25 -0
  224. package/python/frontend_src/vite.config.js +0 -7
  225. package/python/pyproject.toml +6 -3
  226. package/python/requirements.txt +311 -0
  227. package/python/tests/conftest.py +0 -22
  228. package/python/tests/run_all.py +3 -66
  229. package/python/tests/test_agent_lifecycle.py +4 -38
  230. package/python/tests/test_ahs_p2.py +4 -25
  231. package/python/tests/test_anthropic_tools.py +1 -15
  232. package/python/tests/test_api.py +19 -9
  233. package/python/tests/test_api_auth.py +1 -5
  234. package/python/tests/test_attachments.py +9 -25
  235. package/python/tests/test_audit_chain.py +2 -7
  236. package/python/tests/test_audit_sink.py +0 -6
  237. package/python/tests/test_browser.py +2 -6
  238. package/python/tests/test_browser_request_guard.py +0 -4
  239. package/python/tests/test_capabilities.py +0 -6
  240. package/python/tests/test_channel_signing.py +2 -9
  241. package/python/tests/test_channels.py +1 -11
  242. package/python/tests/test_config_layering.py +0 -4
  243. package/python/tests/test_confirm_nonce.py +1 -12
  244. package/python/tests/test_context_compaction_wiring.py +1 -7
  245. package/python/tests/test_dispatch.py +0 -6
  246. package/python/tests/test_domain_evals.py +11 -80
  247. package/python/tests/test_domain_import.py +0 -6
  248. package/python/tests/test_domain_reasoners.py +0 -14
  249. package/python/tests/test_e2e.py +0 -17
  250. package/python/tests/test_emb_cache.py +7 -17
  251. package/python/tests/test_embedding_singleton.py +1 -3
  252. package/python/tests/test_evals_smoke.py +0 -24
  253. package/python/tests/test_event_spine.py +10 -18
  254. package/python/tests/test_evidence_semantic.py +1 -14
  255. package/python/tests/test_evidence_sse.py +3 -6
  256. package/python/tests/test_file_ingest.py +0 -11
  257. package/python/tests/test_finance.py +0 -9
  258. package/python/tests/test_finance_ledger.py +5 -12
  259. package/python/tests/test_finance_persist.py +8 -12
  260. package/python/tests/test_gateway.py +3 -22
  261. package/python/tests/test_governance.py +5 -52
  262. package/python/tests/test_governance_audit.py +4 -39
  263. package/python/tests/test_governance_middleware.py +1 -7
  264. package/python/tests/test_hkc.py +6 -24
  265. package/python/tests/test_hkc_index_build_manager.py +0 -33
  266. package/python/tests/test_hkc_manifest_reuse.py +4 -27
  267. package/python/tests/test_hkc_search_event_sync.py +3 -54
  268. package/python/tests/test_import_path_guard.py +0 -6
  269. package/python/tests/test_injection_guard.py +18 -36
  270. package/python/tests/test_integration.py +5 -29
  271. package/python/tests/test_itinerary.py +0 -3
  272. package/python/tests/test_kee_semantic.py +0 -5
  273. package/python/tests/test_kernel.py +1 -11
  274. package/python/tests/test_learning.py +3 -12
  275. package/python/tests/test_llm_admin.py +4 -19
  276. package/python/tests/test_llm_planner.py +1 -23
  277. package/python/tests/test_markdown_skills.py +3 -14
  278. package/python/tests/test_markdown_skills_openclaw.py +4 -9
  279. package/python/tests/test_mcp_connectors.py +3 -15
  280. package/python/tests/test_mcp_guardian_discovery.py +2 -6
  281. package/python/tests/test_mcp_no_memory.py +3 -3
  282. package/python/tests/test_mcp_policy_gate.py +0 -1
  283. package/python/tests/test_mcp_server.py +6 -9
  284. package/python/tests/test_memory_recall.py +1 -4
  285. package/python/tests/test_metrics.py +0 -8
  286. package/python/tests/test_multi_chain.py +1 -13
  287. package/python/tests/test_multimodal.py +5 -18
  288. package/python/tests/test_negotiation.py +3 -13
  289. package/python/tests/test_orchestration.py +1 -11
  290. package/python/tests/test_orchestration_decoupled.py +0 -6
  291. package/python/tests/test_p3_p5_hardening.py +3 -17
  292. package/python/tests/test_parallel_dispatch.py +4 -12
  293. package/python/tests/test_pipeline.py +17 -58
  294. package/python/tests/test_plan_visible.py +4 -4
  295. package/python/tests/test_planner.py +0 -7
  296. package/python/tests/test_policy_gate.py +2 -3
  297. package/python/tests/test_profile.py +67 -0
  298. package/python/tests/test_readme_claims.py +163 -0
  299. package/python/tests/test_reasoner.py +2 -17
  300. package/python/tests/test_release_check.py +2 -13
  301. package/python/tests/test_resume_checkpoint.py +7 -15
  302. package/python/tests/test_routing.py +2 -12
  303. package/python/tests/test_scheduler.py +7 -10
  304. package/python/tests/test_search.py +1 -11
  305. package/python/tests/test_secrets.py +4 -10
  306. package/python/tests/test_semantic.py +1 -10
  307. package/python/tests/test_sessions.py +3 -4
  308. package/python/tests/test_settings.py +2 -18
  309. package/python/tests/test_shadow_learning.py +3 -43
  310. package/python/tests/test_shared_write.py +0 -7
  311. package/python/tests/test_skill_taint_gate.py +1 -10
  312. package/python/tests/test_skills.py +2 -18
  313. package/python/tests/test_skills_fetch.py +282 -0
  314. package/python/tests/test_stats_and_events.py +2 -5
  315. package/python/tests/test_streaming.py +2 -7
  316. package/python/tests/test_supervisor_pipeline.py +4 -5
  317. package/python/tests/test_supervisor_triage.py +4 -10
  318. package/python/tests/test_taint.py +2 -7
  319. package/python/tests/test_task_stream.py +1 -12
  320. package/python/tests/test_thinking_sse.py +0 -1
  321. package/python/tests/test_tool_center.py +1 -17
  322. package/python/tests/test_tool_parallel.py +2 -2
  323. package/python/tests/test_tool_params.py +1 -5
  324. package/python/tests/test_tool_use.py +0 -13
  325. package/python/tests/test_toplevel_concurrency.py +3 -4
  326. package/python/tests/test_tracing.py +7 -41
  327. package/python/tests/test_truth_crystallizer.py +4 -6
  328. package/python/tests/test_truth_flow.py +2 -7
  329. package/python/tests/test_update_check.py +95 -0
  330. package/python/tests/test_verify_evidence.py +8 -21
  331. package/python/tests/test_version_sync.py +35 -6
  332. package/python/tests/test_web_trigger.py +0 -17
  333. package/python/tools/check_faiss_type.py +0 -1
  334. package/python/tools/check_log.ps1 +0 -8
  335. package/python/tools/mypy_baseline.txt +12 -4
  336. package/python/tools/mypy_ratchet.py +52 -6
  337. package/python/tools/pack.py +0 -5
  338. package/python/tools/release_check.py +7 -7
  339. package/python/tools/restart_foxclaw.ps1 +5 -8
  340. package/python/tools/shared_write_audit.py +1 -15
  341. package/python/tools/size_check.py +1 -1
  342. package/python/tools/strip_comments.py +228 -0
  343. package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
  344. package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
@@ -22,14 +22,14 @@ RequestStatus = Literal["pending", "approved", "rejected", "needs_confirmation"]
22
22
  class CrossAgentRequest:
23
23
  """跨 Agent 请求 — 编排的基本单元。
24
24
  一个 Agent 向另一个发起请求,目标 Agent 用自己的守门人裁决。"""
25
- from_agent: str # 发起方
26
- to_agent: str # 目标方
25
+ from_agent: str
26
+ to_agent: str
27
27
  request_type: RequestType
28
- payload: dict # 请求内容(金额/时间/查询等)
28
+ payload: dict
29
29
  reason: str = ""
30
30
  status: RequestStatus = "pending"
31
- verdict: str = "" # 目标 Agent 的裁决说明
32
- negotiation: list = field(default_factory=list) # 协商回合记录(每轮:裁决 + 让步)
31
+ verdict: str = ""
32
+ negotiation: list = field(default_factory=list)
33
33
  id: str = field(default_factory=lambda: f"req_{uuid.uuid4().hex[:10]}")
34
34
  created_at: str = field(default_factory=lambda: datetime.now().isoformat())
35
35
 
@@ -40,10 +40,10 @@ class CrossAgentRequest:
40
40
  @dataclass
41
41
  class GlobalSituation:
42
42
  """全局态势 — 主管的驾驶舱视图,汇总各内核关键指标。"""
43
- finance: dict = field(default_factory=dict) # 超支?守门人告警?
44
- learning: dict = field(default_factory=dict) # 待复习?薄弱主题?
45
- itinerary: dict = field(default_factory=dict) # 行程冲突?赶不及?
46
- alerts: list = field(default_factory=list) # 跨域汇总的高优先告警
43
+ finance: dict = field(default_factory=dict)
44
+ learning: dict = field(default_factory=dict)
45
+ itinerary: dict = field(default_factory=dict)
46
+ alerts: list = field(default_factory=list)
47
47
  timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
48
48
 
49
49
  def to_dict(self) -> dict:
@@ -58,13 +58,8 @@ class Orchestrator:
58
58
  不再反向直接持有含主管的 agents 字典)。"""
59
59
 
60
60
  def __init__(self, caps, auditor=None):
61
- self._caps = caps # CapabilityRegistry
62
- # 【P1-3】统一治理审计(可空:standalone 无审计)。裁决事件原本**完全没有**——
63
- # 跨 Agent 委派的批准/拒绝/转人工,事前无任何可观测记录。
61
+ self._caps = caps
64
62
  self._auditor = auditor
65
- # 【深审修复·长期运行泄漏】原为普通 list、只增不减且**全库从不读**——每次跨 Agent 请求
66
- # append 一个 req → 无界增长。改有界 deque(保留最近 N 条供未来观测,符合本库 token_meter/
67
- # HookManager 的有界日志惯例);deque.append 原子 ⇒ 并发路由也安全。
68
63
  self.request_log: deque = deque(maxlen=1000)
69
64
 
70
65
  @property
@@ -72,12 +67,11 @@ class Orchestrator:
72
67
  """按名字从注册中枢解析 agents(id -> agent 实例)。"""
73
68
  return self._caps.get("agents") or {}
74
69
 
75
- # ── 全局态势汇总(P3:通用聚合,不懂任何领域内部 API)────────────
76
70
  def gather_situation(self) -> GlobalSituation:
77
71
  """通用聚合各 Agent 自报的领域态势(agent.situation()),形成驾驶舱视图。
78
72
  编排层不再直接读 finance/learning/itinerary 内核——领域知识已归属各领域 Agent。"""
79
73
  sit = GlobalSituation()
80
- alerts = []
74
+ alerts: list[dict] = []
81
75
  for agent in self.agents.values():
82
76
  try:
83
77
  s = agent.situation()
@@ -85,12 +79,10 @@ class Orchestrator:
85
79
  continue
86
80
  if not s:
87
81
  continue
88
- # 一个 Agent 可以自报**多个**领域(拓扑 5→3 后,办公学习 Agent 同时覆盖
89
- # learning 与 itinerary),故 `domains: {name: status}` 与旧的单 `domain` 都收。
90
82
  doms = s.get("domains")
91
83
  if isinstance(doms, dict) and doms:
92
84
  for d, st in doms.items():
93
- if d and hasattr(sit, d): # finance/learning/itinerary
85
+ if d and hasattr(sit, d):
94
86
  setattr(sit, d, st or {})
95
87
  else:
96
88
  domain = s.get("domain")
@@ -102,24 +94,16 @@ class Orchestrator:
102
94
  .get(a.get("severity"), 3))
103
95
  return sit
104
96
 
105
- # ── 跨 Agent 请求路由(P3:通用委托,由目标 Agent 用自己的守门人裁决)──────
106
97
  def route_request(self, req: CrossAgentRequest) -> CrossAgentRequest:
107
98
  """把请求路由给目标 Agent,由**目标 Agent**用自己的守门人裁决(编排层不懂领域)。"""
108
99
  target = self.agents.get(req.to_agent)
109
100
  if target is None:
110
- # 【P1-0】原为 status="approved"(默认放行)—— 那是 fail-open:发起方会拿到
111
- # "已批准",但**没有任何 Agent 真的处理过这个请求**。后果是实打实的,例如
112
- # 行程 Agent 向未注册的 finance 申请差旅预算,会直接拿到批准并据此下单。
113
- # 与下方「裁决异常 → 转人工确认」同属一条原则:裁决权不在编排层,凡未真正
114
- # 裁决过的,都不能算通过。改为 needs_confirmation 同样"不崩",只是不再说谎。
115
101
  req.status = "needs_confirmation"
116
102
  req.verdict = f"目标 Agent '{req.to_agent}' 不存在,无法裁决,转人工确认"
117
103
  else:
118
104
  try:
119
105
  target.handle_cross_request(req)
120
106
  except Exception as e:
121
- # 守门人裁决崩溃(如畸形 payload 让解析抛错)必须 **fail-safe**:转人工确认,
122
- # 绝不自动放行——对守门人而言,裁决失败时放行=fail-open,是安全隐患。
123
107
  req.status = "needs_confirmation"
124
108
  req.verdict = f"裁决异常,转人工确认: {e}"
125
109
  self.request_log.append(req)
@@ -143,7 +127,6 @@ class Orchestrator:
143
127
  "negotiation_round": len(req.negotiation)},
144
128
  correlation_id=req.id)
145
129
 
146
- # ── 协商闭环:被拒 → 发起方让步 → 再裁决 ──────────────────────────
147
130
  def negotiate(self, req: CrossAgentRequest, max_rounds: int = 3) -> CrossAgentRequest:
148
131
  """跑协商协议。原先裁决是**终局**:财务说预算不足→rejected→到此为止,用户只拿到
149
132
  一句"被拒绝"。现在:发起方可拿着拒绝理由降标准重来(改高铁/换酒店/减一天)。
@@ -165,17 +148,17 @@ class Orchestrator:
165
148
  "verdict": req.verdict,
166
149
  "payload": dict(req.payload)})
167
150
  if req.status != "rejected":
168
- break # 通过/待确认 → 协商结束
151
+ break
169
152
  requester = self.agents.get(req.from_agent)
170
153
  if requester is None:
171
154
  break
172
155
  try:
173
- revised = requester.concede(req, req.verdict) # 问发起方:能让吗?
156
+ revised = requester.concede(req, req.verdict)
174
157
  except Exception as e:
175
158
  req.negotiation.append({"round": rnd, "concede_error": str(e)[:120]})
176
- break # 让步逻辑出错 → 停,维持原裁决
159
+ break
177
160
  if not revised or revised == req.payload:
178
- break # 让无可让 / 无实质变化 → 停
161
+ break
179
162
  req.negotiation[-1]["conceded_to"] = dict(revised)
180
163
  req.payload = revised
181
164
  return req
@@ -15,6 +15,7 @@ import inspect
15
15
  import os
16
16
  from dataclasses import dataclass, field
17
17
  from typing import Any, Callable
18
+ from .stage_meta import named_stage
18
19
 
19
20
 
20
21
  @dataclass
@@ -27,32 +28,27 @@ class PipelineContext:
27
28
  反对的是"跨任务/并发共享可变状态"(改一半被读到不一致);ctx 不属于该反模式,可变安全。
28
29
  并发隔离由 test_pipeline.py::test_concurrent_ctx_isolation 锁定(24 并发任务零串)。
29
30
  (会话指针 _session_id/_parent_node_id 另经 contextvar 隔离,见 base.py。)"""
30
- agent: Any # 执行本任务的 Agent(CognitiveAgent)
31
+ agent: Any
31
32
  task: str
32
33
  context: dict = field(default_factory=dict)
33
34
  session_id: str = ""
34
- # 各 stage 逐步填充的产物
35
- perceived: dict = field(default_factory=dict) # perceive stage 的输入理解
35
+ perceived: dict = field(default_factory=dict)
36
36
  memories: list = field(default_factory=list)
37
- plan: dict = field(default_factory=dict) # plan stage 的任务结构(多步?并行?)
38
- route: dict = field(default_factory=dict) # route stage 的路由决策(target)
37
+ plan: dict = field(default_factory=dict)
38
+ route: dict = field(default_factory=dict)
39
39
  result: Any = None
40
40
  extra: dict = field(default_factory=dict)
41
- verified: dict = field(default_factory=dict) # verify stage 汇总的 Harness 校验结果
42
- reflect: dict = field(default_factory=dict) # reflect stage 的决策(accept/retry + 次数)
43
- trace: list = field(default_factory=list) # 逐 stage 轨迹(后续步骤 emit 用)
44
- # ── 协作式中断(P3,opt-in)──────────────────────────────
45
- stop_event: Any = None # 置位则在下一 stage 边界 / reflect 重试迭代前优雅停止;默认 None=永不停
46
- interrupted: bool = False # 被 stop_event 提前中断(供调用方识别"部分完成、未跑完全部 stage")
47
- # ── 【P1-1】主管路由专用字段(领域 Agent 不用,默认空)──────────
48
- # 主管 pipeline(supervisor_stages.SUPERVISOR_STAGES)在 schedule/route/plan/dispatch
49
- # 各 stage 间经这些字段传递路由决策;领域 Agent 的 DEFAULT_STAGES 从不读它们。
50
- sched: Any = None # schedule stage 的 ScheduleDecision
51
- target: str = "" # route stage 的分诊目标(子 Agent id)
52
- plan_obj: Any = None # plan stage 的 planner 拆解结果(多步计划)
53
- step_routes: list = field(default_factory=list) # [(子任务文本, 目标 Agent)]
54
- multi_agent: bool = False # 是否跨多 Agent(顺序链式 / 并行 fork-join)
55
- dispatch_result: dict = field(default_factory=dict) # dispatch stage 的原始分发返回
41
+ verified: dict = field(default_factory=dict)
42
+ reflect: dict = field(default_factory=dict)
43
+ trace: list = field(default_factory=list)
44
+ stop_event: Any = None
45
+ interrupted: bool = False
46
+ sched: Any = None
47
+ target: str = ""
48
+ plan_obj: Any = None
49
+ step_routes: list = field(default_factory=list)
50
+ multi_agent: bool = False
51
+ dispatch_result: dict = field(default_factory=dict)
56
52
 
57
53
 
58
54
  def _emit_stage(ctx: "PipelineContext", stage_name: str) -> None:
@@ -95,16 +91,10 @@ class CognitivePipeline:
95
91
  回调异常被吞(fail-safe):流式反馈绝不拖垮任务执行。"""
96
92
  from foxclaw.core.tracing import span as _span
97
93
  for stage in self.stages:
98
- # 协作式中断点(P3):stage **边界**检查 stop_event。opt-in——默认 stop_event=None 恒不触发,
99
- # 与改前逐字一致。置位时优雅停:标记 interrupted、保留已完成 stage 的 ctx 产物、不跑后续 stage。
100
- # 粒度=stage 边界:一般无法中止已在执行的单个 stage;但**流式路径(run_task_stream,D1)**下
101
- # act 的 LLM 生成经 `_act_stream` 的**同一** stop_event 串到 `router.stream`,可 **token 级**
102
- # 即时停 → 断连时二者协同:停后续 stage(此处)+ 停 act 生成(base._call_llm),省 token。
103
94
  if _stop_requested(ctx):
104
95
  ctx.interrupted = True
105
96
  break
106
97
  name = getattr(stage, "stage_name", getattr(stage, "__name__", "stage"))
107
- # 单一 stage 循环 → 一处埋点覆盖全部 stage(链路里能看到 perceive→…→reflect 各自耗时)
108
98
  with _span(f"stage.{name}", agent=getattr(ctx.agent, "AGENT_ID", "")):
109
99
  r = stage(ctx)
110
100
  if inspect.isawaitable(r):
@@ -113,15 +103,13 @@ class CognitivePipeline:
113
103
  _emit_stage(ctx, name)
114
104
  if on_stage is not None:
115
105
  try:
116
- on_stage(name, ctx) # D1:逐 stage 吐进度给流式路径
106
+ on_stage(name, ctx)
117
107
  except Exception:
118
- pass # 流式回调故障不拖垮任务执行(fail-safe)
108
+ pass
119
109
  return ctx
120
110
 
121
111
 
122
- # ── Stages(Step 1:recall / act)──────────────────────────────
123
- # stage 是操作 ctx 的可调用;命名 stage_* 便于事件与轨迹标注。
124
-
112
+ @named_stage("perceive")
125
113
  def stage_perceive(ctx: PipelineContext) -> None:
126
114
  """感知:把原始输入框定为结构化感知(Step 5)。当前记录轻量输入特征;
127
115
  这里是未来"意图识别/输入解析"的落点。也是逐 stage 事件链的起点。"""
@@ -129,14 +117,13 @@ def stage_perceive(ctx: PipelineContext) -> None:
129
117
  "task_len": len(ctx.task or ""),
130
118
  "has_context": bool(ctx.context),
131
119
  }
132
- stage_perceive.stage_name = "perceive"
133
120
 
134
121
 
122
+ @named_stage("recall")
135
123
  async def stage_recall(ctx: PipelineContext) -> None:
136
124
  """召回相关记忆(等价 P1-1 模板的 _recall_memory(_recall_query, top_k))。"""
137
125
  a = ctx.agent
138
126
  ctx.memories = a._recall_memory(a._recall_query(ctx.task), top_k=a._recall_topk)
139
- stage_recall.stage_name = "recall"
140
127
 
141
128
 
142
129
  def _plan_text(plan: dict) -> str:
@@ -152,7 +139,7 @@ def _record_plan_node(ctx: PipelineContext) -> None:
152
139
  计划节点不参与准入裁决,也不被 act 读取(见下方「informational」边界说明)。
153
140
  """
154
141
  agent = getattr(ctx, "agent", None)
155
- think = getattr(agent, "_think", None) # 轻量替身/测试 Agent 可能没有 _think
142
+ think = getattr(agent, "_think", None)
156
143
  if think is None:
157
144
  return
158
145
  try:
@@ -162,6 +149,7 @@ def _record_plan_node(ctx: PipelineContext) -> None:
162
149
  pass
163
150
 
164
151
 
152
+ @named_stage("plan")
165
153
  def stage_plan(ctx: PipelineContext) -> None:
166
154
  """结构规划:用 **P1-2 统一规划引擎** 分析任务结构(是否多步/并行),记录到 ctx.plan。
167
155
 
@@ -187,28 +175,10 @@ def stage_plan(ctx: PipelineContext) -> None:
187
175
  steps, flow = [task], "single"
188
176
  ctx.plan = {"steps": steps, "flow": flow, "multi_step": len(steps) > 1}
189
177
  _record_plan_node(ctx)
190
- stage_plan.stage_name = "plan"
191
- # ⚠【预留·informational,不驱动执行】(2026-07-17 V3·B1 审核标注)
192
- # stage_plan 写 ctx.plan 供观测/审计,但**没有任何 _execute 读取它**,故不改变执行
193
- # (act 整段交 LLM/skill)。让 act 真按 plan 多步执行是**行为改变**,须 opt-in + 每步
194
- # confirm 挂起 + fail-safe(见 V2_ROADMAP「Skill 接入认知管线」),不在此顺手做。
195
- # (B1 自审:曾在此加 `stage_plan.informational=True` 属性,但无任何消费者=又一个死标记,
196
- # 已删——注释足以说明,不造没人读的假结构化标记。)
197
-
198
-
199
- # 默认自动派发的门槛(A·Step5,2026-07-16:从"默认关"改为"默认开但严格受限")。
200
- # 背景:此前 route/dispatch 双双默认关 → 工具中心/MCP 连接器/技能台**在正常对话里根本够不着**
201
- # (「帮我算 3+5」助手只会用 LLM 硬答,计算器技能不会被调用)。整套工具生态形同虚设。
202
- # 放量策略:只自动派发**只读**技能、且要求**高置信**;有副作用的(write/send/pay/destructive)
203
- # 一律不自动选中,仍须显式 use_skill —— 自动执行绝不越过"会改变世界"的那条线。
204
- # 阈值取 0.6 —— 这是**量出来的**,不是拍的。router 实测分值只有三档:
205
- # 0.600 强匹配(触发词直接命中,如「帮我计算…」→calculator)
206
- # 0.480 弱匹配(如「查下去公司的路线」→amap_route、「发邮件给张三」→gmail_send)
207
- # 0.000 无匹配(闲聊/写代码)
208
- # 故 (0.48, 0.60] 区间内的任何值都等价于"只要强匹配";再高(如 0.75)则**一个都选不中**、
209
- # 功能形同虚设。0.6 已是最保守的可用值 —— "比 0.6 更保守"是伪命题。
178
+
179
+
210
180
  AUTO_ROUTE_THRESHOLD = 0.6
211
- AUTO_ROUTE_READ_ONLY = True # 只自动派发只读技能(有副作用的绝不自动动手)
181
+ AUTO_ROUTE_READ_ONLY = True
212
182
 
213
183
 
214
184
  def _is_read_only(skill) -> bool:
@@ -242,6 +212,7 @@ def _route_policy(agent, context: dict) -> dict:
242
212
  "read_only": bool(p.get("read_only", AUTO_ROUTE_READ_ONLY))}
243
213
 
244
214
 
215
+ @named_stage("route")
245
216
  def stage_route(ctx: PipelineContext) -> None:
246
217
  """路由决策:记录本任务由谁处理到 ctx.route(target=self 或 skill)。
247
218
 
@@ -260,33 +231,27 @@ def stage_route(ctx: PipelineContext) -> None:
260
231
  return
261
232
  reg = getattr(ctx.agent, "skill_registry", None)
262
233
  if reg is None:
263
- return # 无工具箱(finance/admin/office)→ 保持 self
234
+ return
264
235
  try:
265
236
  from ..skills.pipeline import route as skill_route
266
237
  r = skill_route(ctx.task, reg)
267
238
  except Exception:
268
- return # 选路失败不影响主流程 → 保持 self(fail-safe)
239
+ return
269
240
  if r.primary and r.confidence >= policy["threshold"]:
270
241
  sk = reg.get(r.primary)
271
- # 只读闸:有副作用的技能(write/send/pay/destructive)**绝不自动选中**——
272
- # 自动执行不得越过"会改变世界"的线;要用仍可显式 use_skill(那时用户是知情的)。
273
242
  if policy["read_only"] and sk is not None and not _is_read_only(sk):
274
- return # 保持 self → LLM 作答,不自动动手
275
- # 外泄闸:问的是**用户自己的数据**(账本/行程/学习)→ 绝不自动发给**外部**技能。
276
- # 与 needs_search 共用同一判定(单一真源)。否则「查一下最新的本月支出」会把私人
277
- # 财务问题自动送去外部搜索——那正是 needs_search 已挡住、却从技能这条旁路漏回来的 bug。
243
+ return
278
244
  if sk is not None and getattr(sk, "external", False):
279
245
  try:
280
246
  from ..core.web_search import is_personal_query
281
247
  if is_personal_query(ctx.task):
282
- return # 私人问题不外发 → 保持 self
248
+ return
283
249
  except Exception:
284
- pass # 判定不可用 → 不因此放行外部技能之外的行为(保持原逻辑)
250
+ pass
285
251
  ctx.route = {"target": "skill", "skill_id": r.primary,
286
252
  "confidence": round(float(r.confidence), 3),
287
253
  "reason": f"router 选中 skill「{r.primary}」"
288
254
  f"(置信 {r.confidence:.2f}≥{policy['threshold']})"}
289
- stage_route.stage_name = "route"
290
255
 
291
256
 
292
257
  def _skill_dispatch_enabled(agent, context: dict) -> bool:
@@ -339,21 +304,21 @@ async def _act_via_skill(ctx: "PipelineContext"):
339
304
  reg = getattr(agent, "skill_registry", None)
340
305
  adp = getattr(agent, "skill_adapter", None)
341
306
  if reg is None or adp is None:
342
- return None # 无工具箱(finance/admin/office)→ 回落 _execute
307
+ return None
343
308
  sid = (ctx.route or {}).get("skill_id")
344
- skill = reg.get(sid) if sid else None
309
+ if not isinstance(sid, str) or not sid:
310
+ return None
311
+ skill = reg.get(sid)
345
312
  if skill is None:
346
313
  return None
347
314
  from ..skills.pipeline import _build_args
348
315
  from ..skills import set_loop_session, reset_loop_session
349
316
  args = _build_args(skill, ctx.task, {})
350
317
  confirmed = _confirmed_for(ctx.context, sid)
351
- token = set_loop_session(getattr(ctx, "session_id", None)) # 循环检测按会话分槽
318
+ token = set_loop_session(getattr(ctx, "session_id", None))
352
319
  try:
353
320
  res = await asyncio.to_thread(adp.invoke, skill, args, confirmed)
354
321
  except Exception as e:
355
- # invoke 理应自兜底(catches _dispatch 异常);万一权限评估等意外抛错 → **fail-closed**:
356
- # 结构化 error 回传,**绝不回落 LLM**(路由到发邮件却让 LLM 代答=幻觉/危险),也不崩整个任务。
357
322
  return ({"skill": sid, "status": "error", "message": f"skill dispatch 异常: {e}",
358
323
  "pending": False}, {"via": "skill", "skill_id": sid, "skill_status": "error"})
359
324
  finally:
@@ -361,12 +326,11 @@ async def _act_via_skill(ctx: "PipelineContext"):
361
326
  extra = {"via": "skill", "skill_id": sid, "skill_status": res.status}
362
327
  if res.success:
363
328
  return ({"skill": sid, "output": res.output, "status": res.status}, extra)
364
- # 非成功:无副作用已发生(权限门+confirmed=False保证)。结构化回传,绝不当完成、绝不回落 LLM
365
- # (路由到发邮件却让 LLM 代答=危险/幻觉);needs_confirmation 待用户确认后重跑(带 _confirmed)。
366
329
  return ({"skill": sid, "status": res.status, "message": res.message,
367
330
  "pending": res.status == "needs_confirmation"}, extra)
368
331
 
369
332
 
333
+ @named_stage("act")
370
334
  async def stage_act(ctx: PipelineContext) -> None:
371
335
  """领域执行步骤。默认:领域 `_execute`(→ LLM),等价 P1-1 模板。
372
336
 
@@ -381,9 +345,9 @@ async def stage_act(ctx: PipelineContext) -> None:
381
345
  return
382
346
  ctx.result, ctx.extra = await ctx.agent._execute(
383
347
  ctx.task, ctx.context, ctx.session_id, ctx.memories)
384
- stage_act.stage_name = "act"
385
348
 
386
349
 
350
+ @named_stage("verify")
387
351
  def stage_verify(ctx: PipelineContext) -> None:
388
352
  """把本次执行的 Harness 校验**结果**显式化到 ctx.verified(Step 2)。
389
353
 
@@ -399,19 +363,15 @@ def stage_verify(ctx: PipelineContext) -> None:
399
363
  except Exception:
400
364
  nodes = []
401
365
  warns = sum(1 for n in nodes if getattr(n, "harness_status", "") == "WARN")
402
- deltas = [n.harness_delta for n in nodes # get_chain 按 timestamp 升序
366
+ deltas = [n.harness_delta for n in nodes
403
367
  if getattr(n, "harness_delta", None) is not None]
404
368
  ctx.verified = {
405
- "passed": True, # 未被 BLOCK
369
+ "passed": True,
406
370
  "nodes": len(nodes),
407
371
  "warns": warns,
408
- "max_delta": round(max(deltas), 3) if deltas else 0.0, # 全 session 峰值(信息量)
409
- # last_delta = 最近一次推理的漂移(本次尝试的最终状态)。reflect 用它作重试信号——
410
- # 深审修复:max_delta 是 max over 累积历史(单调不减),重试追加节点后仍是旧峰值,
411
- # 会让重试永远到上限、无法因"重试改善"提前退出;last_delta 反映当前尝试,可正确早退。
372
+ "max_delta": round(max(deltas), 3) if deltas else 0.0,
412
373
  "last_delta": round(deltas[-1], 3) if deltas else 0.0,
413
374
  }
414
- stage_verify.stage_name = "verify"
415
375
 
416
376
 
417
377
  def _reflect_policy(agent, context: dict) -> dict:
@@ -426,10 +386,11 @@ def _reflect_policy(agent, context: dict) -> dict:
426
386
  if p is True:
427
387
  p = {}
428
388
  return {"enabled": True,
429
- "max_retries": int(p.get("max_retries", 1)), # 硬上限,防成环
389
+ "max_retries": int(p.get("max_retries", 1)),
430
390
  "drift_threshold": float(p.get("drift_threshold", 0.5))}
431
391
 
432
392
 
393
+ @named_stage("reflect")
433
394
  async def stage_reflect(ctx: PipelineContext) -> None:
434
395
  """反思:读 verify 信号(max_delta),决定接受还是**有上限地**重试(Step 3)。
435
396
 
@@ -440,42 +401,29 @@ async def stage_reflect(ctx: PipelineContext) -> None:
440
401
  policy = _reflect_policy(ctx.agent, ctx.context)
441
402
  decision, retries = "accept", 0
442
403
  if policy["enabled"]:
443
- # 用 last_delta(当前尝试的最终漂移)作重试信号,而非 max_delta(累积峰值,单调不减)——
444
- # 否则重试改善了也无法早退。重试若把漂移降到阈值内,下一轮 last_delta 达标即停止。
445
404
  while (ctx.verified.get("last_delta", 0.0) > policy["drift_threshold"]
446
405
  and retries < policy["max_retries"]):
447
- # 协作式中断点(P3):每次重试都是一次 `_execute`(LLM 调用)——这里是本管线里最接近
448
- # 范式"agentic loop 每轮检查中断"的地方。置位则停在下一轮 LLM 调用**之前**(省 token),
449
- # 保留已有最好结果。opt-in:stop_event=None 恒不触发,重试行为逐字不变。
450
406
  if _stop_requested(ctx):
451
- # 中断:decision 统一记 "interrupted",已发生的重试次数由 retries 字段承载。
452
- # (B2 审核 F1:此前分 interrupted/retry_interrupted 两值,其中 retries==0 一支仅在
453
- # 外部线程于"pre-reflect 边界检查"与"首个 while 检查"之间置位才可达——单线程测试无法
454
- # 稳定覆盖。合并为单值消除该未覆盖分支,信息量不减: retries 已表达重试了几次。)
455
407
  ctx.interrupted = True
456
408
  decision = "interrupted"
457
409
  break
458
- prev_result, prev_extra = ctx.result, ctx.extra # 重试前的最好结果
410
+ prev_result, prev_extra = ctx.result, ctx.extra
459
411
  try:
460
412
  retries += 1
461
413
  decision = "retry"
462
414
  ctx.result, ctx.extra = await ctx.agent._execute(
463
415
  ctx.task, ctx.context, ctx.session_id, ctx.memories)
464
- stage_verify(ctx) # 重新汇总校验(刷新 last_delta)
416
+ stage_verify(ctx)
465
417
  except Exception:
466
- # 重试出错(含重跑被 BLOCK)→ 回滚到上一次好结果,停止重试(不让重试变差)
467
418
  ctx.result, ctx.extra = prev_result, prev_extra
468
419
  decision = "retry_reverted"
469
420
  break
470
- # 重试后 last_delta 已达标 → 视为改善接受
471
421
  if decision == "retry" and ctx.verified.get("last_delta", 0.0) <= policy["drift_threshold"]:
472
422
  decision = "retry_accepted"
473
423
  ctx.reflect = {"decision": decision, "retries": retries,
474
424
  "final_last_delta": ctx.verified.get("last_delta", 0.0),
475
425
  "final_max_delta": ctx.verified.get("max_delta", 0.0)}
476
- stage_reflect.stage_name = "reflect"
477
426
 
478
427
 
479
- # 默认管线的 stage 序列(完整八阶段之七;persist=run_task 里的 emit + 领域 _store_memory)。
480
428
  DEFAULT_STAGES = [stage_perceive, stage_recall, stage_plan, stage_route,
481
429
  stage_act, stage_verify, stage_reflect]
@@ -0,0 +1,21 @@
1
+ """stage 的命名元数据。
2
+
3
+ pipeline 的两个模块都按「函数 + stage_name 属性」标注阶段,读侧用 getattr 兜底
4
+ (见 cognitive_pipeline 里取 name 的那处)。属性赋值 `fn.stage_name = ...` 在类型
5
+ 检查里不成立 —— 函数对象的类型没有这个属性;setattr 是运行期真做的同一件事,
6
+ 而且让「名字」与「定义」写在同一处,不会漏标。
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from typing import Callable, TypeVar
11
+
12
+ F = TypeVar("F", bound=Callable[..., object])
13
+
14
+
15
+ def named_stage(name: str) -> Callable[[F], F]:
16
+ """给 stage 可调用挂 stage_name(stage 事件与轨迹标注用这个键)。"""
17
+ def deco(fn: F) -> F:
18
+ vars(fn)["stage_name"] = name
19
+ return fn
20
+
21
+ return deco