foxclaw 0.1.0 → 0.1.2

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 (495) hide show
  1. package/README.md +54 -29
  2. package/bin/foxclaw.cjs +51 -0
  3. package/index.js +11 -10
  4. package/package.json +46 -38
  5. package/python/.dockerignore +35 -0
  6. package/python/.env.example +165 -0
  7. package/python/.gitattributes +46 -0
  8. package/python/.github/workflows/ci.yml +122 -0
  9. package/python/.github/workflows/release.yml +183 -0
  10. package/python/Dockerfile +64 -0
  11. package/python/LICENSE +21 -0
  12. package/python/MANIFEST.in +14 -0
  13. package/python/README.md +192 -0
  14. package/python/README_CN.md +324 -0
  15. package/python/bench/bm25_audit.py +199 -0
  16. package/python/bench/bm25_regression.py +178 -0
  17. package/python/bench/bm25_stress.py +159 -0
  18. package/python/bench/cd_audit.py +238 -0
  19. package/python/bench/chaos.py +142 -0
  20. package/python/bench/concurrency.py +103 -0
  21. package/python/bench/e2e_real_models.py +95 -0
  22. package/python/bench/llm_retry_test.py +248 -0
  23. package/python/bench/load_test.py +196 -0
  24. package/python/bench/vector_ivf_upgrade.py +134 -0
  25. package/python/docs/CHANNELS.md +195 -0
  26. package/python/docs/GOVERNANCE.md +164 -0
  27. package/python/docs/RELEASE.md +109 -0
  28. package/python/docs/RUN.md +75 -0
  29. package/python/docs/RUN_CN.md +121 -0
  30. package/python/docs//351/205/215/347/275/256/346/214/207/345/215/227.md +152 -0
  31. package/python/evals/__init__.py +0 -0
  32. package/python/evals/governance/README.md +61 -0
  33. package/python/evals/governance/__init__.py +0 -0
  34. package/python/evals/governance/cases.py +152 -0
  35. package/python/evals/governance/compare_embeddings.py +94 -0
  36. package/python/evals/governance/convergence.svg +11 -0
  37. package/python/evals/governance/report.json +371 -0
  38. package/python/evals/governance/run_evals.py +400 -0
  39. package/python/examples/governance_middleware_demo.py +77 -0
  40. package/python/examples/langgraph_governance_demo.py +108 -0
  41. package/python/examples/sample_itinerary.csv +6 -0
  42. package/python/examples/sample_knowledge.csv +9 -0
  43. package/python/examples/sample_transactions.csv +14 -0
  44. package/python/foxclaw/__init__.py +526 -0
  45. package/python/foxclaw/agents/__init__.py +12 -0
  46. package/python/foxclaw/agents/agents.py +35 -0
  47. package/python/foxclaw/agents/base.py +865 -0
  48. package/python/foxclaw/agents/impl/__init__.py +1 -0
  49. package/python/foxclaw/agents/impl/_helpers.py +78 -0
  50. package/python/foxclaw/agents/impl/finance.py +248 -0
  51. package/python/foxclaw/agents/impl/office.py +367 -0
  52. package/python/foxclaw/agents/impl/supervisor.py +659 -0
  53. package/python/foxclaw/agents/mixins/_context.py +151 -0
  54. package/python/foxclaw/agents/mixins/augment.py +149 -0
  55. package/python/foxclaw/agents/mixins/llm.py +66 -0
  56. package/python/foxclaw/agents/mixins/memory.py +35 -0
  57. package/python/foxclaw/agents/mixins/preference.py +81 -0
  58. package/python/foxclaw/agents/mixins/prompt.py +77 -0
  59. package/python/foxclaw/agents/mixins/skillbox.py +58 -0
  60. package/python/foxclaw/agents/mixins/tool_exec.py +443 -0
  61. package/python/foxclaw/api/__init__.py +283 -0
  62. package/python/foxclaw/api/app.py +123 -0
  63. package/python/foxclaw/api/middleware.py +317 -0
  64. package/python/foxclaw/api/routes/ahs.py +417 -0
  65. package/python/foxclaw/api/routes/attachments.py +117 -0
  66. package/python/foxclaw/api/routes/browser.py +39 -0
  67. package/python/foxclaw/api/routes/domain.py +314 -0
  68. package/python/foxclaw/api/routes/knowledge.py +163 -0
  69. package/python/foxclaw/api/routes/llm_admin.py +461 -0
  70. package/python/foxclaw/api/routes/memory.py +89 -0
  71. package/python/foxclaw/api/routes/orchestration.py +41 -0
  72. package/python/foxclaw/api/routes/recovery.py +43 -0
  73. package/python/foxclaw/api/routes/review.py +275 -0
  74. package/python/foxclaw/api/routes/search.py +37 -0
  75. package/python/foxclaw/api/routes/system.py +327 -0
  76. package/python/foxclaw/api/routes/task.py +281 -0
  77. package/python/foxclaw/api/routes/ws.py +68 -0
  78. package/python/foxclaw/api/spine.py +55 -0
  79. package/python/foxclaw/browser/__init__.py +13 -0
  80. package/python/foxclaw/browser/action/__init__.py +1 -0
  81. package/python/foxclaw/browser/action/action_executor.py +54 -0
  82. package/python/foxclaw/browser/action/action_types.py +45 -0
  83. package/python/foxclaw/browser/action/action_verifier.py +68 -0
  84. package/python/foxclaw/browser/action/url_policy.py +147 -0
  85. package/python/foxclaw/browser/adapter/__init__.py +1 -0
  86. package/python/foxclaw/browser/adapter/base_adapter.py +23 -0
  87. package/python/foxclaw/browser/adapter/web_adapter.py +46 -0
  88. package/python/foxclaw/browser/driver/__init__.py +1 -0
  89. package/python/foxclaw/browser/driver/base_driver.py +61 -0
  90. package/python/foxclaw/browser/driver/playwright_driver.py +166 -0
  91. package/python/foxclaw/browser/perception/__init__.py +1 -0
  92. package/python/foxclaw/browser/perception/dom_extractor.py +43 -0
  93. package/python/foxclaw/browser/perception/element_indexer.py +57 -0
  94. package/python/foxclaw/browser/perception/semantic_reducer.py +33 -0
  95. package/python/foxclaw/browser/perception/snapshot.py +29 -0
  96. package/python/foxclaw/browser/service.py +148 -0
  97. package/python/foxclaw/browser/session/__init__.py +1 -0
  98. package/python/foxclaw/browser/session/session_manager.py +57 -0
  99. package/python/foxclaw/capability/__init__.py +12 -0
  100. package/python/foxclaw/capability/embedding_service.py +67 -0
  101. package/python/foxclaw/channels/__init__.py +140 -0
  102. package/python/foxclaw/channels/discord.py +184 -0
  103. package/python/foxclaw/channels/dispatcher.py +204 -0
  104. package/python/foxclaw/channels/feishu.py +225 -0
  105. package/python/foxclaw/channels/openclaw_bridge.py +156 -0
  106. package/python/foxclaw/channels/signing.py +187 -0
  107. package/python/foxclaw/channels/telegram.py +121 -0
  108. package/python/foxclaw/channels/weixin.py +225 -0
  109. package/python/foxclaw/cli.py +154 -0
  110. package/python/foxclaw/cognitive/__init__.py +4 -0
  111. package/python/foxclaw/cognitive/reasoner.py +82 -0
  112. package/python/foxclaw/cognitive/state.py +147 -0
  113. package/python/foxclaw/core/__init__.py +9 -0
  114. package/python/foxclaw/core/audit_chain.py +133 -0
  115. package/python/foxclaw/core/audit_sink.py +100 -0
  116. package/python/foxclaw/core/compaction.py +283 -0
  117. package/python/foxclaw/core/config.py +65 -0
  118. package/python/foxclaw/core/confirm_nonce.py +157 -0
  119. package/python/foxclaw/core/dispatch.py +27 -0
  120. package/python/foxclaw/core/envfile.py +48 -0
  121. package/python/foxclaw/core/failure_policy.py +151 -0
  122. package/python/foxclaw/core/gateway.py +85 -0
  123. package/python/foxclaw/core/governance_audit.py +175 -0
  124. package/python/foxclaw/core/idempotency.py +289 -0
  125. package/python/foxclaw/core/injection_guard.py +221 -0
  126. package/python/foxclaw/core/memory_adapter.py +516 -0
  127. package/python/foxclaw/core/metrics.py +89 -0
  128. package/python/foxclaw/core/model_router.py +1097 -0
  129. package/python/foxclaw/core/paths.py +103 -0
  130. package/python/foxclaw/core/recovery.py +362 -0
  131. package/python/foxclaw/core/scheduler.py +144 -0
  132. package/python/foxclaw/core/secrets.py +134 -0
  133. package/python/foxclaw/core/sessions.py +157 -0
  134. package/python/foxclaw/core/settings.py +430 -0
  135. package/python/foxclaw/core/shared_write.py +198 -0
  136. package/python/foxclaw/core/steering_queue.py +153 -0
  137. package/python/foxclaw/core/system_bus.py +84 -0
  138. package/python/foxclaw/core/thought_chain.py +318 -0
  139. package/python/foxclaw/core/token_meter.py +235 -0
  140. package/python/foxclaw/core/tools.py +172 -0
  141. package/python/foxclaw/core/tracing.py +390 -0
  142. package/python/foxclaw/core/truth_pipeline.py +114 -0
  143. package/python/foxclaw/core/vec_recall_adapter.py +503 -0
  144. package/python/foxclaw/core/verify.py +181 -0
  145. package/python/foxclaw/core/web_search.py +242 -0
  146. package/python/foxclaw/finance/__init__.py +15 -0
  147. package/python/foxclaw/finance/evidence.py +153 -0
  148. package/python/foxclaw/finance/fx.py +100 -0
  149. package/python/foxclaw/finance/graph.py +188 -0
  150. package/python/foxclaw/finance/models.py +150 -0
  151. package/python/foxclaw/finance/nlparse.py +575 -0
  152. package/python/foxclaw/finance/policy.py +329 -0
  153. package/python/foxclaw/finance/reasoner.py +129 -0
  154. package/python/foxclaw/finance/reasoning.py +340 -0
  155. package/python/foxclaw/finance/reconcile.py +180 -0
  156. package/python/foxclaw/finance/runtime.py +527 -0
  157. package/python/foxclaw/governance/__init__.py +24 -0
  158. package/python/foxclaw/governance/middleware.py +184 -0
  159. package/python/foxclaw/harness/__init__.py +17 -0
  160. package/python/foxclaw/harness/_base.py +72 -0
  161. package/python/foxclaw/harness/_policy.py +359 -0
  162. package/python/foxclaw/harness/drift.py +89 -0
  163. package/python/foxclaw/harness/event_bus.py +31 -0
  164. package/python/foxclaw/harness/harness.py +446 -0
  165. package/python/foxclaw/harness/loop_detection.py +152 -0
  166. package/python/foxclaw/itinerary/__init__.py +10 -0
  167. package/python/foxclaw/itinerary/amap_client.py +117 -0
  168. package/python/foxclaw/itinerary/models.py +80 -0
  169. package/python/foxclaw/itinerary/reasoner.py +101 -0
  170. package/python/foxclaw/itinerary/reasoning.py +174 -0
  171. package/python/foxclaw/itinerary/runtime.py +148 -0
  172. package/python/foxclaw/kernel/__init__.py +13 -0
  173. package/python/foxclaw/kernel/capabilities.py +73 -0
  174. package/python/foxclaw/kernel/policy_gate.py +114 -0
  175. package/python/foxclaw/kernel/process_manager.py +205 -0
  176. package/python/foxclaw/kernel/scheduler.py +131 -0
  177. package/python/foxclaw/kernel/sentinel.py +453 -0
  178. package/python/foxclaw/kernel/sentinel_client.py +246 -0
  179. package/python/foxclaw/kernel/spine.py +97 -0
  180. package/python/foxclaw/knowledge/__init__.py +376 -0
  181. package/python/foxclaw/knowledge/truth_crystallizer.py +92 -0
  182. package/python/foxclaw/learning/__init__.py +12 -0
  183. package/python/foxclaw/learning/knowledge_graph.py +149 -0
  184. package/python/foxclaw/learning/models.py +93 -0
  185. package/python/foxclaw/learning/reasoner.py +70 -0
  186. package/python/foxclaw/learning/runtime.py +363 -0
  187. package/python/foxclaw/learning/spaced_repetition.py +96 -0
  188. package/python/foxclaw/mcp_server.py +189 -0
  189. package/python/foxclaw/orchestration/__init__.py +184 -0
  190. package/python/foxclaw/pipeline/__init__.py +23 -0
  191. package/python/foxclaw/pipeline/cognitive_pipeline.py +481 -0
  192. package/python/foxclaw/pipeline/supervisor_stages.py +230 -0
  193. package/python/foxclaw/planning/__init__.py +17 -0
  194. package/python/foxclaw/planning/llm_planner.py +232 -0
  195. package/python/foxclaw/planning/planner.py +99 -0
  196. package/python/foxclaw/search/__init__.py +24 -0
  197. package/python/foxclaw/search/agent_port.py +194 -0
  198. package/python/foxclaw/search/evidence.py +113 -0
  199. package/python/foxclaw/search/external_search.py +50 -0
  200. package/python/foxclaw/search/hkc_read_port.py +60 -0
  201. package/python/foxclaw/search/internal_search.py +73 -0
  202. package/python/foxclaw/search/models.py +129 -0
  203. package/python/foxclaw/search/planner.py +44 -0
  204. package/python/foxclaw/search/retriever.py +39 -0
  205. package/python/foxclaw/search/runtime.py +134 -0
  206. package/python/foxclaw/search/strategy.py +44 -0
  207. package/python/foxclaw/skills/__init__.py +613 -0
  208. package/python/foxclaw/skills/hooks_spec.py +73 -0
  209. package/python/foxclaw/skills/lifecycle.py +278 -0
  210. package/python/foxclaw/skills/markdown_skill.py +174 -0
  211. package/python/foxclaw/skills/markdown_store.py +39 -0
  212. package/python/foxclaw/skills/mcp_client.py +190 -0
  213. package/python/foxclaw/skills/mcp_store.py +43 -0
  214. package/python/foxclaw/skills/permission.py +180 -0
  215. package/python/foxclaw/skills/pipeline.py +386 -0
  216. package/python/foxclaw/skills/policy.py +80 -0
  217. package/python/foxclaw/skills/review.py +72 -0
  218. package/python/foxclaw/skills/store.py +57 -0
  219. package/python/frontend/assets/core_bg.jpg +0 -0
  220. package/python/frontend/deck/auth.js +32 -0
  221. package/python/frontend/deck/chat_model_picker.js +537 -0
  222. package/python/frontend/deck/domain.js +296 -0
  223. package/python/frontend/deck/main.js +1950 -0
  224. package/python/frontend/deck/markdown.js +48 -0
  225. package/python/frontend/deck/settings.js +324 -0
  226. package/python/frontend/deck/state.js +6 -0
  227. package/python/frontend/deck/util.js +6 -0
  228. package/python/frontend/deck/visuals.js +212 -0
  229. package/python/frontend/deck.css +855 -0
  230. package/python/frontend/deck.html +686 -0
  231. package/python/frontend/deck_v2.html +341 -0
  232. package/python/frontend/planet/index.html +266 -0
  233. package/python/frontend/planet/planet.css +579 -0
  234. package/python/frontend/planet/planet.js +1637 -0
  235. package/python/frontend/planet/vendor/three.core.min.js +6 -0
  236. package/python/frontend/planet/vendor/three.module.min.js +6 -0
  237. package/python/frontend/v3/assets/index-BD7VPbMV.js +33 -0
  238. package/python/frontend/v3/assets/index-CAvnYWGH.css +1 -0
  239. package/python/frontend/v3/index.html +15 -0
  240. package/python/frontend/widget_runtime.js +295 -0
  241. package/python/frontend/widgets.js +696 -0
  242. package/python/frontend_src/index.html +14 -0
  243. package/python/frontend_src/package-lock.json +1376 -0
  244. package/python/frontend_src/package.json +19 -0
  245. package/python/frontend_src/src/App.vue +62 -0
  246. package/python/frontend_src/src/api.js +89 -0
  247. package/python/frontend_src/src/components/ChatDock.vue +377 -0
  248. package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
  249. package/python/frontend_src/src/components/DocViewer.vue +171 -0
  250. package/python/frontend_src/src/components/Inspector.vue +241 -0
  251. package/python/frontend_src/src/components/LeftRail.vue +394 -0
  252. package/python/frontend_src/src/components/MenuPopover.vue +130 -0
  253. package/python/frontend_src/src/components/MessageStream.vue +401 -0
  254. package/python/frontend_src/src/components/ModelPicker.vue +355 -0
  255. package/python/frontend_src/src/components/QuickIngest.vue +118 -0
  256. package/python/frontend_src/src/components/Stage.vue +226 -0
  257. package/python/frontend_src/src/components/ToolMenu.vue +144 -0
  258. package/python/frontend_src/src/components/TopBar.vue +90 -0
  259. package/python/frontend_src/src/components/panels/ActivityPanel.vue +84 -0
  260. package/python/frontend_src/src/components/panels/AgentsPanel.vue +86 -0
  261. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +441 -0
  262. package/python/frontend_src/src/components/panels/MemoryPanel.vue +118 -0
  263. package/python/frontend_src/src/components/panels/PanelBox.vue +51 -0
  264. package/python/frontend_src/src/components/panels/ReviewPanel.vue +308 -0
  265. package/python/frontend_src/src/components/panels/SettingsPanel.vue +254 -0
  266. package/python/frontend_src/src/components/panels/TasksPanel.vue +74 -0
  267. package/python/frontend_src/src/components/panels/ToolsPanel.vue +640 -0
  268. package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
  269. package/python/frontend_src/src/lib/fmt.js +48 -0
  270. package/python/frontend_src/src/lib/md.js +204 -0
  271. package/python/frontend_src/src/main.js +7 -0
  272. package/python/frontend_src/src/stores/app.js +106 -0
  273. package/python/frontend_src/src/stores/chat.js +484 -0
  274. package/python/frontend_src/src/stores/model.js +104 -0
  275. package/python/frontend_src/src/stores/panels.js +330 -0
  276. package/python/frontend_src/src/styles/base.css +138 -0
  277. package/python/frontend_src/src/styles/panel.css +117 -0
  278. package/python/frontend_src/src/styles/tokens.css +78 -0
  279. package/python/frontend_src/vite.config.js +46 -0
  280. package/python/pyproject.toml +265 -0
  281. package/python/skills/README.md +123 -0
  282. package/python/skills/execution-contract/SKILL.md +30 -0
  283. package/python/skills/general-assistant/SKILL.md +34 -0
  284. package/python/skills/planner/SKILL.md +33 -0
  285. package/python/skills/schedule-itinerary/SKILL.md +35 -0
  286. package/python/skills/skill-composition/SKILL.md +31 -0
  287. package/python/skills/skill-guard/SKILL.md +67 -0
  288. package/python/skills/skill-memory/SKILL.md +50 -0
  289. package/python/skills/skill-router/SKILL.md +35 -0
  290. package/python/tests/conftest.py +76 -0
  291. package/python/tests/run_all.py +263 -0
  292. package/python/tests/test_agent_lifecycle.py +360 -0
  293. package/python/tests/test_ahs_p2.py +414 -0
  294. package/python/tests/test_anthropic_tools.py +206 -0
  295. package/python/tests/test_api.py +87 -0
  296. package/python/tests/test_api_auth.py +83 -0
  297. package/python/tests/test_api_host_guard.py +69 -0
  298. package/python/tests/test_attachments.py +167 -0
  299. package/python/tests/test_audit_chain.py +121 -0
  300. package/python/tests/test_audit_sink.py +136 -0
  301. package/python/tests/test_browser.py +153 -0
  302. package/python/tests/test_browser_request_guard.py +184 -0
  303. package/python/tests/test_capabilities.py +158 -0
  304. package/python/tests/test_channel_signing.py +192 -0
  305. package/python/tests/test_channel_trust.py +109 -0
  306. package/python/tests/test_channels.py +184 -0
  307. package/python/tests/test_cli.py +46 -0
  308. package/python/tests/test_compaction_toolpair.py +119 -0
  309. package/python/tests/test_config_layering.py +65 -0
  310. package/python/tests/test_confirm_nonce.py +197 -0
  311. package/python/tests/test_context_compaction_wiring.py +130 -0
  312. package/python/tests/test_dispatch.py +95 -0
  313. package/python/tests/test_domain_evals.py +403 -0
  314. package/python/tests/test_domain_import.py +78 -0
  315. package/python/tests/test_domain_reasoners.py +148 -0
  316. package/python/tests/test_e2e.py +178 -0
  317. package/python/tests/test_emb_cache.py +107 -0
  318. package/python/tests/test_embedding_singleton.py +89 -0
  319. package/python/tests/test_envfile.py +83 -0
  320. package/python/tests/test_evals_smoke.py +114 -0
  321. package/python/tests/test_event_spine.py +182 -0
  322. package/python/tests/test_evidence_semantic.py +101 -0
  323. package/python/tests/test_evidence_sse.py +97 -0
  324. package/python/tests/test_file_ingest.py +146 -0
  325. package/python/tests/test_finance.py +153 -0
  326. package/python/tests/test_finance_ledger.py +252 -0
  327. package/python/tests/test_finance_persist.py +178 -0
  328. package/python/tests/test_gateway.py +144 -0
  329. package/python/tests/test_governance.py +468 -0
  330. package/python/tests/test_governance_audit.py +359 -0
  331. package/python/tests/test_governance_middleware.py +143 -0
  332. package/python/tests/test_hkc.py +286 -0
  333. package/python/tests/test_hkc_index_build_manager.py +339 -0
  334. package/python/tests/test_hkc_manifest_reuse.py +317 -0
  335. package/python/tests/test_hkc_search_event_sync.py +533 -0
  336. package/python/tests/test_import_path_guard.py +161 -0
  337. package/python/tests/test_injection_guard.py +211 -0
  338. package/python/tests/test_integration.py +141 -0
  339. package/python/tests/test_itinerary.py +103 -0
  340. package/python/tests/test_kee_semantic.py +89 -0
  341. package/python/tests/test_kernel.py +222 -0
  342. package/python/tests/test_learning.py +110 -0
  343. package/python/tests/test_llm_admin.py +160 -0
  344. package/python/tests/test_llm_planner.py +366 -0
  345. package/python/tests/test_markdown_skills.py +73 -0
  346. package/python/tests/test_markdown_skills_openclaw.py +71 -0
  347. package/python/tests/test_mcp_connectors.py +109 -0
  348. package/python/tests/test_mcp_guardian_discovery.py +91 -0
  349. package/python/tests/test_mcp_no_memory.py +43 -0
  350. package/python/tests/test_mcp_policy_gate.py +77 -0
  351. package/python/tests/test_mcp_server.py +95 -0
  352. package/python/tests/test_memory_recall.py +95 -0
  353. package/python/tests/test_metrics.py +66 -0
  354. package/python/tests/test_multi_chain.py +102 -0
  355. package/python/tests/test_multimodal.py +135 -0
  356. package/python/tests/test_negotiation.py +146 -0
  357. package/python/tests/test_orchestration.py +119 -0
  358. package/python/tests/test_orchestration_decoupled.py +109 -0
  359. package/python/tests/test_p3_p5_hardening.py +338 -0
  360. package/python/tests/test_parallel_dispatch.py +215 -0
  361. package/python/tests/test_pipeline.py +533 -0
  362. package/python/tests/test_plan_visible.py +86 -0
  363. package/python/tests/test_planner.py +95 -0
  364. package/python/tests/test_policy_gate.py +109 -0
  365. package/python/tests/test_reasoner.py +185 -0
  366. package/python/tests/test_release_check.py +263 -0
  367. package/python/tests/test_resume_checkpoint.py +156 -0
  368. package/python/tests/test_routing.py +95 -0
  369. package/python/tests/test_scheduler.py +141 -0
  370. package/python/tests/test_search.py +204 -0
  371. package/python/tests/test_secrets.py +159 -0
  372. package/python/tests/test_semantic.py +78 -0
  373. package/python/tests/test_sessions.py +91 -0
  374. package/python/tests/test_settings.py +281 -0
  375. package/python/tests/test_shadow_learning.py +330 -0
  376. package/python/tests/test_shared_write.py +207 -0
  377. package/python/tests/test_skill_taint_gate.py +154 -0
  378. package/python/tests/test_skills.py +133 -0
  379. package/python/tests/test_stats_and_events.py +165 -0
  380. package/python/tests/test_streaming.py +60 -0
  381. package/python/tests/test_supervisor_pipeline.py +177 -0
  382. package/python/tests/test_supervisor_triage.py +108 -0
  383. package/python/tests/test_taint.py +117 -0
  384. package/python/tests/test_task_stream.py +85 -0
  385. package/python/tests/test_thinking_sse.py +67 -0
  386. package/python/tests/test_thinking_stream.py +76 -0
  387. package/python/tests/test_tool_center.py +129 -0
  388. package/python/tests/test_tool_parallel.py +113 -0
  389. package/python/tests/test_tool_params.py +103 -0
  390. package/python/tests/test_tool_use.py +242 -0
  391. package/python/tests/test_toplevel_concurrency.py +117 -0
  392. package/python/tests/test_tracing.py +290 -0
  393. package/python/tests/test_truth_crystallizer.py +127 -0
  394. package/python/tests/test_truth_flow.py +93 -0
  395. package/python/tests/test_url_policy.py +53 -0
  396. package/python/tests/test_verify_evidence.py +225 -0
  397. package/python/tests/test_version_sync.py +71 -0
  398. package/python/tests/test_web_trigger.py +99 -0
  399. package/python/tools/check_faiss_type.py +31 -0
  400. package/python/tools/check_log.ps1 +46 -0
  401. package/python/tools/ivf_status.py +72 -0
  402. package/python/tools/mypy_baseline.txt +5 -0
  403. package/python/tools/mypy_ratchet.py +97 -0
  404. package/python/tools/pack.py +178 -0
  405. package/python/tools/release_check.py +349 -0
  406. package/python/tools/restart_foxclaw.ps1 +74 -0
  407. package/python/tools/shared_write_audit.py +204 -0
  408. package/python/tools/size_baseline.txt +28 -0
  409. package/python/tools/size_check.py +102 -0
  410. package/python/tools/verify_env.bat +2 -0
  411. package/python/tools/verify_env.py +25 -0
  412. package/python/vendor/HMR_LICENSE +37 -0
  413. package/python/vendor/HMR_README.md +65 -0
  414. package/python/vendor/hkc/LICENSE +37 -0
  415. package/python/vendor/hkc/hkc_ace/__init__.py +0 -0
  416. package/python/vendor/hkc/hkc_ace/ace.py +457 -0
  417. package/python/vendor/hkc/hkc_ace/skill_taxonomy.json +465 -0
  418. package/python/vendor/hkc/hkc_api/__init__.py +0 -0
  419. package/python/vendor/hkc/hkc_api/container.py +416 -0
  420. package/python/vendor/hkc/hkc_api/ingest_jobs.py +103 -0
  421. package/python/vendor/hkc/hkc_api/main.py +157 -0
  422. package/python/vendor/hkc/hkc_api/routes/__init__.py +0 -0
  423. package/python/vendor/hkc/hkc_api/routes/ability.py +151 -0
  424. package/python/vendor/hkc/hkc_api/routes/config.py +51 -0
  425. package/python/vendor/hkc/hkc_api/routes/knowledge.py +630 -0
  426. package/python/vendor/hkc/hkc_api/routes/search.py +88 -0
  427. package/python/vendor/hkc/hkc_api/schemas.py +153 -0
  428. package/python/vendor/hkc/hkc_api/synthesis.py +179 -0
  429. package/python/vendor/hkc/hkc_core/__init__.py +0 -0
  430. package/python/vendor/hkc/hkc_core/graph/__init__.py +0 -0
  431. package/python/vendor/hkc/hkc_core/graph/base.py +188 -0
  432. package/python/vendor/hkc/hkc_core/graph/sqlite_store.py +789 -0
  433. package/python/vendor/hkc/hkc_core/models/__init__.py +0 -0
  434. package/python/vendor/hkc/hkc_core/models/enums.py +65 -0
  435. package/python/vendor/hkc/hkc_core/models/ku.py +307 -0
  436. package/python/vendor/hkc/hkc_core/utils/__init__.py +0 -0
  437. package/python/vendor/hkc/hkc_core/utils/id_gen.py +113 -0
  438. package/python/vendor/hkc/hkc_crystallizer/__init__.py +44 -0
  439. package/python/vendor/hkc/hkc_crystallizer/adapters.py +94 -0
  440. package/python/vendor/hkc/hkc_crystallizer/candidate.py +145 -0
  441. package/python/vendor/hkc/hkc_crystallizer/crystallizer.py +179 -0
  442. package/python/vendor/hkc/hkc_crystallizer/ingress.py +90 -0
  443. package/python/vendor/hkc/hkc_kde/__init__.py +0 -0
  444. package/python/vendor/hkc/hkc_kde/chunker.py +197 -0
  445. package/python/vendor/hkc/hkc_kde/extractor.py +623 -0
  446. package/python/vendor/hkc/hkc_kde/kde.py +298 -0
  447. package/python/vendor/hkc/hkc_kde/loaders/__init__.py +0 -0
  448. package/python/vendor/hkc/hkc_kde/loaders/ebook_loader.py +153 -0
  449. package/python/vendor/hkc/hkc_kde/loaders/office_loader.py +194 -0
  450. package/python/vendor/hkc/hkc_kde/loaders/pdf_loader.py +162 -0
  451. package/python/vendor/hkc/hkc_kde/loaders/text_loader.py +157 -0
  452. package/python/vendor/hkc/hkc_kde/loaders/web_loader.py +91 -0
  453. package/python/vendor/hkc/hkc_kde/models.py +74 -0
  454. package/python/vendor/hkc/hkc_kde/packager.py +329 -0
  455. package/python/vendor/hkc/hkc_kde/relation_disc.py +151 -0
  456. package/python/vendor/hkc/hkc_kee/__init__.py +0 -0
  457. package/python/vendor/hkc/hkc_kee/dedup.py +227 -0
  458. package/python/vendor/hkc/hkc_kee/fingerprint.py +106 -0
  459. package/python/vendor/hkc/hkc_kee/kee.py +541 -0
  460. package/python/vendor/hkc/hkc_kep/__init__.py +0 -0
  461. package/python/vendor/hkc/hkc_kep/event_bus.py +205 -0
  462. package/python/vendor/hkc/hkc_sdk/__init__.py +63 -0
  463. package/python/vendor/hkc/hkc_sdk/base.py +151 -0
  464. package/python/vendor/hkc/hkc_sdk/direct_client.py +153 -0
  465. package/python/vendor/hkc/hkc_sdk/exceptions.py +39 -0
  466. package/python/vendor/hkc/hkc_sdk/http_client.py +166 -0
  467. package/python/vendor/hkc/hkc_sdk/models.py +171 -0
  468. package/python/vendor/hkc/hkc_search/__init__.py +0 -0
  469. package/python/vendor/hkc/hkc_search/bm25.py +325 -0
  470. package/python/vendor/hkc/hkc_search/embedding_backends.py +374 -0
  471. package/python/vendor/hkc/hkc_search/graph_search.py +132 -0
  472. package/python/vendor/hkc/hkc_search/hybrid.py +331 -0
  473. package/python/vendor/hkc/hkc_search/index_build_manager.py +378 -0
  474. package/python/vendor/hkc/hkc_search/vector_search.py +484 -0
  475. package/python/vendor/hmr/__init__.py +0 -0
  476. package/python/vendor/hmr/core/__init__.py +0 -0
  477. package/python/vendor/hmr/core/hmr.py +684 -0
  478. package/python/vendor/hmr/core/models.py +310 -0
  479. package/python/vendor/hmr/engines/__init__.py +0 -0
  480. package/python/vendor/hmr/engines/evolution.py +710 -0
  481. package/python/vendor/hmr/engines/jit_compiler.py +472 -0
  482. package/python/vendor/hmr/engines/lifecycle.py +264 -0
  483. package/python/vendor/hmr/engines/policy.py +625 -0
  484. package/python/vendor/hmr/engines/recall.py +260 -0
  485. package/python/vendor/hmr/engines/runtime_state.py +158 -0
  486. package/python/vendor/hmr/engines/scheduler.py +323 -0
  487. package/python/vendor/hmr/engines/semantic.py +150 -0
  488. package/python/vendor/hmr/engines/temporal.py +261 -0
  489. package/python/vendor/hmr/engines/thought_chain.py +635 -0
  490. package/python/vendor/hmr/graph/__init__.py +0 -0
  491. package/python/vendor/hmr/graph/cwg.py +338 -0
  492. package/python/vendor/hmr/graph/memory_graph.py +530 -0
  493. package/python/vendor/hmr/storage/__init__.py +0 -0
  494. package/python/vendor/hmr/storage/memory_fs.py +300 -0
  495. package/python/vendor/hmr/storage/vector_store.py +520 -0
