foxclaw 0.1.2 → 0.1.4

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 (368) 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/.github/workflows/release.yml +5 -0
  6. package/python/CHANGELOG.md +100 -0
  7. package/python/CHANGELOG_CN.md +100 -0
  8. package/python/README.md +17 -2
  9. package/python/README_CN.md +38 -20
  10. package/python/bench/bm25_audit.py +1 -14
  11. package/python/bench/bm25_regression.py +0 -1
  12. package/python/bench/bm25_stress.py +0 -4
  13. package/python/bench/cd_audit.py +0 -23
  14. package/python/bench/chaos.py +6 -8
  15. package/python/bench/e2e_real_models.py +0 -1
  16. package/python/bench/llm_retry_test.py +2 -9
  17. package/python/bench/load_test.py +5 -8
  18. package/python/bench/vector_ivf_upgrade.py +1 -7
  19. package/python/docs/CONTENT_GUARD.md +189 -0
  20. package/python/docs/CONTENT_GUARD_CN.md +189 -0
  21. package/python/docs/RELEASE.md +78 -37
  22. package/python/docs/RUN_CN.md +2 -2
  23. package/python/evals/governance/cases.py +0 -37
  24. package/python/evals/governance/run_evals.py +5 -37
  25. package/python/examples/governance_middleware_demo.py +1 -6
  26. package/python/examples/langgraph_governance_demo.py +7 -11
  27. package/python/foxclaw/__init__.py +74 -152
  28. package/python/foxclaw/agents/base.py +82 -136
  29. package/python/foxclaw/agents/impl/_helpers.py +2 -10
  30. package/python/foxclaw/agents/impl/finance.py +4 -21
  31. package/python/foxclaw/agents/impl/office.py +7 -24
  32. package/python/foxclaw/agents/impl/supervisor.py +12 -79
  33. package/python/foxclaw/agents/mixins/_context.py +0 -23
  34. package/python/foxclaw/agents/mixins/augment.py +10 -16
  35. package/python/foxclaw/agents/mixins/llm.py +7 -6
  36. package/python/foxclaw/agents/mixins/memory.py +23 -3
  37. package/python/foxclaw/agents/mixins/preference.py +58 -8
  38. package/python/foxclaw/agents/mixins/prompt.py +59 -12
  39. package/python/foxclaw/agents/mixins/skillbox.py +6 -6
  40. package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
  41. package/python/foxclaw/agents/rules/finance/RULES.md +131 -0
  42. package/python/foxclaw/api/__init__.py +18 -77
  43. package/python/foxclaw/api/_state.py +43 -0
  44. package/python/foxclaw/api/bootstrap.py +114 -0
  45. package/python/foxclaw/api/middleware.py +5 -28
  46. package/python/foxclaw/api/routes/ahs.py +60 -73
  47. package/python/foxclaw/api/routes/attachments.py +5 -6
  48. package/python/foxclaw/api/routes/browser.py +7 -11
  49. package/python/foxclaw/api/routes/domain.py +18 -26
  50. package/python/foxclaw/api/routes/knowledge.py +3 -21
  51. package/python/foxclaw/api/routes/llm_admin.py +21 -33
  52. package/python/foxclaw/api/routes/memory.py +50 -12
  53. package/python/foxclaw/api/routes/orchestration.py +3 -5
  54. package/python/foxclaw/api/routes/recovery.py +4 -3
  55. package/python/foxclaw/api/routes/review.py +2 -11
  56. package/python/foxclaw/api/routes/search.py +3 -7
  57. package/python/foxclaw/api/routes/system.py +33 -49
  58. package/python/foxclaw/api/routes/task.py +31 -49
  59. package/python/foxclaw/api/routes/ws.py +2 -6
  60. package/python/foxclaw/api/spine.py +1 -4
  61. package/python/foxclaw/behavior/__init__.py +20 -0
  62. package/python/foxclaw/behavior/behavior.yaml +62 -0
  63. package/python/foxclaw/behavior/spec.py +287 -0
  64. package/python/foxclaw/browser/action/action_executor.py +1 -5
  65. package/python/foxclaw/browser/action/action_types.py +0 -1
  66. package/python/foxclaw/browser/action/url_policy.py +1 -5
  67. package/python/foxclaw/browser/adapter/web_adapter.py +1 -1
  68. package/python/foxclaw/browser/driver/playwright_driver.py +20 -16
  69. package/python/foxclaw/browser/perception/dom_extractor.py +0 -1
  70. package/python/foxclaw/browser/perception/element_indexer.py +1 -2
  71. package/python/foxclaw/browser/service.py +7 -11
  72. package/python/foxclaw/capability/embedding_service.py +4 -6
  73. package/python/foxclaw/channels/__init__.py +9 -11
  74. package/python/foxclaw/channels/discord.py +4 -13
  75. package/python/foxclaw/channels/dispatcher.py +2 -27
  76. package/python/foxclaw/channels/feishu.py +10 -20
  77. package/python/foxclaw/channels/openclaw_bridge.py +4 -9
  78. package/python/foxclaw/channels/signing.py +9 -9
  79. package/python/foxclaw/channels/telegram.py +3 -4
  80. package/python/foxclaw/channels/weixin.py +7 -17
  81. package/python/foxclaw/cli.py +42 -13
  82. package/python/foxclaw/cognitive/reasoner.py +9 -10
  83. package/python/foxclaw/cognitive/state.py +18 -24
  84. package/python/foxclaw/core/audit_chain.py +1 -3
  85. package/python/foxclaw/core/audit_sink.py +3 -3
  86. package/python/foxclaw/core/compaction.py +8 -26
  87. package/python/foxclaw/core/config.py +2 -2
  88. package/python/foxclaw/core/confirm_nonce.py +6 -12
  89. package/python/foxclaw/core/content_guard.py +341 -0
  90. package/python/foxclaw/core/envfile.py +2 -2
  91. package/python/foxclaw/core/failure_policy.py +17 -26
  92. package/python/foxclaw/core/gateway.py +6 -10
  93. package/python/foxclaw/core/governance_audit.py +3 -7
  94. package/python/foxclaw/core/idempotency.py +15 -22
  95. package/python/foxclaw/core/injection_guard.py +10 -29
  96. package/python/foxclaw/core/memory_adapter.py +53 -60
  97. package/python/foxclaw/core/metrics.py +4 -5
  98. package/python/foxclaw/core/model_router.py +74 -150
  99. package/python/foxclaw/core/paths.py +1 -1
  100. package/python/foxclaw/core/recovery.py +21 -34
  101. package/python/foxclaw/core/scheduler.py +23 -26
  102. package/python/foxclaw/core/secrets.py +0 -3
  103. package/python/foxclaw/core/sensitive_write.py +283 -0
  104. package/python/foxclaw/core/sessions.py +4 -6
  105. package/python/foxclaw/core/settings.py +16 -32
  106. package/python/foxclaw/core/shared_write.py +11 -23
  107. package/python/foxclaw/core/steering_queue.py +12 -11
  108. package/python/foxclaw/core/system_bus.py +1 -1
  109. package/python/foxclaw/core/thought_chain.py +30 -32
  110. package/python/foxclaw/core/token_meter.py +9 -13
  111. package/python/foxclaw/core/tools.py +4 -12
  112. package/python/foxclaw/core/tracing.py +25 -40
  113. package/python/foxclaw/core/truth_pipeline.py +5 -9
  114. package/python/foxclaw/core/update_check.py +149 -0
  115. package/python/foxclaw/core/vec_recall_adapter.py +11 -37
  116. package/python/foxclaw/core/verify.py +6 -14
  117. package/python/foxclaw/core/web_search.py +4 -28
  118. package/python/foxclaw/finance/evidence.py +14 -18
  119. package/python/foxclaw/finance/fx.py +1 -3
  120. package/python/foxclaw/finance/graph.py +9 -18
  121. package/python/foxclaw/finance/models.py +20 -24
  122. package/python/foxclaw/finance/nlparse.py +12 -72
  123. package/python/foxclaw/finance/policy.py +8 -36
  124. package/python/foxclaw/finance/reasoner.py +3 -11
  125. package/python/foxclaw/finance/reasoning.py +13 -34
  126. package/python/foxclaw/finance/reconcile.py +5 -10
  127. package/python/foxclaw/finance/runtime.py +19 -46
  128. package/python/foxclaw/governance/middleware.py +13 -21
  129. package/python/foxclaw/harness/_base.py +2 -8
  130. package/python/foxclaw/harness/_policy.py +19 -38
  131. package/python/foxclaw/harness/drift.py +1 -13
  132. package/python/foxclaw/harness/event_bus.py +2 -5
  133. package/python/foxclaw/harness/harness.py +9 -97
  134. package/python/foxclaw/harness/loop_detection.py +11 -18
  135. package/python/foxclaw/itinerary/amap_client.py +2 -5
  136. package/python/foxclaw/itinerary/models.py +7 -7
  137. package/python/foxclaw/itinerary/reasoner.py +1 -6
  138. package/python/foxclaw/itinerary/reasoning.py +15 -21
  139. package/python/foxclaw/itinerary/runtime.py +8 -12
  140. package/python/foxclaw/kernel/capabilities.py +5 -6
  141. package/python/foxclaw/kernel/policy_gate.py +4 -10
  142. package/python/foxclaw/kernel/process_manager.py +17 -36
  143. package/python/foxclaw/kernel/scheduler.py +3 -9
  144. package/python/foxclaw/kernel/sentinel.py +13 -23
  145. package/python/foxclaw/kernel/sentinel_client.py +12 -15
  146. package/python/foxclaw/kernel/spine.py +9 -9
  147. package/python/foxclaw/knowledge/__init__.py +4 -20
  148. package/python/foxclaw/knowledge/truth_crystallizer.py +5 -8
  149. package/python/foxclaw/learning/knowledge_graph.py +4 -9
  150. package/python/foxclaw/learning/models.py +13 -13
  151. package/python/foxclaw/learning/reasoner.py +1 -4
  152. package/python/foxclaw/learning/runtime.py +19 -31
  153. package/python/foxclaw/learning/spaced_repetition.py +2 -7
  154. package/python/foxclaw/mcp_server.py +2 -3
  155. package/python/foxclaw/orchestration/__init__.py +16 -33
  156. package/python/foxclaw/pipeline/cognitive_pipeline.py +45 -97
  157. package/python/foxclaw/pipeline/stage_meta.py +21 -0
  158. package/python/foxclaw/pipeline/supervisor_stages.py +13 -42
  159. package/python/foxclaw/planning/llm_planner.py +3 -3
  160. package/python/foxclaw/planning/planner.py +6 -11
  161. package/python/foxclaw/search/agent_port.py +7 -11
  162. package/python/foxclaw/search/evidence.py +0 -4
  163. package/python/foxclaw/search/internal_search.py +3 -5
  164. package/python/foxclaw/search/models.py +11 -13
  165. package/python/foxclaw/search/planner.py +3 -3
  166. package/python/foxclaw/search/retriever.py +1 -1
  167. package/python/foxclaw/search/runtime.py +1 -8
  168. package/python/foxclaw/skills/__init__.py +46 -100
  169. package/python/foxclaw/skills/archive.py +111 -0
  170. package/python/foxclaw/skills/fetch.py +226 -0
  171. package/python/foxclaw/skills/hooks_spec.py +21 -25
  172. package/python/foxclaw/skills/install_cmd.py +216 -0
  173. package/python/foxclaw/skills/lifecycle.py +16 -42
  174. package/python/foxclaw/skills/markdown_skill.py +10 -9
  175. package/python/foxclaw/skills/mcp_client.py +5 -11
  176. package/python/foxclaw/skills/mcp_store.py +0 -3
  177. package/python/foxclaw/skills/permission.py +13 -48
  178. package/python/foxclaw/skills/pipeline.py +21 -53
  179. package/python/foxclaw/skills/policy.py +4 -10
  180. package/python/foxclaw/skills/review.py +3 -3
  181. package/python/frontend/deck/auth.js +0 -7
  182. package/python/frontend/deck/chat_model_picker.js +0 -54
  183. package/python/frontend/deck/domain.js +0 -20
  184. package/python/frontend/deck/main.js +0 -144
  185. package/python/frontend/deck/markdown.js +0 -4
  186. package/python/frontend/deck/settings.js +1 -18
  187. package/python/frontend/deck/state.js +0 -1
  188. package/python/frontend/deck/util.js +0 -2
  189. package/python/frontend/deck/visuals.js +0 -22
  190. package/python/frontend/planet/planet.js +18 -201
  191. package/python/frontend/v3/assets/index-JKfHlb-a.css +1 -0
  192. package/python/frontend/v3/assets/index-tN90dpUH.js +34 -0
  193. package/python/frontend/v3/index.html +3 -3
  194. package/python/frontend/widget_runtime.js +0 -43
  195. package/python/frontend/widgets.js +0 -57
  196. package/python/frontend_src/index.html +1 -1
  197. package/python/frontend_src/package-lock.json +2 -2
  198. package/python/frontend_src/package.json +1 -1
  199. package/python/frontend_src/src/App.vue +3 -7
  200. package/python/frontend_src/src/api.js +0 -7
  201. package/python/frontend_src/src/components/ChatDock.vue +27 -39
  202. package/python/frontend_src/src/components/DocViewer.vue +0 -9
  203. package/python/frontend_src/src/components/Inspector.vue +0 -6
  204. package/python/frontend_src/src/components/LeftRail.vue +60 -39
  205. package/python/frontend_src/src/components/MenuPopover.vue +9 -11
  206. package/python/frontend_src/src/components/MessageStream.vue +18 -30
  207. package/python/frontend_src/src/components/ModelPicker.vue +0 -5
  208. package/python/frontend_src/src/components/QuickIngest.vue +0 -6
  209. package/python/frontend_src/src/components/Stage.vue +71 -25
  210. package/python/frontend_src/src/components/ToolMenu.vue +0 -3
  211. package/python/frontend_src/src/components/TopBar.vue +38 -10
  212. package/python/frontend_src/src/components/panels/ActivityPanel.vue +0 -2
  213. package/python/frontend_src/src/components/panels/AgentsPanel.vue +2 -5
  214. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +0 -16
  215. package/python/frontend_src/src/components/panels/MemoryPanel.vue +42 -3
  216. package/python/frontend_src/src/components/panels/PanelBox.vue +6 -7
  217. package/python/frontend_src/src/components/panels/ReviewPanel.vue +0 -16
  218. package/python/frontend_src/src/components/panels/SettingsPanel.vue +142 -38
  219. package/python/frontend_src/src/components/panels/TasksPanel.vue +0 -1
  220. package/python/frontend_src/src/components/panels/ToolsPanel.vue +125 -19
  221. package/python/frontend_src/src/lib/fmt.js +0 -4
  222. package/python/frontend_src/src/lib/i18n.js +53 -0
  223. package/python/frontend_src/src/lib/locales/en.js +173 -0
  224. package/python/frontend_src/src/lib/locales/zh.js +174 -0
  225. package/python/frontend_src/src/lib/md.js +0 -14
  226. package/python/frontend_src/src/main.js +2 -0
  227. package/python/frontend_src/src/stores/app.js +17 -32
  228. package/python/frontend_src/src/stores/chat.js +0 -34
  229. package/python/frontend_src/src/stores/model.js +0 -8
  230. package/python/frontend_src/src/stores/panels.js +16 -44
  231. package/python/frontend_src/src/stores/profile.js +31 -0
  232. package/python/frontend_src/src/stores/system.js +25 -0
  233. package/python/frontend_src/vite.config.js +0 -7
  234. package/python/pyproject.toml +17 -3
  235. package/python/requirements.txt +311 -0
  236. package/python/tests/conftest.py +0 -22
  237. package/python/tests/run_all.py +4 -66
  238. package/python/tests/test_agent_lifecycle.py +4 -38
  239. package/python/tests/test_agent_rules.py +81 -0
  240. package/python/tests/test_ahs_p2.py +4 -25
  241. package/python/tests/test_anthropic_tools.py +1 -15
  242. package/python/tests/test_api.py +19 -9
  243. package/python/tests/test_api_auth.py +1 -5
  244. package/python/tests/test_attachments.py +9 -25
  245. package/python/tests/test_audit_chain.py +2 -7
  246. package/python/tests/test_audit_sink.py +0 -6
  247. package/python/tests/test_behavior_spec.py +198 -0
  248. package/python/tests/test_browser.py +2 -6
  249. package/python/tests/test_browser_request_guard.py +0 -4
  250. package/python/tests/test_capabilities.py +0 -6
  251. package/python/tests/test_channel_signing.py +2 -9
  252. package/python/tests/test_channels.py +1 -11
  253. package/python/tests/test_config_layering.py +0 -4
  254. package/python/tests/test_confirm_nonce.py +1 -12
  255. package/python/tests/test_conflict_dismiss.py +143 -0
  256. package/python/tests/test_content_guard.py +177 -0
  257. package/python/tests/test_context_compaction_wiring.py +1 -7
  258. package/python/tests/test_dispatch.py +0 -6
  259. package/python/tests/test_domain_evals.py +11 -80
  260. package/python/tests/test_domain_import.py +0 -6
  261. package/python/tests/test_domain_reasoners.py +0 -14
  262. package/python/tests/test_e2e.py +0 -17
  263. package/python/tests/test_emb_cache.py +7 -17
  264. package/python/tests/test_embedding_singleton.py +1 -3
  265. package/python/tests/test_evals_smoke.py +0 -24
  266. package/python/tests/test_event_spine.py +10 -18
  267. package/python/tests/test_evidence_semantic.py +1 -14
  268. package/python/tests/test_evidence_sse.py +3 -6
  269. package/python/tests/test_file_ingest.py +0 -11
  270. package/python/tests/test_finance.py +0 -9
  271. package/python/tests/test_finance_ledger.py +5 -12
  272. package/python/tests/test_finance_persist.py +8 -12
  273. package/python/tests/test_gateway.py +3 -22
  274. package/python/tests/test_governance.py +5 -52
  275. package/python/tests/test_governance_audit.py +4 -39
  276. package/python/tests/test_governance_middleware.py +1 -7
  277. package/python/tests/test_hkc.py +6 -24
  278. package/python/tests/test_hkc_index_build_manager.py +0 -33
  279. package/python/tests/test_hkc_manifest_reuse.py +4 -27
  280. package/python/tests/test_hkc_search_event_sync.py +3 -54
  281. package/python/tests/test_import_path_guard.py +0 -6
  282. package/python/tests/test_injection_guard.py +18 -36
  283. package/python/tests/test_integration.py +5 -29
  284. package/python/tests/test_itinerary.py +0 -3
  285. package/python/tests/test_kee_semantic.py +0 -5
  286. package/python/tests/test_kernel.py +1 -11
  287. package/python/tests/test_learning.py +3 -12
  288. package/python/tests/test_llm_admin.py +4 -19
  289. package/python/tests/test_llm_planner.py +1 -23
  290. package/python/tests/test_markdown_skills.py +3 -14
  291. package/python/tests/test_markdown_skills_openclaw.py +4 -9
  292. package/python/tests/test_mcp_connectors.py +3 -15
  293. package/python/tests/test_mcp_guardian_discovery.py +2 -6
  294. package/python/tests/test_mcp_no_memory.py +3 -3
  295. package/python/tests/test_mcp_policy_gate.py +0 -1
  296. package/python/tests/test_mcp_server.py +6 -9
  297. package/python/tests/test_memory_recall.py +1 -4
  298. package/python/tests/test_metrics.py +0 -8
  299. package/python/tests/test_multi_chain.py +1 -13
  300. package/python/tests/test_multimodal.py +5 -18
  301. package/python/tests/test_negotiation.py +3 -13
  302. package/python/tests/test_orchestration.py +1 -11
  303. package/python/tests/test_orchestration_decoupled.py +0 -6
  304. package/python/tests/test_p3_p5_hardening.py +3 -17
  305. package/python/tests/test_parallel_dispatch.py +4 -12
  306. package/python/tests/test_pipeline.py +17 -58
  307. package/python/tests/test_plan_visible.py +4 -4
  308. package/python/tests/test_planner.py +0 -7
  309. package/python/tests/test_policy_gate.py +2 -3
  310. package/python/tests/test_profile.py +67 -0
  311. package/python/tests/test_readme_claims.py +163 -0
  312. package/python/tests/test_reasoner.py +2 -17
  313. package/python/tests/test_release_check.py +2 -13
  314. package/python/tests/test_resume_checkpoint.py +7 -15
  315. package/python/tests/test_routing.py +2 -12
  316. package/python/tests/test_scheduler.py +7 -10
  317. package/python/tests/test_search.py +1 -11
  318. package/python/tests/test_secrets.py +4 -10
  319. package/python/tests/test_semantic.py +1 -10
  320. package/python/tests/test_sensitive_write.py +181 -0
  321. package/python/tests/test_sessions.py +3 -4
  322. package/python/tests/test_settings.py +2 -18
  323. package/python/tests/test_shadow_learning.py +3 -43
  324. package/python/tests/test_shared_write.py +0 -7
  325. package/python/tests/test_skill_taint_gate.py +1 -10
  326. package/python/tests/test_skills.py +2 -18
  327. package/python/tests/test_skills_fetch.py +282 -0
  328. package/python/tests/test_stats_and_events.py +2 -5
  329. package/python/tests/test_streaming.py +2 -7
  330. package/python/tests/test_supervisor_pipeline.py +4 -5
  331. package/python/tests/test_supervisor_triage.py +4 -10
  332. package/python/tests/test_taint.py +2 -7
  333. package/python/tests/test_task_stream.py +1 -12
  334. package/python/tests/test_thinking_sse.py +0 -1
  335. package/python/tests/test_tool_center.py +1 -17
  336. package/python/tests/test_tool_parallel.py +2 -2
  337. package/python/tests/test_tool_params.py +1 -5
  338. package/python/tests/test_tool_use.py +0 -13
  339. package/python/tests/test_toplevel_concurrency.py +3 -4
  340. package/python/tests/test_tracing.py +7 -41
  341. package/python/tests/test_truth_crystallizer.py +4 -6
  342. package/python/tests/test_truth_flow.py +2 -7
  343. package/python/tests/test_update_check.py +95 -0
  344. package/python/tests/test_verify_evidence.py +8 -21
  345. package/python/tests/test_version_sync.py +35 -6
  346. package/python/tests/test_web_trigger.py +0 -17
  347. package/python/tools/build_content_lexicon.py +218 -0
  348. package/python/tools/bundle_web.py +98 -0
  349. package/python/tools/check_faiss_type.py +0 -1
  350. package/python/tools/check_log.ps1 +0 -8
  351. package/python/tools/mypy_baseline.txt +12 -4
  352. package/python/tools/mypy_ratchet.py +52 -6
  353. package/python/tools/pack.py +0 -5
  354. package/python/tools/recheck_conflicts.py +156 -0
  355. package/python/tools/release_check.py +24 -15
  356. package/python/tools/restart_foxclaw.ps1 +5 -8
  357. package/python/tools/shared_write_audit.py +1 -15
  358. package/python/tools/size_check.py +1 -1
  359. package/python/tools/strip_comments.py +228 -0
  360. package/python/vendor/hkc/hkc_api/routes/ability.py +31 -0
  361. package/python/vendor/hkc/hkc_api/schemas.py +11 -0
  362. package/python/vendor/hkc/hkc_kee/kee.py +124 -3
  363. package/python/vendor/hkc/hkc_sdk/base.py +22 -0
  364. package/python/vendor/hkc/hkc_sdk/direct_client.py +10 -0
  365. package/python/vendor/hkc/hkc_sdk/http_client.py +19 -0
  366. package/python/foxclaw/api/app.py +0 -123
  367. package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
  368. package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
