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,117 @@
1
+ """
2
+ 顶层任务并发隔离回归(per-task 会话状态 via contextvars)
3
+
4
+ 背景:FastAPI 多个并发请求跑在同一事件循环、可能打到同一 Agent 实例。旧实现把
5
+ _session_id/_parent_node_id/harness._baseline 存在实例上,await 之后的推理节点会被
6
+ "最后启动的会话"吞掉(实测分布 [2×7,10])。改为 contextvar 承载后各任务隔离。
7
+
8
+ 验证:
9
+ 1. 同一 Agent 实例并发 N 个顶层任务 → 每会话节点数均衡、无跨会话父链接、goal 归属正确;
10
+ 2. 主管并发处理不同领域任务 → 各自分诊正确、会话不串。
11
+ """
12
+ import asyncio
13
+ import json
14
+ import sqlite3
15
+ import sys
16
+ import tempfile
17
+ import time
18
+ from pathlib import Path
19
+
20
+ import pytest
21
+
22
+ sys.path.insert(0, str(Path(__file__).parent.parent))
23
+
24
+ import foxclaw.core.web_search as ws
25
+ ws.needs_search = lambda q: False
26
+
27
+ from foxclaw import Foxclaw
28
+
29
+
30
+ def check(cond, msg):
31
+ if not cond:
32
+ raise AssertionError(f"✗ {msg}")
33
+ print(f" ✓ {msg}")
34
+
35
+
36
+ def _slow(system, messages, max_tokens=2048, agent_id=""):
37
+ time.sleep(0.1)
38
+ return "ok"
39
+
40
+
41
+ def _build(data_dir=None):
42
+ os_ = Foxclaw(data_dir=data_dir or tempfile.mkdtemp())
43
+ os_.router.complete = _slow
44
+ return os_
45
+
46
+
47
+ @pytest.fixture(scope="module")
48
+ def os_(tmp_path_factory):
49
+ """共享同一 Foxclaw 实例(等价脚本式 main() 里 `os_ = _build()` 一次传给 2 个测试)。"""
50
+ return _build(data_dir=str(tmp_path_factory.mktemp("toplevel_concurrency")))
51
+
52
+
53
+ def test_same_instance_concurrent(os_):
54
+ print("\n【1】同一 Agent 实例并发 8 个顶层任务:无串味")
55
+ office = os_.office
56
+ N = 8
57
+ tasks = [f"学习任务 MARK{i:02d} 请总结" for i in range(N)]
58
+
59
+ async def run():
60
+ return await asyncio.gather(*[office.run_task(t) for t in tasks])
61
+ res = asyncio.run(run())
62
+
63
+ tc = os_.thought_chain
64
+ con = sqlite3.connect(tc.db_path)
65
+ rows = con.execute("SELECT id,session_id,parent_ids FROM thought_nodes "
66
+ "WHERE agent_id='office'").fetchall()
67
+ sid_of = {r[0]: r[1] for r in rows}
68
+ per = {}
69
+ for _id, sid, _p in rows:
70
+ per[sid] = per.get(sid, 0) + 1
71
+ cross = sum(1 for _id, sid, p in rows for par in json.loads(p)
72
+ if par in sid_of and sid_of[par] != sid)
73
+ # goal 归属:每个返回会话的 goal 节点含本任务标记
74
+ ok_goal = 0
75
+ for i, r in enumerate(res):
76
+ g = con.execute("SELECT content FROM thought_nodes WHERE session_id=? "
77
+ "AND type='goal'", (r["session_id"],)).fetchall()
78
+ if g and f"MARK{i:02d}" in g[0][0]:
79
+ ok_goal += 1
80
+
81
+ check(len(per) == N, f"产生 {N} 个独立会话")
82
+ check(len(set(per.values())) == 1, f"每会话节点数均衡(无坍缩): {sorted(per.values())}")
83
+ check(cross == 0, "无跨会话父链接(推理链未错连)")
84
+ check(ok_goal == N, f"每个会话的 goal 归属正确: {ok_goal}/{N}")
85
+
86
+
87
+ def test_supervisor_concurrent(os_):
88
+ print("\n【2】主管并发处理不同领域任务:分诊正确、会话不串")
89
+ sup = os_.supervisor
90
+
91
+ async def run():
92
+ return await asyncio.gather(
93
+ sup.run_task("分析本月理财预算"), # → finance
94
+ sup.run_task("总结这篇论文的要点"), # → office(学习)
95
+ sup.run_task("你能做什么"), # 元问题 → supervisor 自答
96
+ )
97
+ res = asyncio.run(run())
98
+ routed = [r["result"].get("dispatched_to") for r in res]
99
+ sids = [r["session_id"] for r in res]
100
+ check(routed == ["finance", "office", "supervisor"], f"三个并发任务分诊各就各位: {routed}")
101
+ check(len(set(sids)) == 3, "三个任务会话 id 互不相同(未串会话)")
102
+
103
+
104
+ def main():
105
+ print("=" * 56)
106
+ print("顶层任务并发隔离回归")
107
+ print("=" * 56)
108
+ os_ = _build()
109
+ for t in (test_same_instance_concurrent, test_supervisor_concurrent):
110
+ t(os_)
111
+ print("\n" + "=" * 56)
112
+ print("顶层并发隔离通过")
113
+ print("=" * 56)
114
+
115
+
116
+ if __name__ == "__main__":
117
+ main()
@@ -0,0 +1,290 @@
1
+ """请求链路追踪:一个 trace_id 贯穿 请求→Agent→LLM,带 span 耗时;有界环存;无 trace 零成本。
2
+ 与 METRICS(聚合)分工:traces 回答"**这一次**慢在哪、调了谁"。全离线(stub)。"""
3
+ import os, sys, time, json, threading, tempfile
4
+ from pathlib import Path
5
+ sys.path.insert(0, str(Path(__file__).parent.parent))
6
+
7
+ def test_tracing():
8
+ # 关掉 .env:否则开发者 .env 的 FOXCLAW_MCP_SERVER=1 会在 import api 时构建 MCP 服务器,
9
+ # 而本测试有**两处** `with TestClient(app)`(82/189 行),第二次进 lifespan 会撞
10
+ # StreamableHTTPSessionManager「.run() can only be called once per instance」而失败。
11
+ os.environ["FOXCLAW_SKIP_DOTENV"] = "1"
12
+ os.environ["HKC_EMBEDDING"] = "stub"
13
+ os.environ["FOXCLAW_SHADOW_ROUNDS"] = "6"
14
+ os.environ["FOXCLAW_DATA_DIR"] = tempfile.mkdtemp(prefix="foxclaw_trace_")
15
+ for k in ("ANTHROPIC_API_KEY", "OPENAI_API_KEY", "DEEPSEEK_API_KEY"):
16
+ os.environ.pop(k, None)
17
+
18
+ from foxclaw.core.tracing import (Trace, TraceStore, start_trace, current_trace,
19
+ clear_trace, span, MAX_SPANS_PER_TRACE)
20
+
21
+ print("=" * 60); print("请求链路追踪验证"); print("=" * 60)
22
+
23
+ # [1] 无 trace 时 span 是零成本 no-op(后台任务不该报错)
24
+ clear_trace()
25
+ assert current_trace() is None
26
+ with span("orphan", x=1) as s:
27
+ assert s is None
28
+ print("[1] 无 trace 时 span no-op(不报错/不产生垃圾)OK")
29
+
30
+ # [2] span 记录名称/耗时/meta;嵌套按结束顺序落盘
31
+ tr = start_trace("unit")
32
+ with span("outer", k="v"):
33
+ with span("inner"):
34
+ time.sleep(0.01)
35
+ d = tr.to_dict()
36
+ names = [s["name"] for s in d["spans"]]
37
+ assert names == ["inner", "outer"], names # inner 先结束先记
38
+ inner = d["spans"][0]
39
+ assert inner["ok"] is True and inner["ms"] >= 9, inner
40
+ assert d["spans"][1]["meta"] == {"k": "v"}
41
+ print(f"[2] span 记录(嵌套/耗时 {inner['ms']}ms/meta)OK")
42
+
43
+ # [3] 异常:记 ok=False + error,并原样抛出(不吞异常)
44
+ try:
45
+ with span("boom"):
46
+ raise ValueError("kaboom")
47
+ raise AssertionError("异常应原样抛出")
48
+ except ValueError:
49
+ pass
50
+ bad = [s for s in tr.to_dict()["spans"] if s["name"] == "boom"][0]
51
+ assert bad["ok"] is False and "kaboom" in bad["error"], bad
52
+ print("[3] span 异常记录 ok=False + 原样抛出 OK")
53
+
54
+ # [4] 单 trace span 上限(防病态循环撑爆内存)
55
+ t2 = Trace("cap")
56
+ for i in range(MAX_SPANS_PER_TRACE + 50):
57
+ t2.add_span({"name": f"s{i}"})
58
+ assert len(t2.to_dict()["spans"]) == MAX_SPANS_PER_TRACE and t2.to_dict()["dropped_spans"] == 50
59
+ print(f"[4] 单 trace span 封顶 {MAX_SPANS_PER_TRACE}(超出计 dropped)OK")
60
+
61
+ # [5] TraceStore 有界环存 + 按 id 取
62
+ st = TraceStore(limit=3)
63
+ ids = []
64
+ for i in range(5):
65
+ t = Trace(f"t{i}"); t.finish(); st.put(t); ids.append(t.trace_id)
66
+ assert len(st.list(limit=99)) == 3, "环存未封顶"
67
+ assert st.get(ids[-1]) is not None and st.get(ids[0]) is None, "应只留最近 3 条"
68
+ print("[5] TraceStore 有界环存(只留最近 N)+ 按 id 取 OK")
69
+
70
+ # [6] 线程隔离:contextvars 各线程独立,span 不串 trace
71
+ clear_trace()
72
+ res = {}
73
+ def worker(name):
74
+ t = start_trace(name)
75
+ with span("work"):
76
+ time.sleep(0.01)
77
+ res[name] = (t.trace_id, [s["name"] for s in t.to_dict()["spans"]])
78
+ ths = [threading.Thread(target=worker, args=(f"w{i}",)) for i in range(3)]
79
+ [t.start() for t in ths]; [t.join() for t in ths]
80
+ assert len(res) == 3 and len({v[0] for v in res.values()}) == 3, res
81
+ assert all(v[1] == ["work"] for v in res.values()), "各线程 span 应各归各的 trace"
82
+ print("[6] 线程隔离(3 线程 3 条独立 trace,span 不串)OK")
83
+
84
+ # ── 端到端:经 API 请求产生 trace ─────────────────────────
85
+ from fastapi.testclient import TestClient
86
+ from foxclaw.api import app
87
+
88
+ with TestClient(app) as c:
89
+ # [7] 数据面请求 → 带 X-Trace-Id + 可查详情;链路里能看到 LLM span
90
+ # (用 office:主管走路由管线,不经过 act/verify,埋点覆盖要用领域 Agent 验)
91
+ r = c.post("/api/task", json={"task": "追踪自测", "agent_id": "office"})
92
+ assert r.status_code == 200, r.status_code
93
+ tid = r.headers.get("X-Trace-Id")
94
+ assert tid, "响应应带 X-Trace-Id"
95
+ detail = c.get(f"/api/traces/{tid}").json()
96
+ assert detail["trace_id"] == tid and detail["duration_ms"] > 0, detail
97
+ names = [s["name"] for s in detail["spans"]]
98
+ llm = [n for n in names if n.startswith("llm.")]
99
+ assert llm, f"链路里应有 LLM span: {names}"
100
+ # 跨层传播:Agent 跑在 asyncio.to_thread 的线程里,它的 span 仍落进本请求的 trace
101
+ # → 证明 contextvars 确实穿过了线程边界(本设计的核心假设)
102
+ mem = [n for n in names if n.startswith("memory.")]
103
+ assert mem, f"跨线程的 Agent 记忆 span 应归属本 trace(contextvars 传播): {names}"
104
+ assert all(s["ms"] >= 0 for s in detail["spans"]), "每个 span 都应有耗时"
105
+ # 覆盖度:认知管线各 stage 都要在链路里(否则"慢在哪"会落在没埋点的暗区)
106
+ stages = [n for n in names if n.startswith("stage.")]
107
+ for must in ("stage.recall", "stage.act", "stage.verify"):
108
+ assert must in names, f"缺 {must},埋点覆盖退化了: {names}"
109
+ assert len(detail["spans"]) >= 6, f"链路过稀(疑似埋点退化): {names}"
110
+ print(f"[7] 请求→trace + X-Trace-Id;{detail['span_count']} span "
111
+ f"(管线 {len(stages)} stage + 跨线程 {mem[0]})OK")
112
+
113
+ # [8] 列表可查;未知 id → 404
114
+ lst = c.get("/api/traces?limit=5").json()["traces"]
115
+ assert any(t["trace_id"] == tid for t in lst), "最近列表应含刚才那条"
116
+ assert "spans" not in lst[0], "列表是摘要,不带 spans(省负载)"
117
+ assert c.get("/api/traces/deadbeef0000").status_code == 404
118
+ print(f"[8] /api/traces 列表({len(lst)} 条摘要)+ 未知 id 404 OK")
119
+
120
+ # [9] 探针/静态不追踪(不污染)
121
+ before = len(c.get("/api/traces?limit=200").json()["traces"])
122
+ c.get("/health"); c.get("/metrics")
123
+ after = c.get("/api/traces?limit=200").json()["traces"]
124
+ assert not any(t["name"].endswith("/health") for t in after), "探针不该被追踪"
125
+ print("[9] 探针 /health、/metrics 不进 trace OK")
126
+
127
+ # ── 持久化 + 采样(内存环重启即失;慢的/出错的必须留得住)──────────
128
+ import time as _t
129
+ store = TraceStore(limit=2)
130
+ store.attach_db(Path(os.environ["FOXCLAW_DATA_DIR"]) / "t.db")
131
+
132
+ def _mk(name, ms, err=False):
133
+ tr = Trace(name); tr.add_span({"name": "s", "ok": not err, "ms": ms})
134
+ tr.duration_ms = ms; tr.start_ts = _t.time()
135
+ return tr
136
+
137
+ # [10] 采样:慢的必存、出错的必存、正常的(采样率 0)不存
138
+ os.environ["FOXCLAW_TRACE_SLOW_MS"] = "500"; os.environ["FOXCLAW_TRACE_SAMPLE"] = "0"
139
+ slow = _mk("GET /slow", 900); fast = _mk("GET /fast", 5); bad = _mk("GET /err", 5, err=True)
140
+ for t in (slow, fast, bad):
141
+ store.put(t)
142
+ ids = {r["trace_id"] for r in store.query(limit=99)}
143
+ assert slow.trace_id in ids, "慢请求必须落库"
144
+ assert bad.trace_id in ids, "出错请求必须落库"
145
+ assert fast.trace_id not in ids, "正常请求(采样率0)不该落库"
146
+ print("[10] 采样策略(慢/出错必存、正常按采样率不存)OK")
147
+
148
+ # [11] 内存环滚出后,仍能从库里捞回慢请求 —— 这才是"查昨天那次慢请求"
149
+ for i in range(5): # 环 limit=2,把 slow 挤出内存
150
+ store.put(_mk(f"GET /x{i}", 1))
151
+ assert store.get(slow.trace_id) is None, "应已滚出内存环"
152
+ d = store.get_dict(slow.trace_id)
153
+ assert d and d.get("persisted") and d["duration_ms"] == 900 and d["spans"], d
154
+ print("[11] 内存环滚出后仍可从库捞回(重启/滚动后可查历史)OK")
155
+
156
+ # [12] 过滤:按慢阈值 / 只看出错
157
+ assert all(r["duration_ms"] >= 500 for r in store.query(limit=99, slow_ms=500))
158
+ errs = store.query(limit=99, errors_only=True)
159
+ assert errs and all(r["ok"] is False for r in errs), errs
160
+ print("[12] 历史查询过滤(slow_ms / errors_only)OK")
161
+
162
+ # [13] 保留上限:超出删最旧(磁盘有界)
163
+ os.environ["FOXCLAW_TRACE_RETAIN"] = "100"; os.environ["FOXCLAW_TRACE_SAMPLE"] = "1"
164
+ for i in range(140):
165
+ store.put(_mk(f"GET /bulk{i}", 1))
166
+ assert len(store.query(limit=999)) <= 100, "落库未按保留上限修剪"
167
+ os.environ["FOXCLAW_TRACE_SAMPLE"] = "0"
168
+ print("[13] 落库保留上限(超出删最旧,磁盘有界)OK")
169
+
170
+ # [14] 库不可用 → 优雅降级为仅内存(不能因为落库失败就把请求搞挂)
171
+ s2 = TraceStore(limit=2)
172
+ s2.attach_db("/nonexistent-dir-xyz/なし/t.db")
173
+ s2.put(_mk("GET /ok", 900)) # 不应抛异常
174
+ assert s2.query(limit=9) == [] and len(s2.list(limit=9)) == 1
175
+ print("[14] 库不可用 → 降级仅内存,不影响请求 OK")
176
+
177
+ # ── W3C Trace Context:零依赖跨进程传播(Foxclaw 唯一的跨进程边界=MCP 双向)──
178
+ from foxclaw.core.tracing import parse_traceparent, to_traceparent, current_traceparent
179
+
180
+ # [15] traceparent 解析:合法接受,非法/全零一律拒(不信外部输入)
181
+ good = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
182
+ assert parse_traceparent(good) == "4bf92f3577b34da6a3ce929d0e0e4736"
183
+ assert parse_traceparent("00-" + "0" * 32 + "-00f067aa0ba902b7-01") is None, "全零 trace-id 非法"
184
+ for bad_tp in (None, "", "garbage", "00-short-00f067aa0ba902b7-01", "99-" + "a" * 32 + "-" + "b" * 16 + "-01"):
185
+ assert parse_traceparent(bad_tp) is None, bad_tp
186
+ print("[15] traceparent 解析(合法接受/非法+全零拒绝)OK")
187
+
188
+ # [16] trace_id 符合 W3C 规格(32 hex)→ 出站 traceparent 可被对端接受
189
+ t16 = Trace("w3c")
190
+ assert len(t16.trace_id) == 32 and all(c in "0123456789abcdef" for c in t16.trace_id), t16.trace_id
191
+ tp = to_traceparent(t16.trace_id)
192
+ assert parse_traceparent(tp) == t16.trace_id, "自己生成的应能被自己解析(自洽)"
193
+ print("[16] trace_id 32hex 合规 + 出站 traceparent 自洽 OK")
194
+
195
+ # [17] 入站接链:带 traceparent 的请求应**复用**其 trace_id(两边链路对齐)
196
+ with TestClient(app) as c:
197
+ r = c.post("/api/task", json={"task": "接链自测", "agent_id": "supervisor"},
198
+ headers={"traceparent": good})
199
+ assert r.headers.get("X-Trace-Id") == "4bf92f3577b34da6a3ce929d0e0e4736", r.headers.get("X-Trace-Id")
200
+ # 非法 traceparent → 不采信,自开一条
201
+ r2 = c.post("/api/task", json={"task": "非法头", "agent_id": "supervisor"},
202
+ headers={"traceparent": "garbage"})
203
+ assert r2.headers.get("X-Trace-Id") and r2.headers["X-Trace-Id"] != "garbage"
204
+ print("[17] 入站接链(合法 traceparent 复用其 trace_id;非法则自开)OK")
205
+
206
+ # [18] 出站:current_traceparent 在 trace 内可用、trace 外为 None(MCP 调用据此带头)
207
+ clear_trace()
208
+ assert current_traceparent() is None
209
+ t18 = start_trace("out")
210
+ assert parse_traceparent(current_traceparent()) == t18.trace_id
211
+ clear_trace()
212
+ print("[18] 出站 current_traceparent(trace 内可用/trace 外 None)OK")
213
+
214
+ # ── OTLP/HTTP-JSON 导出(可选、零依赖、绝不阻塞请求)───────────────
215
+ from foxclaw.core.tracing import OTLP, _trace_to_otlp
216
+
217
+ # [19] 默认关:未配 endpoint → 完全不启用(零成本)
218
+ os.environ.pop("FOXCLAW_OTLP_ENDPOINT", None)
219
+ assert OTLP.stats()["enabled"] is False
220
+ OTLP.submit(_mk("GET /x", 5)) # 未配 endpoint → no-op,不该起线程/不该报错
221
+ print("[19] 未配 endpoint → OTLP 完全不启用(零成本)OK")
222
+
223
+ # [20] OTLP/JSON 结构:根 span + 子 span(parentSpanId 挂根)、traceId 32hex、状态码
224
+ t20 = start_trace("GET /demo") # 建 trace 并绑到当前上下文
225
+ with span("child.ok"): pass
226
+ try:
227
+ with span("child.bad"):
228
+ raise RuntimeError("x")
229
+ except RuntimeError:
230
+ pass
231
+ t20.finish(); clear_trace()
232
+ o = _trace_to_otlp(t20)
233
+ sp = o["resourceSpans"][0]["scopeSpans"][0]["spans"]
234
+ root = sp[0]; kids = sp[1:]
235
+ assert len(root["traceId"]) == 32 and root["name"] == "GET /demo" and root["kind"] == 2
236
+ assert all(k["parentSpanId"] == root["spanId"] for k in kids), "子 span 应挂在根 span 下"
237
+ assert all(k["traceId"] == t20.trace_id for k in kids), "同一 trace 的 span traceId 必须一致"
238
+ assert all(int(k["endTimeUnixNano"]) >= int(k["startTimeUnixNano"]) for k in sp), "结束不得早于开始"
239
+ bad = [k for k in kids if k["name"] == "child.bad"][0]
240
+ assert bad["status"]["code"] == 2 and root["status"]["code"] == 2, "出错应标 ERROR 并冒泡到根"
241
+ ok = [k for k in kids if k["name"] == "child.ok"][0]
242
+ assert ok["status"]["code"] == 1
243
+ svc = o["resourceSpans"][0]["resource"]["attributes"][0]
244
+ assert svc["key"] == "service.name" and svc["value"]["stringValue"] == "foxclaw"
245
+ print(f"[20] OTLP/JSON 结构(根+{len(kids)} 子 span·parentSpanId·32hex traceId·状态码)OK")
246
+
247
+ # [21] 端到端:起一个本地 collector,确认真的 POST /v1/traces 且 body 可解析
248
+ import http.server, socketserver, threading as _th2
249
+ received = []
250
+ class _H(http.server.BaseHTTPRequestHandler):
251
+ def do_POST(self):
252
+ n = int(self.headers.get("Content-Length", 0))
253
+ received.append((self.path, json.loads(self.rfile.read(n) or b"{}")))
254
+ self.send_response(200); self.end_headers(); self.wfile.write(b"{}")
255
+ def log_message(self, *a): pass
256
+ srv = socketserver.TCPServer(("127.0.0.1", 0), _H)
257
+ port = srv.server_address[1]
258
+ _th2.Thread(target=srv.serve_forever, daemon=True).start()
259
+ os.environ["FOXCLAW_OTLP_ENDPOINT"] = f"http://127.0.0.1:{port}"
260
+ t21 = _mk("GET /exported", 12)
261
+ OTLP.submit(t21)
262
+ for _ in range(50): # 后台线程异步发,等它落地
263
+ if received: break
264
+ _t.sleep(0.1)
265
+ srv.shutdown()
266
+ assert received, "collector 未收到 OTLP 上报"
267
+ path, body = received[0]
268
+ assert path == "/v1/traces", path
269
+ got = body["resourceSpans"][0]["scopeSpans"][0]["spans"][0]
270
+ assert got["traceId"] == t21.trace_id and got["name"] == "GET /exported", got
271
+ print("[21] 端到端导出(本地 collector 收到 POST /v1/traces,traceId 对得上)OK")
272
+
273
+ # [22] 队列满 → 丢弃计数增长,submit 不阻塞(遥测绝不能拖慢请求)
274
+ os.environ["FOXCLAW_OTLP_ENDPOINT"] = "http://127.0.0.1:1" # 打不通 → worker 卡住,队列会满
275
+ from foxclaw.core.tracing import _OtlpExporter
276
+ ex = _OtlpExporter(maxsize=2)
277
+ before = ex.dropped
278
+ t0_ = _t.perf_counter()
279
+ for i in range(50):
280
+ ex.submit(_mk(f"GET /f{i}", 1))
281
+ elapsed = (_t.perf_counter() - t0_) * 1000
282
+ assert ex.dropped > before, "队列满时应丢弃而非阻塞"
283
+ assert elapsed < 2000, f"submit 不该阻塞调用方(耗时 {elapsed:.0f}ms)"
284
+ os.environ.pop("FOXCLAW_OTLP_ENDPOINT", None)
285
+ print(f"[22] 队列满→丢弃 {ex.dropped} 条且不阻塞(50 次 submit 仅 {elapsed:.0f}ms)OK")
286
+
287
+ print("\n请求链路追踪验证全部通过")
288
+
289
+ if __name__ == "__main__":
290
+ test_tracing()
@@ -0,0 +1,127 @@
1
+ """
2
+ 真值走 HKC/KEE(v2 ④ Step1)测试 —— 全离线,假 HKC(不调 LLM)。
3
+
4
+ 覆盖:
5
+ 1. 默认关:on_truth 不结晶(行为不变)
6
+ 2. 启用:后台把真值 fact 经 hkc.client.ingest_text(KDE→KEE)结晶
7
+ 3. 被拦截任务跳过 / HKC 不可用跳过(不崩)
8
+ 4. 真实接线:Foxclaw 把结晶器订阅到 truth.appended;发该事件即触发(启用+假HKC)
9
+ """
10
+ import os, sys, asyncio, tempfile, shutil, types
11
+ from pathlib import Path
12
+ os.environ.setdefault("HF_HUB_OFFLINE", "1")
13
+ os.environ.setdefault("TRANSFORMERS_OFFLINE", "1")
14
+ os.environ["FOXCLAW_EMBEDDING_MODEL"] = "foxclaw-nonexistent-fast"
15
+ sys.path.insert(0, str(Path(__file__).parent.parent))
16
+
17
+ from foxclaw.knowledge import TruthCrystallizer
18
+
19
+
20
+ class _Client:
21
+ def __init__(self): self.ingested = []
22
+ def ingest_text(self, text, source_title="", domain="", **kw):
23
+ self.ingested.append((text, domain)); return None
24
+
25
+
26
+ class _HKC:
27
+ def __init__(self, available=True): self.available = available; self.client = _Client()
28
+
29
+
30
+ def _msg(payload): return types.SimpleNamespace(payload=payload)
31
+
32
+
33
+ def test_disabled_default():
34
+ print("\n【1】默认关:不结晶(行为不变)")
35
+ hkc = _HKC()
36
+ tc = TruthCrystallizer(hkc_provider=lambda: hkc, enabled=False)
37
+ tc.on_truth(_msg({"truth": {"fact": "毕昇发明活字印刷术"}, "agent_id": "office"}))
38
+ tc.drain()
39
+ assert hkc.client.ingested == [], "默认关不应结晶"
40
+ tc.close()
41
+ print(" ✓ enabled=False → 零结晶")
42
+
43
+
44
+ def test_enabled_crystallizes():
45
+ print("\n【2】启用:后台经 ingest_text(KDE→KEE)结晶真值")
46
+ hkc = _HKC()
47
+ tc = TruthCrystallizer(hkc_provider=lambda: hkc, enabled=True)
48
+ tc.on_truth(_msg({"truth": {"fact": "毕昇发明活字印刷术"}, "agent_id": "office"}))
49
+ tc.drain()
50
+ assert hkc.client.ingested == [("毕昇发明活字印刷术", "office")], hkc.client.ingested
51
+ tc.close()
52
+ print(f" ✓ 结晶: {hkc.client.ingested}(domain 取 agent_id)")
53
+
54
+
55
+ def test_blocked_and_unavailable_skipped():
56
+ print("\n【3】被拦截 / HKC 不可用 → 跳过(不崩)")
57
+ hkc = _HKC()
58
+ tc = TruthCrystallizer(hkc_provider=lambda: hkc, enabled=True)
59
+ tc.on_truth(_msg({"truth": {"fact": "x"}, "agent_id": "finance", "blocked": True}))
60
+ tc.drain()
61
+ assert hkc.client.ingested == [], "被拦截任务不结晶"
62
+ un = _HKC(available=False)
63
+ tc2 = TruthCrystallizer(hkc_provider=lambda: un, enabled=True)
64
+ tc2.on_truth(_msg({"truth": {"fact": "y"}, "agent_id": "finance"}))
65
+ tc2.drain()
66
+ assert un.client.ingested == [], "HKC 不可用则跳过"
67
+ tc.close(); tc2.close()
68
+ print(" ✓ blocked 跳过;HKC 不可用跳过、不崩")
69
+
70
+
71
+ def test_pending_bounded_no_leak():
72
+ print("\n【3b】深审回归:持续结晶下 _pending 不无界增长(prune 已完成 Future)")
73
+ hkc = _HKC()
74
+ tc = TruthCrystallizer(hkc_provider=lambda: hkc, enabled=True)
75
+ for i in range(200): # 模拟长期运行:大量 truth.appended
76
+ tc.on_truth(_msg({"truth": {"fact": f"事实{i}"}, "agent_id": "office"}))
77
+ tc.drain() # 等全部结晶完成
78
+ # 再来一发触发 prune,此时之前 200 个都已 done → 被清掉
79
+ tc.on_truth(_msg({"truth": {"fact": "末尾"}, "agent_id": "office"}))
80
+ import time as _t
81
+ for _ in range(50): # 等末尾这发也完成
82
+ if all(f.done() for f in tc._pending):
83
+ break
84
+ _t.sleep(0.02)
85
+ assert len(tc._pending) <= 2, f"_pending 应只留在飞行中的,实得 {len(tc._pending)}"
86
+ assert len(hkc.client.ingested) == 201
87
+ tc.close()
88
+ print(f" ✓ 提交 201 次,_pending 稳定在 {len(tc._pending)}(非 201)→ 无泄漏")
89
+
90
+
91
+ def test_integration_wiring():
92
+ print("\n【4】真实接线:Foxclaw 订阅 truth.appended → 触发结晶")
93
+ d = tempfile.mkdtemp(prefix="truthx_")
94
+ try:
95
+ from foxclaw import Foxclaw
96
+ os_ = Foxclaw(data_dir=d, shadow_rounds=50)
97
+ fake = _HKC()
98
+ os_.truth_crystallizer._hkc_provider = lambda: fake # 换成假 HKC(不调 LLM)
99
+ os_.truth_crystallizer.enabled = True
100
+ # 真值链末端事件 → 结晶器应被 Foxclaw 订阅到并触发
101
+ os_.system_bus.publish("truth.appended",
102
+ {"truth": {"fact": "测试真值事实"}, "agent_id": "finance"})
103
+ os_.truth_crystallizer.drain()
104
+ assert fake.client.ingested and fake.client.ingested[0][0] == "测试真值事实", \
105
+ f"应经订阅触发结晶,实得 {fake.client.ingested}"
106
+ print(f" ✓ 发 truth.appended → 结晶 {fake.client.ingested}")
107
+ asyncio.run(os_.aclose())
108
+ finally:
109
+ shutil.rmtree(d, ignore_errors=True)
110
+
111
+
112
+ def main():
113
+ print("=" * 60)
114
+ print("真值走 HKC/KEE · TruthCrystallizer(v2 ④ Step1)")
115
+ print("=" * 60)
116
+ test_disabled_default()
117
+ test_enabled_crystallizes()
118
+ test_blocked_and_unavailable_skipped()
119
+ test_pending_bounded_no_leak()
120
+ test_integration_wiring()
121
+ print("\n" + "=" * 60)
122
+ print("✓ Step1 验证完成:默认关(双写不变) + 启用后台结晶 + 跳过降级 + 订阅接线")
123
+ print("=" * 60)
124
+
125
+
126
+ if __name__ == "__main__":
127
+ main()
@@ -0,0 +1,93 @@
1
+ """
2
+ 真值流全路径触发(P2)测试 —— 全离线(stub LLM,bogus embedding 让 harness 走 hash)。
3
+
4
+ 审计 P2 前提"删重复落库"已被代码推翻(TruthPipeline 写的是**有意的共享真值记录**,
5
+ 非私有情景记忆的重复)。真正的问题是"共享真值记录只在主管分发路径产生"的不一致。
6
+ 本次修复:统一模板 run_task 也发 execution.completed → 直连领域 Agent 的任务同样进真值链;
7
+ 主管分发时子 Agent 带 _no_emit,由主管统一发(带 schedule),避免双发。
8
+
9
+ 覆盖:
10
+ 1. _no_emit 守卫:context['_no_emit']=True 时子 Agent 不自发 execution.completed
11
+ 2. 全路径触发:直连领域 Agent 任务发**恰好一条** execution.completed
12
+ 3. 共享真值记录:直连任务经 TruthPipeline 产生 shared=True 记录,别的 Agent 能召回
13
+ """
14
+ import os, sys, asyncio, tempfile, shutil
15
+ from pathlib import Path
16
+ os.environ.setdefault("HF_HUB_OFFLINE", "1")
17
+ os.environ.setdefault("TRANSFORMERS_OFFLINE", "1")
18
+ os.environ["FOXCLAW_EMBEDDING_MODEL"] = "foxclaw-nonexistent-fast" # harness 走 hash,快
19
+ sys.path.insert(0, str(Path(__file__).parent.parent))
20
+
21
+
22
+ def main():
23
+ print("=" * 60)
24
+ print("真值流 · 全路径触发(P2)")
25
+ print("=" * 60)
26
+ d = tempfile.mkdtemp(prefix="truth_")
27
+ try:
28
+ from foxclaw import Foxclaw
29
+ os_ = Foxclaw(data_dir=d, shadow_rounds=50)
30
+ seen = []
31
+ os_.event_spine.project(lambda e: seen.append(e.topic))
32
+
33
+ # 1. _no_emit 守卫:子 Agent 不自发
34
+ print("\n【1】_no_emit 守卫:分发时子 Agent 不自发 execution.completed")
35
+ asyncio.run(os_.finance.run_task("守卫测试", {"_no_emit": True}))
36
+ assert "execution.completed" not in seen, "带 _no_emit 时不应发 execution.completed"
37
+ print(" ✓ context['_no_emit']=True → 子 Agent 未自发(留给主管统一发)")
38
+
39
+ # 2. 全路径触发:直连任务发恰好一条
40
+ print("\n【2】全路径触发:直连领域 Agent 任务发恰好一条 execution.completed")
41
+ before = seen.count("execution.completed")
42
+ asyncio.run(os_.finance.run_task("分析我的月度开支"))
43
+ emitted = seen.count("execution.completed") - before
44
+ assert emitted == 1, f"直连任务应发恰好 1 条,实得 {emitted}"
45
+ print(f" ✓ 直连 finance.run_task → execution.completed ×{emitted}")
46
+
47
+ # 3. 共享真值记录:别的 Agent 能召回
48
+ print("\n【3】共享真值记录:直连任务经 TruthPipeline 产生 shared 记录,跨 Agent 可见")
49
+ shared = os_.hmr.recall(query="分析我的月度开支 处理", agent_id="office", top_k=10)
50
+ shared_hits = [m for m in shared if m.get("shared")]
51
+ assert shared_hits, "直连任务应产生 shared=True 真值记录(修复:此前仅分发路径有)"
52
+ print(f" ✓ office 召回到 {len(shared_hits)} 条共享真值记录(来自 finance 的直连任务)")
53
+ print(f" 样例: {shared_hits[0].get('content','')[:50]!r}")
54
+
55
+ # 4. 被拦截的直连任务:发 blocked=True、不落共享真值记录、CognitiveState 不崩
56
+ print("\n【4】被拦截的直连任务:blocked=True + 不落共享记录 + 观测不崩")
57
+ import types
58
+ from foxclaw.agents.base import HarnessBlockedError
59
+ async def boom(self, task, ctx, sid, mem):
60
+ raise HarnessBlockedError(types.SimpleNamespace(reason="越界"), None)
61
+ os_.finance._execute = types.MethodType(boom, os_.finance)
62
+ blocked_flags = []
63
+ os_.event_spine.project(
64
+ lambda e: blocked_flags.append(e.payload.get("blocked"))
65
+ if e.topic == "execution.completed" else None)
66
+ b_before = len([x for x in blocked_flags if x is True])
67
+ r = asyncio.run(os_.finance.run_task("危险越界任务"))
68
+ b_emitted = [x for x in blocked_flags if x is True]
69
+ assert r.get("error") == "harness_blocked"
70
+ assert len(b_emitted) - b_before == 1, "被拦截直连应发恰好一条 blocked=True"
71
+ # 精确判定:blocked 任务本身没产生真值记录(按内容过滤;语义召回可能带回 check2 的旧记录)
72
+ own = [m for m in os_.hmr.recall(query="危险越界任务 处理", agent_id="office", top_k=10)
73
+ if m.get("shared") and "危险越界" in m.get("content", "")]
74
+ assert len(own) == 0, "被拦截任务不应产生自己的共享真值记录(TruthPipeline 跳过 blocked)"
75
+ snap = os_.cognitive_state.snapshot() # 缺 schedule 的直连事件不应让观测崩溃
76
+ assert "active_agent" in snap
77
+ print(f" ✓ blocked=True ×1、无共享记录、CognitiveState 快照正常(focus={snap.get('semantic_focus')})")
78
+
79
+ asyncio.run(os_.aclose())
80
+ finally:
81
+ shutil.rmtree(d, ignore_errors=True)
82
+
83
+ print("\n" + "=" * 60)
84
+ print("✓ P2 验证完成:全路径触发 + _no_emit 防双发 + 共享真值记录一致")
85
+ print("=" * 60)
86
+
87
+
88
+ def test_truth_flow():
89
+ main()
90
+
91
+
92
+ if __name__ == "__main__":
93
+ main()