@@ -0,0 +1,184 @@
1
+ """
2
+ 框架无关治理中间件的实现。
3
+
4
+ 只依赖治理内核(Harness + ThoughtChain + ToolLoopDetector),不碰 Foxclaw 其余部分。
5
+ 对外只暴露两个概念:
6
+ · GovernanceSession —— 一次带目标基线的治理会话:observe(每步) + audit(取审计链)+ 循环检测;
7
+ · governed —— 把某个工具函数包成"调用前先过 Harness"的装饰器(同步/异步都支持)。
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ import functools
13
+ import tempfile
14
+ import uuid
15
+ from dataclasses import dataclass, field
16
+ from typing import Optional
17
+
18
+ from ..harness.harness import Harness
19
+ from ..harness.loop_detection import ToolLoopDetector
20
+ from ..core.thought_chain import ThoughtChain, ThoughtNode
21
+
22
+
23
+ @dataclass
24
+ class GovResult:
25
+ """一步治理判定的结果(框架无关,不泄露 Foxclaw 内部类型)。"""
26
+ status: str # PASS / WARN / BLOCK
27
+ blocked: bool
28
+ warned: bool
29
+ delta: float
30
+ dimension: str # 主导漂移维度 semantic/tool/scope
31
+ reason: Optional[str]
32
+ node_id: str
33
+ scores: dict = field(default_factory=dict) # 各维度分 semantic/tool/scope
34
+ loop: Optional[dict] = None # 若检出工具死循环,填充告警
35
+
36
+ @property
37
+ def tool_violation(self) -> bool:
38
+ """工具越权(与语言/语义无关的确定性信号)。"""
39
+ return self.scores.get("tool", 0) >= 1.0
40
+
41
+ @property
42
+ def scope_violation(self) -> bool:
43
+ """范围蔓延越上限(确定性信号)。"""
44
+ return self.scores.get("scope", 0) >= 1.0
45
+
46
+ def to_dict(self) -> dict:
47
+ return {"status": self.status, "blocked": self.blocked, "warned": self.warned,
48
+ "delta": round(self.delta, 4), "dimension": self.dimension,
49
+ "reason": self.reason, "node_id": self.node_id,
50
+ "scores": {k: round(v, 4) for k, v in self.scores.items()},
51
+ "loop": self.loop}
52
+
53
+
54
+ class GovernanceBlocked(Exception):
55
+ """被治理层拦截时抛出(供 governed 装饰器/框架回调使用)。"""
56
+ def __init__(self, result: GovResult):
57
+ self.result = result
58
+ super().__init__(f"governance blocked: {result.reason}")
59
+
60
+
61
+ class GovernanceSession:
62
+ """一次治理会话:注册目标基线,之后每步 observe 都过三维漂移 + 循环检测,并落审计链。
63
+
64
+ 参数:
65
+ goal —— 任务目标(语义漂移的基线);
66
+ allowed_tools —— 允许的工具类别集合(如 {"read","finance"});越类别即工具越权;
67
+ entities —— 基线实体集(范围蔓延的参照);
68
+ agent_id —— 审计归属标识;
69
+ shadow_rounds —— 观测期轮数(前 N 步只观测不拦截,用真实分布校准阈值);默认 0=即时判定;
70
+ db_path —— 审计链落盘路径;默认临时文件(会话级)。
71
+
72
+ 线程/进程无关:不启动任何后台线程,不联网;语义向量用本地缓存模型,缺失自动降级哈希嵌入。
73
+ """
74
+
75
+ def __init__(self, goal: str, allowed_tools=None, entities=None,
76
+ agent_id: str = "agent", shadow_rounds: int = 0,
77
+ db_path: str = None, policy_path: str = None,
78
+ embedding_model: str = None):
79
+ self.agent_id = agent_id
80
+ self._session_id = f"gov_{uuid.uuid4().hex[:10]}"
81
+ self.thought_chain = ThoughtChain(
82
+ db_path=db_path or tempfile.mktemp(suffix=".gov.db"))
83
+ self.harness = Harness(
84
+ agent_id=agent_id, thought_chain=self.thought_chain,
85
+ policy_path=policy_path or tempfile.mktemp(suffix=".gov.yaml"),
86
+ shadow_rounds=shadow_rounds, embedding_model=embedding_model)
87
+ self.harness.register_goal(
88
+ goal_text=goal,
89
+ allowed_tool_categories=set(allowed_tools or {"read"}),
90
+ baseline_entities=set(entities or []))
91
+ # shadow_rounds<=0:中间件默认**即时按默认阈值判定**(warn 0.15 / block 0.25),
92
+ # 不做"拿第一个样本自校准"(那会让首步逃逸并用单样本污染阈值)。想用真实分布
93
+ # 校准的调用方,显式传 shadow_rounds>0 走观测期。
94
+ if shadow_rounds <= 0:
95
+ self.harness._shadow_mode = False
96
+ self._loop = ToolLoopDetector()
97
+ self._blocked_count = 0
98
+
99
+ # ── 核心:观测一步 ────────────────────────────────────────
100
+ def observe(self, content: str, tool: str = None, entities=None,
101
+ node_type: str = None, conf: float = 0.9,
102
+ semantic: bool = True) -> GovResult:
103
+ """观测一个推理/工具步骤,返回治理判定。不抛异常(判定在结果里)。
104
+
105
+ semantic=False:只按工具/范围维度判定(语义置 0)。用于纯"工具授权"检查——
106
+ 工具调用串对目标的语义距离是噪声。"""
107
+ node = ThoughtNode.create(
108
+ session_id=self._session_id, agent_id=self.agent_id,
109
+ type=node_type or ("tool_intent" if tool else "reasoning"),
110
+ content=content, conf=conf, tool_name=tool, entities=entities or [])
111
+ self.thought_chain.add_node(node)
112
+ r = self.harness.check(node, ignore_semantic=not semantic)
113
+ loop_warn = None
114
+ if tool:
115
+ lv = self._loop.record(tool, {"content": content})
116
+ if lv.stuck:
117
+ loop_warn = lv.to_dict()
118
+ if r.is_blocked:
119
+ self._blocked_count += 1
120
+ return GovResult(status=r.status, blocked=r.is_blocked, warned=r.is_warned,
121
+ delta=r.delta, dimension=r.dominant_dim, reason=r.reason,
122
+ node_id=node.id, scores=dict(r.scores), loop=loop_warn)
123
+
124
+ def guard(self, content: str, tool: str = None, entities=None) -> GovResult:
125
+ """与 observe 相同,但被 BLOCK 时**抛 GovernanceBlocked**(fail-closed 风格)。"""
126
+ r = self.observe(content, tool=tool, entities=entities)
127
+ if r.blocked:
128
+ raise GovernanceBlocked(r)
129
+ return r
130
+
131
+ # ── 审计 ────────────────────────────────────────────────
132
+ def audit(self) -> list[dict]:
133
+ """取回本会话的完整可审计推理链(带每步 Harness 判定)。"""
134
+ return [n.to_dict() for n in
135
+ self.thought_chain.get_chain(self._session_id, self.agent_id)]
136
+
137
+ def summary(self) -> dict:
138
+ """治理摘要:总步数/拦截数/漂移趋势(供框架侧上报)。"""
139
+ s = self.thought_chain.get_summary(self.agent_id)
140
+ s["blocked_this_session"] = self._blocked_count
141
+ s["shadow_mode"] = self.harness.is_shadow_mode
142
+ return s
143
+
144
+ def __enter__(self):
145
+ return self
146
+
147
+ def __exit__(self, *exc):
148
+ return False
149
+
150
+
151
+ def governed(session: GovernanceSession, tool_name: str = None, entities=None):
152
+ """装饰器:把一个工具函数包成"调用前先过 Harness"。
153
+
154
+ 命中越权/跑题/范围蔓延 → 抛 GovernanceBlocked,函数不执行。同步/异步函数都支持。
155
+
156
+ gov = GovernanceSession(goal=..., allowed_tools={"read"})
157
+ @governed(gov)
158
+ def delete_row(...): ... # 调用即被拦(delete 不在 read 允许集)
159
+ """
160
+ def _check(name):
161
+ # 工具装饰器只对"这个工具授权了吗"负责 → semantic=False 只按 tool/scope 判定
162
+ # (语义是 observe() 的职责;工具名对目标的语义距离是噪声,跨语言时更会饱和,
163
+ # 不能作为工具授权依据)。这样中间件语言无关、行为确定、审计不被语义噪声污染。
164
+ r = session.observe(f"tool call: {name}", tool=name, entities=entities,
165
+ semantic=False)
166
+ if r.blocked or r.tool_violation or r.scope_violation:
167
+ raise GovernanceBlocked(r)
168
+
169
+ def deco(fn):
170
+ name = tool_name or getattr(fn, "__name__", "tool")
171
+
172
+ if asyncio.iscoroutinefunction(fn):
173
+ @functools.wraps(fn)
174
+ async def awrapper(*a, **kw):
175
+ _check(name)
176
+ return await fn(*a, **kw)
177
+ return awrapper
178
+
179
+ @functools.wraps(fn)
180
+ def wrapper(*a, **kw):
181
+ _check(name)
182
+ return fn(*a, **kw)
183
+ return wrapper
184
+ return deco
@@ -0,0 +1,17 @@
1
+ """Foxclaw — Harness: 跨层约束-校准机制"""
2
+ from .harness import (
3
+ Harness,
4
+ HarnessResult,
5
+ GoalBaseline,
6
+ SemanticDrift,
7
+ ToolIntentDrift,
8
+ ScopeCreepDrift,
9
+ HarnessEventBus,
10
+ TOOL_CATEGORIES,
11
+ )
12
+
13
+ __all__ = [
14
+ "Harness", "HarnessResult", "GoalBaseline",
15
+ "SemanticDrift", "ToolIntentDrift", "ScopeCreepDrift",
16
+ "HarnessEventBus", "TOOL_CATEGORIES",
17
+ ]
@@ -0,0 +1,72 @@
1
+ """Harness 基础设施:contextvar 分槽 + 工具类别白名单 + 结果/基线数据类。"""
2
+ import contextvars
3
+ from dataclasses import dataclass
4
+ from typing import Optional
5
+
6
+ import numpy as np
7
+
8
+ from ..core.thought_chain import HarnessStatus
9
+
10
+
11
+ # 目标基线是**每任务**语义(register_goal 每次任务设一次),却存在 Harness 实例上——
12
+ # 并发顶层任务打同一 Agent 的 harness 时会互相覆盖,导致 check 对着错误的目标算漂移。
13
+ # 与 base.py 同构:放进单个 module 级 contextvar 承载的 dict、按实例 token 分槽。
14
+ # 累积学习态(weights/_policy/_delta_history/_run_count/_shadow_mode)仍留在实例上——
15
+ # 那本就该按 Agent 累积,且同步 check 内无 await,协作调度下读改写原子安全。
16
+ _harness_ctx: contextvars.ContextVar = contextvars.ContextVar(
17
+ "foxclaw_harness_ctx", default=None)
18
+
19
+
20
+ def _harness_ctx_get(token: str):
21
+ return (_harness_ctx.get() or {}).get(token)
22
+
23
+
24
+ def _harness_ctx_set(token: str, baseline):
25
+ d = dict(_harness_ctx.get() or {})
26
+ d[token] = baseline
27
+ _harness_ctx.set(d)
28
+
29
+
30
+ # ── 工具类别白名单 ─────────────────────────────────────────
31
+ TOOL_CATEGORIES: dict[str, set[str]] = {
32
+ "read": {"read_file", "search", "fetch", "list", "get", "query"},
33
+ "write": {"write_file", "create", "update", "save", "append"},
34
+ "delete": {"delete", "remove", "drop", "clear", "reset"},
35
+ "exec": {"run_code", "shell", "subprocess", "execute"},
36
+ "network": {"http_get", "http_post", "scrape", "download"},
37
+ "calendar": {"add_event", "get_schedule", "update_event"},
38
+ "finance": {"get_balance", "add_transaction", "generate_report"},
39
+ }
40
+
41
+
42
+ @dataclass
43
+ class HarnessResult:
44
+ status: HarnessStatus
45
+ delta: float
46
+ dominant_dim: str # 主导漂移维度
47
+ scores: dict # 各维度分数
48
+ reason: Optional[str] = None
49
+
50
+ @property
51
+ def is_blocked(self) -> bool:
52
+ return self.status == "BLOCK"
53
+
54
+ @property
55
+ def is_warned(self) -> bool:
56
+ return self.status == "WARN"
57
+
58
+
59
+ @dataclass
60
+ class GoalBaseline:
61
+ """目标基线 — 在接口层注册,后续所有层的校验参照此基线"""
62
+ goal_text: str
63
+ allowed_tool_categories: set[str]
64
+ baseline_entities: set[str]
65
+ goal_embedding: Optional[np.ndarray] = None
66
+
67
+ def to_dict(self) -> dict:
68
+ return {
69
+ "goal_text": self.goal_text,
70
+ "allowed_tool_categories": list(self.allowed_tool_categories),
71
+ "baseline_entities": list(self.baseline_entities),
72
+ }
@@ -0,0 +1,359 @@
1
+ """Harness Policy 学习 + 影子观察 + 阈值校准 + 持久化(mixin)。
2
+
3
+ 拆分自 harness.py:`Harness` 主类继承本 mixin,通过 `self.weights/_policy/_lock`
4
+ 等实例状态协同工作。纯搬移、零行为变更。
5
+ """
6
+ import os
7
+
8
+ import numpy as np
9
+ import yaml
10
+ from datetime import datetime, timezone
11
+
12
+ from ..core.thought_chain import ThoughtNode
13
+ from ._base import HarnessResult
14
+
15
+
16
+ class PolicyMixin:
17
+ # ── 学习规则的 action 编译 / 应用 ─────────────────────────
18
+
19
+ def _reindex_learned_actions(self):
20
+ """把 self._policy['rules'] 里已学习的 action 编译成 check() 用得上的结构。
21
+
22
+ 使自学习从「权重级」升级到「规则级」:BLOCK 时 _generate_policy_action 生成的
23
+ penalize_tool / restrict_scope 不再只是躺在 YAML 里的意图,而是被这里编译、
24
+ 并在 check() 里真正改变判定。加载 policy 时 + 每次新学到规则后都会重建。
25
+ """
26
+ penalized: set[str] = set()
27
+ scope_limit: int | None = None
28
+ for rule in self._policy.get("rules", []):
29
+ action = rule.get("action") or {}
30
+ atype = action.get("type")
31
+ if atype == "penalize_tool":
32
+ pat = action.get("tool_pattern")
33
+ if pat:
34
+ penalized.add(pat)
35
+ elif atype == "restrict_scope":
36
+ lim = action.get("entity_expansion_limit")
37
+ if isinstance(lim, int):
38
+ # 多条 restrict 取最严(最小)上限
39
+ scope_limit = lim if scope_limit is None else min(scope_limit, lim)
40
+ self._penalized_tools = penalized
41
+ self._scope_limit = scope_limit
42
+
43
+ def _is_penalized_tool(self, tool_name: str) -> bool:
44
+ """工具名是否命中已学习的 penalize_tool 规则(与 ToolIntentDrift 一致的子串匹配)。"""
45
+ if not tool_name or not self._penalized_tools:
46
+ return False
47
+ return any(tool_name == p or p in tool_name for p in self._penalized_tools)
48
+
49
+ # ── Policy 学习(P0-1:影子验证 + 回滚 + 边界)────────────────
50
+
51
+ # 【P0-1 边界】可生成影子规则的维度。semantic 可学但**仅权重级**(不生成规则——
52
+ # 原 increase_semantic_threshold 规则本就无规则级应用,判定行为不变);
53
+ # 不在 weights 里的未知维度 → 只记录不学习(fail-closed,防未来新维度被默认自学)。
54
+ _LEARNABLE_RULE_DIMS = frozenset({"tool", "scope"})
55
+
56
+ def _trigger_policy_learning(self, node: ThoughtNode, result: HarnessResult):
57
+ """BLOCK 事件 → 规则候选进 **shadow_rules(不生效)** → 影子观察 → promote/discard。
58
+
59
+ 【P0-1】原为「BLOCK → 规则直接写 rules + reindex → 下次 check 立即生效」——
60
+ 在线无验证学习是真实风险(一次偶发 BLOCK 就永久改变判定)。改后:
61
+ · 规则级:候选进 shadow_rules,观察 N 次达标才 promote(生效)/discard;
62
+ · 权重级:保持现状(触发维度 +0.05 归一化)——文稿只影子化规则级,权重微调风险低;
63
+ · 边界:semantic 仅权重级不生成规则;未知维度连权重都不学,只记录。
64
+ """
65
+ dim = result.dominant_dim
66
+
67
+ # ── 边界:未知维度 fail-closed,只记录不学习(权重也不动)──
68
+ if dim not in self.weights:
69
+ self._record_history("skip", "",
70
+ note=f"未知维度 '{dim}' 禁止自学习(fail-closed)")
71
+ self._save_policy()
72
+ return
73
+
74
+ # ── 规则级:tool/scope 生成影子候选;semantic 仅权重级 ──
75
+ if dim in self._LEARNABLE_RULE_DIMS:
76
+ new_rule = {
77
+ "id": f"rule_{node.id}",
78
+ "trigger": {
79
+ "harness_status": "BLOCK",
80
+ "dominant_dim": dim,
81
+ "delta_threshold": round(result.delta, 3),
82
+ },
83
+ "action": self._generate_policy_action(node, result),
84
+ "created_at": datetime.now(timezone.utc).isoformat(),
85
+ "source": "harness_learned",
86
+ "agent_id": self.agent_id,
87
+ # 影子账本:observations=适用次数;would_block=若生效会多拦;
88
+ # would_pass=若生效会多放。三者是 promote/discard 的全部证据。
89
+ "shadow": {
90
+ "created_at": datetime.now(timezone.utc).isoformat(),
91
+ "observations": 0, "would_block": 0, "would_pass": 0,
92
+ "status": "observing",
93
+ },
94
+ }
95
+ self._policy.setdefault("shadow_rules", []).append(new_rule)
96
+ self._record_history("propose", new_rule["id"],
97
+ note=f"{dim} 维度 BLOCK → 影子观察")
98
+ # 不 _reindex_learned_actions —— 影子规则**不得**影响判定
99
+ self.bus.emit("shadow_rule_proposed", {
100
+ "agent_id": self.agent_id, "rule_id": new_rule["id"],
101
+ "dominant_dim": dim,
102
+ "action_type": new_rule["action"].get("type", ""),
103
+ })
104
+
105
+ # 更新权重:触发截断的维度权重 +δ
106
+ # 必须**先把新权重算完整再整体赋值**,不能"原地 +0.05 再归一化":
107
+ # 后者会让 self.weights 出现「各维之和 = 1.05」的瞬时中间态,而此刻若有人读到它
108
+ # (get_policy_summary / policy_updated 的监听者),拿到的就是未归一化的错值。
109
+ # 实测:同一份 summary 持有的 weights 引用会被读出 0.35,而稍后再取是 0.333 —— 正是
110
+ # 这个中间态被观察到。
111
+ bumped = dict(self.weights)
112
+ bumped[dim] = min(bumped[dim] + 0.05, 0.8)
113
+ total = sum(bumped.values())
114
+ self.weights = {k: v / total for k, v in bumped.items()}
115
+
116
+ self._save_policy()
117
+ self.bus.emit("policy_updated", {
118
+ "agent_id": self.agent_id,
119
+ "rule_id": "",
120
+ "dominant_dim": dim,
121
+ "new_weights": dict(self.weights),
122
+ })
123
+
124
+ # ── 影子观察(双轨判定)──────────────────────────────────
125
+
126
+ def _observe_shadow_rules(self, node: ThoughtNode, result: HarnessResult,
127
+ real_scores: dict):
128
+ """对观察中的影子规则做双轨计数,达标后 promote/discard。锁内调用。
129
+
130
+ 适用性:当前场景的 dominant_dim 与规则 trigger.dominant_dim 相同。
131
+ (trigger.delta_threshold 仅记录生成时的漂移强度,不作匹配条件——否则
132
+ observations 几乎无法积累,影子机制会失去意义。)
133
+
134
+ 落盘节流:promote/discard 立即 save;普通计数每 10 次 save 一次(进程崩溃
135
+ 最多丢 9 次观察计数,可接受)——否则「有影子规则时每次 check 都写 YAML」,
136
+ 一次任务几十次 check 就是几十次磁盘写。
137
+ """
138
+ need_save = False
139
+ for rule in list(self._policy.get("shadow_rules", [])):
140
+ sh = rule.get("shadow") or {}
141
+ if sh.get("status") != "observing":
142
+ continue
143
+ trig = rule.get("trigger") or {}
144
+ if trig.get("dominant_dim") != result.dominant_dim:
145
+ continue
146
+
147
+ sh["observations"] = sh.get("observations", 0) + 1
148
+
149
+ # 双轨:把该规则的 action 应用到 scores 副本,用**同一套权重与阈值**判定
150
+ shadow_status = self._status_with_action(rule, node, real_scores)
151
+ real_blocked = result.is_blocked
152
+ shadow_blocked = shadow_status == "BLOCK"
153
+ if shadow_blocked and not real_blocked:
154
+ sh["would_block"] = sh.get("would_block", 0) + 1
155
+ elif not shadow_blocked and real_blocked:
156
+ sh["would_pass"] = sh.get("would_pass", 0) + 1
157
+
158
+ verdict = self._should_promote(sh)
159
+ if verdict is True:
160
+ self._promote_rule(rule)
161
+ need_save = True
162
+ elif verdict is False:
163
+ self._discard_rule(rule, "影子证据不达标")
164
+ need_save = True
165
+ elif sh["observations"] % 10 == 0:
166
+ need_save = True # 计数节流持久化
167
+ if need_save:
168
+ self._save_policy()
169
+
170
+ def _status_with_action(self, rule: dict, node: ThoughtNode,
171
+ real_scores: dict) -> str:
172
+ """模拟应用单条影子规则的 action,给出「若它生效」的判定(复用同一权重/阈值)。"""
173
+ scores = dict(real_scores)
174
+ action = rule.get("action") or {}
175
+ atype = action.get("type")
176
+ if atype == "penalize_tool":
177
+ pat = action.get("tool_pattern", "")
178
+ tool_name = getattr(node, "tool_name", "") or ""
179
+ # 与 _is_penalized_tool 同语义:**仅当前工具命中 pattern 才抬分**。
180
+ # 若不看匹配、凡 tool 分非零就抬,会把「毫不相干工具的场景」误记成
181
+ # would_block —— 影子证据完全失真,好规则会被冤判成误伤 discard。
182
+ if pat and tool_name and (tool_name == pat or pat in tool_name):
183
+ scores["tool"] = 1.0
184
+ elif atype == "restrict_scope":
185
+ # 与 check() 里 restrict_scope 的判定同构:新增实体超限则抬 scope
186
+ lim = action.get("entity_expansion_limit")
187
+ baseline = self._baseline
188
+ if isinstance(lim, int) and baseline is not None and baseline.baseline_entities:
189
+ new_entities = set(node.entities) - baseline.baseline_entities
190
+ if len(new_entities) > lim:
191
+ scores["scope"] = 1.0
192
+ delta = sum(self.weights[k] * v for k, v in scores.items())
193
+ if delta > self._block_threshold:
194
+ return "BLOCK"
195
+ if delta > self._warn_threshold:
196
+ return "WARN"
197
+ return "PASS"
198
+
199
+ def _should_promote(self, sh: dict):
200
+ """影子证据判定(文稿第四步):True=promote / False=discard / None=继续观察。"""
201
+ obs = sh.get("observations", 0)
202
+ if obs < self.promote_min_obs:
203
+ return None
204
+ wb, wp = sh.get("would_block", 0), sh.get("would_pass", 0)
205
+ # 会多拦很多(可能误伤)→ 不 promote
206
+ if wb / max(obs, 1) > 0.3:
207
+ return False
208
+ # 几乎不改变判定 → promote 没意义
209
+ if wb == 0 and wp == 0:
210
+ return False
211
+ return True
212
+
213
+ def _promote_rule(self, rule: dict):
214
+ """影子 → 生效:移入 rules、重建索引、记 history、发事件。"""
215
+ rule.get("shadow", {})["status"] = "promoted"
216
+ self._policy.setdefault("shadow_rules", []).remove(rule)
217
+ rule.pop("shadow", None) # 生效规则不再背影子账本
218
+ self._policy.setdefault("rules", []).append(rule)
219
+ self._reindex_learned_actions()
220
+ self._record_history("promote", rule["id"],
221
+ note=f"观察达标升迁(维度 {rule.get('trigger', {}).get('dominant_dim', '?')})")
222
+ self.bus.emit("shadow_rule_promoted", {
223
+ "agent_id": self.agent_id, "rule_id": rule["id"],
224
+ "dominant_dim": rule.get("trigger", {}).get("dominant_dim", ""),
225
+ })
226
+
227
+ def _discard_rule(self, rule: dict, why: str):
228
+ """影子证据不达标:标记 discarded(保留供审计回放,不再参与双轨评估)。"""
229
+ sh = rule.get("shadow") or {}
230
+ sh["status"] = "discarded"
231
+ self._record_history("discard", rule.get("id", ""), note=why)
232
+ self.bus.emit("shadow_rule_discarded", {
233
+ "agent_id": self.agent_id, "rule_id": rule.get("id", ""),
234
+ "reason": why,
235
+ "observations": sh.get("observations", 0),
236
+ "would_block": sh.get("would_block", 0),
237
+ "would_pass": sh.get("would_pass", 0),
238
+ })
239
+
240
+ def rollback(self, rule_id: str) -> bool:
241
+ """【P0-1 回滚】把一条已生效规则撤出生效集(误 promote 的后悔药)。
242
+
243
+ 规则从 rules 移回 shadow_rules 并标记 rolled_back(不再生效、保留可审计),
244
+ 重建索引 + 记 history + 发事件。找不到返回 False。"""
245
+ for rule in list(self._policy.get("rules", [])):
246
+ if rule.get("id") != rule_id:
247
+ continue
248
+ self._policy["rules"].remove(rule)
249
+ rule["shadow"] = {
250
+ "created_at": rule.get("created_at", ""),
251
+ "observations": 0, "would_block": 0, "would_pass": 0,
252
+ "status": "rolled_back",
253
+ }
254
+ self._policy.setdefault("shadow_rules", []).append(rule)
255
+ self._reindex_learned_actions()
256
+ self._record_history("rollback", rule_id, note="人工/自动回滚")
257
+ self._save_policy()
258
+ self.bus.emit("shadow_rule_rolled_back", {
259
+ "agent_id": self.agent_id, "rule_id": rule_id,
260
+ })
261
+ return True
262
+ return False
263
+
264
+ def _record_history(self, event: str, rule_id: str, note: str = ""):
265
+ """policy YAML 的 history 段(文稿第六步):propose/promote/discard/rollback/skip 全轨迹。
266
+ 有界保留最近 500 条(与库内有界日志惯例一致)。"""
267
+ hist = self._policy.setdefault("history", [])
268
+ hist.append({
269
+ "ts": datetime.now(timezone.utc).isoformat(),
270
+ "event": event, "rule_id": rule_id,
271
+ "version": self._policy.get("version", 1),
272
+ "note": note[:200],
273
+ })
274
+ if len(hist) > 500:
275
+ del hist[:-500]
276
+
277
+ def _generate_policy_action(self, node: ThoughtNode, result: HarnessResult) -> dict:
278
+ """生成结构化策略动作,随 learned rule 持久化到 policy 文件。
279
+
280
+ 自学习现在**两级都生效**:
281
+ · 权重级 —— _trigger_policy_learning 里对触发维度 +0.05 并归一化,影响后续 delta;
282
+ · 规则级 —— 本函数生成的 action 由 _reindex_learned_actions 编译,并在 check() 里应用:
283
+ - penalize_tool → 命中工具的 tool 分被抬到 1.0(即便类别本在允许集里);
284
+ - restrict_scope → 新增实体数超过 entity_expansion_limit 时 scope 分抬到 1.0。
285
+ (increase_semantic_threshold 仍主要靠权重级体现,未单列规则应用。)
286
+ 因此这些 action 不再只是"意图记录"——penalize_tool / restrict_scope 会真正改变判定。
287
+ """
288
+ dim = result.dominant_dim
289
+ if dim == "tool" and node.tool_name:
290
+ return {
291
+ "type": "penalize_tool",
292
+ "tool_pattern": node.tool_name,
293
+ "weight_delta": -0.3,
294
+ }
295
+ elif dim == "scope":
296
+ return {
297
+ "type": "restrict_scope",
298
+ "entity_expansion_limit": 2,
299
+ }
300
+ else:
301
+ return {
302
+ "type": "increase_semantic_threshold",
303
+ "delta": -0.05,
304
+ }
305
+
306
+ # ── 阈值校准 ───────────────────────────────────────────
307
+
308
+ def _calibrate_thresholds(self):
309
+ """Shadow mode 结束 — 用真实 Δ 分布校准阈值。
310
+ 关键:阈值要设在"正常任务分布"之上,这样正常任务通过,
311
+ 只有真正离群的(如完全跑题)才被拦截。
312
+ 否则用中位数附近的分位数会拦掉一半正常流量。
313
+ """
314
+ arr = np.array(sorted(self._delta_history), dtype=float)
315
+ if arr.size == 0:
316
+ # 空历史(理论上不会发生,因调用前每次 check 都 append)→ 用默认阈值,避免 nan
317
+ self._shadow_mode = False
318
+ return
319
+ mean = float(arr.mean())
320
+ std = float(arr.std())
321
+ p90 = float(np.percentile(arr, 90))
322
+ # BLOCK 阈值取"高分位 / 均值+1.5σ"的较大者,再留 10% 余量,
323
+ # 保证正常任务(集中在均值附近)不被误拦
324
+ block = max(p90, mean + 1.5 * std) * 1.10
325
+ # WARN 阈值取 block 的 0.8 倍,给出渐进预警区
326
+ warn = block * 0.8
327
+ # 下限保护:阈值不低于默认值,避免样本噪声把阈值压得过紧
328
+ self._block_threshold = max(block, 0.25)
329
+ self._warn_threshold = max(warn, 0.18)
330
+ self._shadow_mode = False
331
+ self.bus.emit("shadow_calibrated", {
332
+ "agent_id": self.agent_id,
333
+ "warn_threshold": self._warn_threshold,
334
+ "block_threshold": self._block_threshold,
335
+ "sample_size": len(arr),
336
+ "mean": round(mean, 3),
337
+ "std": round(std, 3),
338
+ })
339
+
340
+ # ── Policy 持久化 ──────────────────────────────────────
341
+
342
+ def _load_policy(self) -> dict:
343
+ if self.policy_path.exists():
344
+ with open(self.policy_path, encoding="utf-8") as f:
345
+ return yaml.safe_load(f) or {}
346
+ # 默认 Policy 骨架(格式从第一天锁定)
347
+ return {
348
+ "version": 1,
349
+ "agent_id": self.agent_id,
350
+ "rules": [],
351
+ "created_at": datetime.now(timezone.utc).isoformat(),
352
+ }
353
+
354
+ def _save_policy(self):
355
+ os.makedirs(self.policy_path.parent, exist_ok=True)
356
+ self._policy["version"] = self._policy.get("version", 1) + 1
357
+ self._policy["updated_at"] = datetime.now(timezone.utc).isoformat()
358
+ with open(self.policy_path, "w", encoding="utf-8") as f:
359
+ yaml.dump(self._policy, f, allow_unicode=True, sort_keys=False)