@@ -4,17 +4,30 @@
4
4
  等实例状态协同工作。纯搬移、零行为变更。
5
5
  """
6
6
  import os
7
+ from datetime import datetime, timezone
8
+ from pathlib import Path
9
+ from typing import Any, TYPE_CHECKING
7
10
 
8
11
  import numpy as np
9
12
  import yaml
10
- from datetime import datetime, timezone
11
13
 
12
14
  from ..core.thought_chain import ThoughtNode
13
15
  from ._base import HarnessResult
14
16
 
17
+ if TYPE_CHECKING:
18
+ from .event_bus import HarnessEventBus
19
+
15
20
 
16
21
  class PolicyMixin:
17
- # ── 学习规则的 action 编译 / 应用 ─────────────────────────
22
+ agent_id: str
23
+ bus: "HarnessEventBus"
24
+ policy_path: Path
25
+ weights: dict[str, float]
26
+ promote_min_obs: int
27
+ _policy: dict
28
+ _baseline: Any
29
+ _delta_history: list[float]
30
+
18
31
 
19
32
  def _reindex_learned_actions(self):
20
33
  """把 self._policy['rules'] 里已学习的 action 编译成 check() 用得上的结构。
@@ -35,7 +48,6 @@ class PolicyMixin:
35
48
  elif atype == "restrict_scope":
36
49
  lim = action.get("entity_expansion_limit")
37
50
  if isinstance(lim, int):
38
- # 多条 restrict 取最严(最小)上限
39
51
  scope_limit = lim if scope_limit is None else min(scope_limit, lim)
40
52
  self._penalized_tools = penalized
41
53
  self._scope_limit = scope_limit
@@ -46,11 +58,7 @@ class PolicyMixin:
46
58
  return False
47
59
  return any(tool_name == p or p in tool_name for p in self._penalized_tools)
48
60
 
49
- # ── Policy 学习(P0-1:影子验证 + 回滚 + 边界)────────────────
50
61
 
51
- # 【P0-1 边界】可生成影子规则的维度。semantic 可学但**仅权重级**(不生成规则——
52
- # 原 increase_semantic_threshold 规则本就无规则级应用,判定行为不变);
53
- # 不在 weights 里的未知维度 → 只记录不学习(fail-closed,防未来新维度被默认自学)。
54
62
  _LEARNABLE_RULE_DIMS = frozenset({"tool", "scope"})
55
63
 
56
64
  def _trigger_policy_learning(self, node: ThoughtNode, result: HarnessResult):
@@ -64,16 +72,14 @@ class PolicyMixin:
64
72
  """
65
73
  dim = result.dominant_dim
66
74
 
67
- # ── 边界:未知维度 fail-closed,只记录不学习(权重也不动)──
68
75
  if dim not in self.weights:
69
76
  self._record_history("skip", "",
70
77
  note=f"未知维度 '{dim}' 禁止自学习(fail-closed)")
71
78
  self._save_policy()
72
79
  return
73
80
 
74
- # ── 规则级:tool/scope 生成影子候选;semantic 仅权重级 ──
75
81
  if dim in self._LEARNABLE_RULE_DIMS:
76
- new_rule = {
82
+ new_rule: dict[str, Any] = {
77
83
  "id": f"rule_{node.id}",
78
84
  "trigger": {
79
85
  "harness_status": "BLOCK",
@@ -84,8 +90,6 @@ class PolicyMixin:
84
90
  "created_at": datetime.now(timezone.utc).isoformat(),
85
91
  "source": "harness_learned",
86
92
  "agent_id": self.agent_id,
87
- # 影子账本:observations=适用次数;would_block=若生效会多拦;
88
- # would_pass=若生效会多放。三者是 promote/discard 的全部证据。
89
93
  "shadow": {
90
94
  "created_at": datetime.now(timezone.utc).isoformat(),
91
95
  "observations": 0, "would_block": 0, "would_pass": 0,
@@ -95,19 +99,12 @@ class PolicyMixin:
95
99
  self._policy.setdefault("shadow_rules", []).append(new_rule)
96
100
  self._record_history("propose", new_rule["id"],
97
101
  note=f"{dim} 维度 BLOCK → 影子观察")
98
- # 不 _reindex_learned_actions —— 影子规则**不得**影响判定
99
102
  self.bus.emit("shadow_rule_proposed", {
100
103
  "agent_id": self.agent_id, "rule_id": new_rule["id"],
101
104
  "dominant_dim": dim,
102
105
  "action_type": new_rule["action"].get("type", ""),
103
106
  })
104
107
 
105
- # 更新权重:触发截断的维度权重 +δ
106
- # 必须**先把新权重算完整再整体赋值**,不能"原地 +0.05 再归一化":
107
- # 后者会让 self.weights 出现「各维之和 = 1.05」的瞬时中间态,而此刻若有人读到它
108
- # (get_policy_summary / policy_updated 的监听者),拿到的就是未归一化的错值。
109
- # 实测:同一份 summary 持有的 weights 引用会被读出 0.35,而稍后再取是 0.333 —— 正是
110
- # 这个中间态被观察到。
111
108
  bumped = dict(self.weights)
112
109
  bumped[dim] = min(bumped[dim] + 0.05, 0.8)
113
110
  total = sum(bumped.values())
@@ -121,7 +118,6 @@ class PolicyMixin:
121
118
  "new_weights": dict(self.weights),
122
119
  })
123
120
 
124
- # ── 影子观察(双轨判定)──────────────────────────────────
125
121
 
126
122
  def _observe_shadow_rules(self, node: ThoughtNode, result: HarnessResult,
127
123
  real_scores: dict):
@@ -146,7 +142,6 @@ class PolicyMixin:
146
142
 
147
143
  sh["observations"] = sh.get("observations", 0) + 1
148
144
 
149
- # 双轨:把该规则的 action 应用到 scores 副本,用**同一套权重与阈值**判定
150
145
  shadow_status = self._status_with_action(rule, node, real_scores)
151
146
  real_blocked = result.is_blocked
152
147
  shadow_blocked = shadow_status == "BLOCK"
@@ -163,7 +158,7 @@ class PolicyMixin:
163
158
  self._discard_rule(rule, "影子证据不达标")
164
159
  need_save = True
165
160
  elif sh["observations"] % 10 == 0:
166
- need_save = True # 计数节流持久化
161
+ need_save = True
167
162
  if need_save:
168
163
  self._save_policy()
169
164
 
@@ -176,13 +171,9 @@ class PolicyMixin:
176
171
  if atype == "penalize_tool":
177
172
  pat = action.get("tool_pattern", "")
178
173
  tool_name = getattr(node, "tool_name", "") or ""
179
- # 与 _is_penalized_tool 同语义:**仅当前工具命中 pattern 才抬分**。
180
- # 若不看匹配、凡 tool 分非零就抬,会把「毫不相干工具的场景」误记成
181
- # would_block —— 影子证据完全失真,好规则会被冤判成误伤 discard。
182
174
  if pat and tool_name and (tool_name == pat or pat in tool_name):
183
175
  scores["tool"] = 1.0
184
176
  elif atype == "restrict_scope":
185
- # 与 check() 里 restrict_scope 的判定同构:新增实体超限则抬 scope
186
177
  lim = action.get("entity_expansion_limit")
187
178
  baseline = self._baseline
188
179
  if isinstance(lim, int) and baseline is not None and baseline.baseline_entities:
@@ -202,10 +193,8 @@ class PolicyMixin:
202
193
  if obs < self.promote_min_obs:
203
194
  return None
204
195
  wb, wp = sh.get("would_block", 0), sh.get("would_pass", 0)
205
- # 会多拦很多(可能误伤)→ 不 promote
206
196
  if wb / max(obs, 1) > 0.3:
207
197
  return False
208
- # 几乎不改变判定 → promote 没意义
209
198
  if wb == 0 and wp == 0:
210
199
  return False
211
200
  return True
@@ -214,7 +203,7 @@ class PolicyMixin:
214
203
  """影子 → 生效:移入 rules、重建索引、记 history、发事件。"""
215
204
  rule.get("shadow", {})["status"] = "promoted"
216
205
  self._policy.setdefault("shadow_rules", []).remove(rule)
217
- rule.pop("shadow", None) # 生效规则不再背影子账本
206
+ rule.pop("shadow", None)
218
207
  self._policy.setdefault("rules", []).append(rule)
219
208
  self._reindex_learned_actions()
220
209
  self._record_history("promote", rule["id"],
@@ -303,7 +292,6 @@ class PolicyMixin:
303
292
  "delta": -0.05,
304
293
  }
305
294
 
306
- # ── 阈值校准 ───────────────────────────────────────────
307
295
 
308
296
  def _calibrate_thresholds(self):
309
297
  """Shadow mode 结束 — 用真实 Δ 分布校准阈值。
@@ -311,20 +299,15 @@ class PolicyMixin:
311
299
  只有真正离群的(如完全跑题)才被拦截。
312
300
  否则用中位数附近的分位数会拦掉一半正常流量。
313
301
  """
314
- arr = np.array(sorted(self._delta_history), dtype=float)
302
+ arr = np.array(sorted(self._delta_history), dtype=np.float64)
315
303
  if arr.size == 0:
316
- # 空历史(理论上不会发生,因调用前每次 check 都 append)→ 用默认阈值,避免 nan
317
304
  self._shadow_mode = False
318
305
  return
319
306
  mean = float(arr.mean())
320
307
  std = float(arr.std())
321
308
  p90 = float(np.percentile(arr, 90))
322
- # BLOCK 阈值取"高分位 / 均值+1.5σ"的较大者,再留 10% 余量,
323
- # 保证正常任务(集中在均值附近)不被误拦
324
309
  block = max(p90, mean + 1.5 * std) * 1.10
325
- # WARN 阈值取 block 的 0.8 倍,给出渐进预警区
326
310
  warn = block * 0.8
327
- # 下限保护:阈值不低于默认值,避免样本噪声把阈值压得过紧
328
311
  self._block_threshold = max(block, 0.25)
329
312
  self._warn_threshold = max(warn, 0.18)
330
313
  self._shadow_mode = False
@@ -337,13 +320,11 @@ class PolicyMixin:
337
320
  "std": round(std, 3),
338
321
  })
339
322
 
340
- # ── Policy 持久化 ──────────────────────────────────────
341
323
 
342
324
  def _load_policy(self) -> dict:
343
325
  if self.policy_path.exists():
344
326
  with open(self.policy_path, encoding="utf-8") as f:
345
327
  return yaml.safe_load(f) or {}
346
- # 默认 Policy 骨架(格式从第一天锁定)
347
328
  return {
348
329
  "version": 1,
349
330
  "agent_id": self.agent_id,
@@ -7,18 +7,12 @@ from sklearn.metrics.pairwise import cosine_similarity
7
7
  from ._base import TOOL_CATEGORIES
8
8
 
9
9
 
10
- # 文本向量由 foxclaw.capability.embedding_service 统一提供(进程内共享,同名只加载一次)。
11
- # 不可用时该服务返回 None,SemanticDrift 自动降级为 n-gram 哈希嵌入。
12
-
13
-
14
10
  class SemanticDrift:
15
11
  """维度一:语义距离。优先 sentence-transformers,不可用则降级 n-gram 哈希嵌入。"""
16
12
 
17
13
  DIM = 256
18
14
 
19
15
  def __init__(self, model_name: str = "all-MiniLM-L6-v2"):
20
- # P0:走进程内共享 EmbeddingService,同名模型全系统只加载一次
21
- # (原来每个 Harness 各 load 一份;5 Agent = 5 份 → 现 1 份共享,只读并发 encode 安全)。
22
16
  from ..capability.embedding_service import get_text_embedder
23
17
  self._model = get_text_embedder(model_name)
24
18
  self._mode = "transformer" if self._model is not None else "hash"
@@ -58,8 +52,6 @@ class SemanticDrift:
58
52
  return 0.0
59
53
  node_vec = self._encode(content)
60
54
  sim = cosine_similarity([baseline_vec], [node_vec])[0][0]
61
- # 余弦相似度范围 [-1, 1],哈希嵌入带符号累加可能产生负相似度,
62
- # 1 - sim 会超过 1.0 → 必须双向裁剪到 [0, 1],否则漂移分超出阈值范围使判定失真。
63
55
  return float(min(1.0, max(0.0, 1.0 - sim)))
64
56
 
65
57
 
@@ -72,17 +64,13 @@ class ToolIntentDrift:
72
64
  for cat, patterns in TOOL_CATEGORIES.items():
73
65
  if any(tool_name.startswith(p) or p in tool_name for p in patterns):
74
66
  return 0.0 if cat in allowed_categories else 1.0
75
- return 0.3 # 未知工具:中等风险
67
+ return 0.3
76
68
 
77
69
 
78
70
  class ScopeCreepDrift:
79
71
  """维度三:范围蔓延"""
80
72
 
81
73
  def score(self, node_entities: set[str], baseline_entities: set[str]) -> float:
82
- # 空基线返回 0 是有意设计:范围蔓延是"相对基线的偏离",没有基线参照就无从
83
- # 谈蔓延。若此处对空基线返回高分,会让所有尚未建立实体基线的早期任务被误判为
84
- # 范围越界(误报)。范围检测的有效性依赖基线实体提取——若该维度长期为 0,
85
- # 应排查实体提取(register_goal 的 entities)是否为空,而非改动本函数。
86
74
  if not baseline_entities:
87
75
  return 0.0
88
76
  new_entities = node_entities - baseline_entities
@@ -5,25 +5,22 @@ from typing import Callable
5
5
  class HarnessEventBus:
6
6
  """阶段一:事件总线(观测优先)"""
7
7
 
8
- # 高频且纯内部的信号不上脊柱,避免 WS 噪音(见审计"哪些事件不应该广播")
9
8
  _SPINE_SKIP = {"goal_registered"}
10
9
 
11
10
  def __init__(self, spine=None, source: str = ""):
12
11
  self._handlers: dict[str, list[Callable]] = {}
13
- self._spine = spine # 【P0·统一脊柱】非空则把 emit 同时转发进全局 EventSpine
14
- self._source = source # 事件来源(agent_id),便于投影侧区分
12
+ self._spine = spine
13
+ self._source = source
15
14
 
16
15
  def on(self, event: str, handler: Callable):
17
16
  self._handlers.setdefault(event, []).append(handler)
18
17
 
19
18
  def emit(self, event: str, data: dict):
20
- # 1) 本地订阅者(向后兼容:agent.harness.bus.on / harness 自订阅 / governance)
21
19
  for h in self._handlers.get(event, []):
22
20
  try:
23
21
  h(data)
24
22
  except Exception as e:
25
23
  print(f"[Harness EventBus] handler error: {e}")
26
- # 2) 汇入全局脊柱:harness 事件与系统事件走同一条脊柱、被同一 WS 投影观测
27
24
  if self._spine is not None and event not in self._SPINE_SKIP:
28
25
  try:
29
26
  self._spine.publish(event, data, source=self._source or "harness")
@@ -20,7 +20,6 @@ from typing import Optional
20
20
 
21
21
  from ..core.thought_chain import ThoughtChain, ThoughtNode, HarnessStatus
22
22
 
23
- # ── S3 步骤 4:按检测维度拆分(纯搬移、零行为变更)────────────────────────
24
23
  from ._base import (
25
24
  HarnessResult,
26
25
  GoalBaseline,
@@ -32,7 +31,6 @@ from .drift import SemanticDrift, ToolIntentDrift, ScopeCreepDrift
32
31
  from .event_bus import HarnessEventBus
33
32
  from ._policy import PolicyMixin
34
33
 
35
- # 历史导入兼容:`from foxclaw.harness.harness import X` 的 X 在此 re-export。
36
34
  __all__ = [
37
35
  "Harness", "HarnessResult", "GoalBaseline",
38
36
  "SemanticDrift", "ToolIntentDrift", "ScopeCreepDrift",
@@ -51,18 +49,10 @@ class Harness(PolicyMixin):
51
49
  thought_chain: ThoughtChain,
52
50
  policy_path: str = "./foxclaw_data/policy.yaml",
53
51
  shadow_rounds: int = 20,
54
- embedding_model: str = None,
55
- spine=None, # 【P0】全局 EventSpine(可空:standalone/governance 场景)
56
- promote_min_obs: int = None, # 【P0-1】影子规则升迁所需观察次数(None→env/默认 50)
52
+ embedding_model: str | None = None,
53
+ spine=None,
54
+ promote_min_obs: int | None = None,
57
55
  ):
58
- # embedding 模型解析 —— 【P2·单 embedding 入口】优先级:
59
- # 1. 显式传参(测试/单 Agent 切模型用)
60
- # 2. EMBEDDING_MODEL(系统唯一本地 embedding 入口,.env 一处配 bge-m3 全局生效)
61
- # 3. HKC_EMBEDDING_MODEL(向后兼容,EMBEDDING_MODEL 未设时回退)
62
- # 4. FOXCLAW_EMBEDDING_MODEL(向后兼容)
63
- # 5. all-MiniLM-L6-v2(传统兜底)
64
- # 中文部署设 paraphrase-multilingual-MiniLM-L12-v2 时,语义漂移对中文硬拦率
65
- # 从 0 升到 1.0(见 evals/governance/compare_embeddings.py)。
66
56
  embedding_model = (embedding_model
67
57
  or os.environ.get("EMBEDDING_MODEL")
68
58
  or os.environ.get("HKC_EMBEDDING_MODEL")
@@ -72,83 +62,47 @@ class Harness(PolicyMixin):
72
62
  self.thought_chain = thought_chain
73
63
  self.policy_path = Path(policy_path).resolve()
74
64
  self.shadow_rounds = shadow_rounds
75
- # per-实例分槽 token(目标基线走 contextvar,见文件顶部)
76
65
  self._ctx_token = uuid.uuid4().hex
77
- # 累积学习态(_delta_history/_run_count/weights/_policy/阈值)的并发锁。
78
- # 为把请求 offload 到线程池提速做准备:多线程会并发调 check 打同一 harness,
79
- # 昂贵的语义嵌入放锁外(线程安全、可并行),只有廉价的累积态改写进锁。RLock:
80
- # _trigger_policy_learning 在锁内调用,同线程可重入。
81
66
  self._lock = threading.RLock()
82
67
 
83
- # 检测引擎
84
68
  self.sem_drift = SemanticDrift(embedding_model)
85
69
  self.tool_drift = ToolIntentDrift()
86
70
  self.scope_drift = ScopeCreepDrift()
87
71
 
88
- # 事件总线:本地订阅(向后兼容)+ 转发进全局脊柱(spine 非空时)
89
72
  self.bus = HarnessEventBus(spine=spine, source=agent_id)
90
73
 
91
- # 目标基线(每任务)—— 由 contextvar 承载;_baseline 是路由到 contextvar 的
92
- # property,register_goal/check 里对 self._baseline 的读写代码完全不变。
93
74
  self._baseline = None
94
75
 
95
- # 权重(Policy 学习后动态调整)
96
76
  self.weights = {"semantic": 0.5, "tool": 0.3, "scope": 0.2}
97
77
 
98
- # 是否因「语义漂移」而 BLOCK。领域 Agent=True(执行业务任务时跑题要拦);
99
- # 调度层(主管)=False(接收任意输入是其职责,语义距离大不算异常)。
100
78
  self.block_on_semantic = True
101
79
 
102
- # ── 降级方向修正(B 类修复)────────────────────────────────────
103
- # 语义嵌入不可用时(SemanticDrift.mode == "hash"),3-gram 哈希对中文几乎
104
- # 无区分度 —— 实测同域相似度 -0.17、跨域 +0.00,差异 <0.17 → 漂移分
105
- # 1-sim 裁剪后**恒为 1.0**。该维度一旦参与加权(semantic 权重 0.5),
106
- # Δ 恒 0.5 > block 阈值 0.25 → **所有请求一律 BLOCK**,包括评测集里
107
- # ground-truth 标注为 pass 的正常请求。
108
- # 治理层从「精准拦截」退化为「无差别拦截」,比不治理更危险(阻塞全部正常
109
- # 业务),且失败是静默的(仅 mode 属性变化,无告警)。
110
- #
111
- # 正确处置:安全组件的降级方向必须是 **fail-open(失效但可见)**,
112
- # 而不是 fail-closed-with-garbage(过度拦截且静默)。故降级时**停用语义
113
- # 维度**,而 tool/scope 两个维度是确定性规则(工具类别匹配、实体集合运算),
114
- # 不依赖模型质量,降级后仍可信,照常工作。
115
80
  self.semantic_degraded: bool = (self.sem_drift.mode != "transformer")
116
- # 降级只告警一次,避免每节点刷屏
117
81
  self._degrade_warned: bool = False
118
82
 
119
- # Shadow mode 状态
120
83
  self._shadow_mode = True
121
84
  self._run_count = 0
122
85
  self._delta_history: list[float] = []
123
86
 
124
- # 阈值(shadow mode 结束后用真实数据替换)
125
87
  self._warn_threshold = 0.15
126
88
  self._block_threshold = 0.25
127
89
 
128
- # Policy
129
90
  self._policy: dict = self._load_policy()
130
- # 【P0-1】影子学习段(旧 YAML 没有 → 补空段;不重命名既有键,版本兼容)
131
91
  self._policy.setdefault("shadow_rules", [])
132
92
  self._policy.setdefault("history", [])
133
93
 
134
- # 【P0-1】影子观察达标线:observations 凑满才做 promote/discard 判定。
135
- # 显式构造参数 > 环境变量 > 默认 50(文稿建议值)。个人级低频使用下一条影子
136
- # 规则可能长期 observing —— 这正是影子的目的:证据不够就不生效。
137
94
  if promote_min_obs is not None:
138
95
  self.promote_min_obs = promote_min_obs
139
96
  else:
140
97
  _env = os.environ.get("FOXCLAW_SHADOW_PROMOTE_MIN_OBS", "").strip()
141
98
  self.promote_min_obs = int(_env) if _env.isdigit() else 50
142
99
 
143
- # 把已学习规则的 action 编译成 check() 可直接应用的结构(规则级生效)
144
100
  self._penalized_tools: set[str] = set()
145
101
  self._scope_limit: Optional[int] = None
146
102
  self._reindex_learned_actions()
147
103
 
148
- # 注册默认事件处理
149
104
  self._setup_default_handlers()
150
105
 
151
- # ── 目标基线(每任务,contextvar 支撑,property 透明路由)──────
152
106
  @property
153
107
  def _baseline(self):
154
108
  return _harness_ctx_get(self._ctx_token)
@@ -157,13 +111,12 @@ class Harness(PolicyMixin):
157
111
  def _baseline(self, value):
158
112
  _harness_ctx_set(self._ctx_token, value)
159
113
 
160
- # ── 目标基线注册 ───────────────────────────────────────
161
114
 
162
115
  def register_goal(
163
116
  self,
164
117
  goal_text: str,
165
118
  allowed_tool_categories: set[str],
166
- baseline_entities: set[str] = None,
119
+ baseline_entities: set[str] | None = None,
167
120
  ) -> GoalBaseline:
168
121
  """接口层调用 — 注册任务目标基线"""
169
122
  baseline = GoalBaseline(
@@ -171,14 +124,11 @@ class Harness(PolicyMixin):
171
124
  allowed_tool_categories=allowed_tool_categories,
172
125
  baseline_entities=baseline_entities or set(),
173
126
  )
174
- # 用 encode(纯函数)算每任务自持的基线向量,不写共享的 _baseline_vec →
175
- # 并发顶层任务的语义基线互不覆盖(check 用 self._baseline.goal_embedding 打分)。
176
127
  baseline.goal_embedding = self.sem_drift.encode(goal_text)
177
128
  self._baseline = baseline
178
129
  self.bus.emit("goal_registered", baseline.to_dict())
179
130
  return baseline
180
131
 
181
- # ── 核心校验方法 ───────────────────────────────────────
182
132
 
183
133
  def check(self, node: ThoughtNode, ignore_semantic: bool = False) -> HarnessResult:
184
134
  """
@@ -195,9 +145,8 @@ class Harness(PolicyMixin):
195
145
  停用不可信维度,保留可信的 tool/scope 判定,并在 reason 显式标注降级
196
146
  (失效但可见)。走 _check_degraded 分支,正常路径逻辑逐字不变。
197
147
  """
198
- baseline = self._baseline # contextvar 读(每任务/线程独立)
148
+ baseline = self._baseline
199
149
  if baseline is None:
200
- # 没有注册目标基线,只观测不截断
201
150
  result = HarnessResult(
202
151
  status="PASS", delta=0.0, dominant_dim="none",
203
152
  scores={"semantic": 0.0, "tool": 0.0, "scope": 0.0},
@@ -208,12 +157,9 @@ class Harness(PolicyMixin):
208
157
  if self.semantic_degraded and not ignore_semantic:
209
158
  return self._check_degraded(node)
210
159
 
211
- # 语义嵌入(昂贵、线程安全)—— **放锁外**,使请求 offload 到线程池后,并发请求的
212
- # 嵌入计算真正并行(torch/ST CPU 前向会释放 GIL)。这是吞吐提速的关键点。
213
160
  sem_score = 0.0 if ignore_semantic else \
214
161
  self.sem_drift.score(node.content, baseline.goal_embedding)
215
162
 
216
- # 以下累积态的读改写进锁(廉价):weights/_delta_history/_run_count/阈值/_policy。
217
163
  with self._lock:
218
164
  scores = {
219
165
  "semantic": sem_score,
@@ -224,10 +170,6 @@ class Harness(PolicyMixin):
224
170
  set(node.entities), baseline.baseline_entities),
225
171
  }
226
172
 
227
- # 应用已学习规则的 action(规则级生效,区别于权重级自学习):
228
- # · penalize_tool —— 之前触发过 BLOCK 的工具,即便类别本在允许集里,也判最高
229
- # 工具风险(1.0),让「学到某工具危险」下次真正拦得住,而非只微调 tool 权重。
230
- # · restrict_scope —— 学到的实体扩张上限;新增实体超限直接判范围越界(scope=1.0)。
231
173
  if node.tool_name and self._is_penalized_tool(node.tool_name):
232
174
  scores["tool"] = 1.0
233
175
  if (self._scope_limit is not None and baseline.baseline_entities):
@@ -236,27 +178,19 @@ class Harness(PolicyMixin):
236
178
  scores["scope"] = 1.0
237
179
 
238
180
  delta = sum(self.weights[k] * v for k, v in scores.items())
239
- dominant = max(scores, key=scores.get)
181
+ dominant = max(scores, key=lambda k: scores[k])
240
182
 
241
- # 更新 shadow mode 历史。【深审修复·长期运行泄漏】原先**无条件**每次 check 都 append,
242
- # 但 _delta_history 仅在 _calibrate_thresholds(shadow 结束时跑一次)被读;shadow_mode 只在
243
- # init 置 True、永不重入,故校准后继续 append 是**纯无界泄漏**(每个推理节点一个 float)。
244
- # 改为**仅 shadow 期 append**:样本上限=shadow_rounds,校准看到的样本完全一致(行为不变)。
245
183
  if self._shadow_mode:
246
184
  self._delta_history.append(delta)
247
185
  self._run_count += 1
248
186
 
249
- # Shadow mode 结束:用真实数据校准阈值
250
187
  if self._shadow_mode and self._run_count >= self.shadow_rounds:
251
188
  self._calibrate_thresholds()
252
189
 
253
- # 判断状态
254
190
  if self._shadow_mode:
255
- status: HarnessStatus = "PASS" # 观测期永不截断
191
+ status: HarnessStatus = "PASS"
256
192
  reason = None
257
193
  elif delta > self._block_threshold:
258
- # 调度层(如主管)豁免「语义漂移」拦截:它的职责就是接收任意输入,
259
- # 语义距离天然大,不应因此 BLOCK。但工具越权/范围越界仍然拦截。
260
194
  if not self.block_on_semantic and dominant == "semantic":
261
195
  status = "WARN"
262
196
  reason = f"semantic drift Δ={delta:.3f}(调度层仅告警不拦截)"
@@ -275,19 +209,12 @@ class Harness(PolicyMixin):
275
209
  scores=scores, reason=reason,
276
210
  )
277
211
 
278
- # 【P0-1】影子规则双轨观察(锁内):用**已算好的 scores** 模拟应用影子规则的
279
- # action,只记录 would_block/would_pass,绝不改变上面的 real result。
280
- # 不重新做语义嵌入 —— 开销是 O(观察中规则数) 的纯算术,远优于文稿预估的 2x。
281
- # 顺序有意**先于**下方的 BLOCK 学习:本次 check 是对**既有**候选的一次观察样本;
282
- # 本次 BLOCK 诞生的新候选从**下一次** check 才开始积累证据(诞生事件≠观察样本)。
283
212
  if self._policy.get("shadow_rules"):
284
213
  self._observe_shadow_rules(node, result, scores)
285
214
 
286
- # BLOCK:触发 Policy 学习(mutates weights/_policy,须在锁内)
287
215
  if status == "BLOCK":
288
216
  self._trigger_policy_learning(node, result)
289
217
 
290
- # 锁外:SQLite 写(WAL 线程安全)+ 事件广播(HarnessEventBus 独立)
291
218
  self.thought_chain.update_harness(node.id, status, reason, delta, dominant)
292
219
  self.bus.emit(f"harness_{status.lower()}", {
293
220
  "agent_id": self.agent_id, "node_id": node.id, "node_type": node.type,
@@ -311,7 +238,6 @@ class Harness(PolicyMixin):
311
238
  baseline = self._baseline
312
239
  if not self._degrade_warned:
313
240
  self._degrade_warned = True
314
- # 一次性告警:降级 = 治理能力缺失,属需人工知晓的事件,不能只靠属性静默。
315
241
  import logging as _lg
316
242
  _lg.getLogger("foxclaw.harness").warning(
317
243
  "[harness] 语义嵌入不可用(mode=%s),语义漂移维度已停用 —— "
@@ -320,8 +246,6 @@ class Harness(PolicyMixin):
320
246
  self.sem_drift.mode)
321
247
 
322
248
  with self._lock:
323
- # 降级期间不污染 shadow 样本:把语义分置 0 后再 append,否则恒 1.0 的
324
- # 语义分会污染阈值校准,装回 ST 后阈值仍不准。
325
249
  saved_w = self.weights.get("semantic", 0.0)
326
250
  self.weights["semantic"] = 0.0
327
251
  try:
@@ -333,7 +257,6 @@ class Harness(PolicyMixin):
333
257
  "scope": self.scope_drift.score(
334
258
  set(node.entities), baseline.baseline_entities),
335
259
  }
336
- # 规则级学习(action)照常生效 —— 这部分不依赖 embedding
337
260
  if node.tool_name and self._is_penalized_tool(node.tool_name):
338
261
  scores["tool"] = 1.0
339
262
  if (self._scope_limit is not None and baseline.baseline_entities):
@@ -342,7 +265,7 @@ class Harness(PolicyMixin):
342
265
  scores["scope"] = 1.0
343
266
 
344
267
  delta = sum(self.weights[k] * v for k, v in scores.items())
345
- dominant = max(scores, key=scores.get)
268
+ dominant = max(scores, key=lambda k: scores[k])
346
269
  if self._shadow_mode:
347
270
  self._delta_history.append(delta)
348
271
  self._run_count += 1
@@ -350,7 +273,7 @@ class Harness(PolicyMixin):
350
273
  self._calibrate_thresholds()
351
274
 
352
275
  if self._shadow_mode:
353
- status: HarnessStatus = "PASS" # 观测期永不截断
276
+ status: HarnessStatus = "PASS"
354
277
  reason = None
355
278
  elif delta > self._block_threshold:
356
279
  status = "BLOCK"
@@ -361,7 +284,6 @@ class Harness(PolicyMixin):
361
284
  else:
362
285
  status = "PASS"
363
286
  reason = None
364
- # 降级标记:让上层/前端能识别「本次判定不含语义维度」(失效但可见)
365
287
  reason = ((reason + " [semantic-degraded:治理降级]") if reason
366
288
  else "[semantic-degraded:语义维度停用]")
367
289
  finally:
@@ -376,7 +298,6 @@ class Harness(PolicyMixin):
376
298
  if status == "BLOCK":
377
299
  self._trigger_policy_learning(node, result)
378
300
 
379
- # 锁外:SQLite 写 + 事件广播(同 check,额外带 degraded 标记)
380
301
  self.thought_chain.update_harness(node.id, status, reason, delta, dominant)
381
302
  self.bus.emit(f"harness_{status.lower()}", {
382
303
  "agent_id": self.agent_id, "node_id": node.id, "node_type": node.type,
@@ -385,7 +306,6 @@ class Harness(PolicyMixin):
385
306
  })
386
307
  return result
387
308
 
388
- # ── 默认事件处理 ───────────────────────────────────────
389
309
 
390
310
  def _setup_default_handlers(self):
391
311
  def on_block(data):
@@ -405,7 +325,6 @@ class Harness(PolicyMixin):
405
325
  self.bus.on("harness_block", on_block)
406
326
  self.bus.on("policy_updated", on_policy_updated)
407
327
 
408
- # ── 状态查询 ───────────────────────────────────────────
409
328
 
410
329
  @property
411
330
  def is_shadow_mode(self) -> bool:
@@ -419,10 +338,6 @@ class Harness(PolicyMixin):
419
338
  "shadow_mode": self._shadow_mode,
420
339
  "run_count": self._run_count,
421
340
  "shadow_rounds": self.shadow_rounds,
422
- # ── 降级可见性(B 类修复)────────────────────────────────
423
- # 语义嵌入是否降级。semantic_degraded=True 意味着漂移检测只剩
424
- # tool/scope 两维,**跑题不会被拦截** —— 这是需要人工知晓的治理
425
- # 能力缺失,不能只体现在日志里。
426
341
  "semantic_degraded": self.semantic_degraded,
427
342
  "embedding_mode": self.sem_drift.mode,
428
343
  }
@@ -435,12 +350,9 @@ class Harness(PolicyMixin):
435
350
  "total_rules": len(rules),
436
351
  "manual_rules": sum(1 for r in rules if r.get("source") == "manual"),
437
352
  "learned_rules": sum(1 for r in rules if r.get("source") == "harness_learned"),
438
- # 【P0-1】影子学习账本:observing=观察中(不生效);discarded/rolled_back 保留供审计
439
353
  "shadow_observing": sum(1 for r in shadow
440
354
  if (r.get("shadow") or {}).get("status") == "observing"),
441
355
  "shadow_total": len(shadow),
442
356
  "history_events": len(self._policy.get("history", [])),
443
- # 返回**副本**:此前直接返回 self.weights 引用 —— 调用方拿到的不是快照,
444
- # 既可能被后续权重更新"隔空改写"(读到的值取决于时机),也能反向改坏内部状态。
445
357
  "weights": dict(self.weights),
446
358
  }