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.
- package/README.md +54 -29
- package/bin/foxclaw.cjs +51 -0
- package/index.js +11 -10
- package/package.json +46 -38
- package/python/.dockerignore +35 -0
- package/python/.env.example +165 -0
- package/python/.gitattributes +46 -0
- package/python/.github/workflows/ci.yml +122 -0
- package/python/.github/workflows/release.yml +183 -0
- package/python/Dockerfile +64 -0
- package/python/LICENSE +21 -0
- package/python/MANIFEST.in +14 -0
- package/python/README.md +192 -0
- package/python/README_CN.md +324 -0
- package/python/bench/bm25_audit.py +199 -0
- package/python/bench/bm25_regression.py +178 -0
- package/python/bench/bm25_stress.py +159 -0
- package/python/bench/cd_audit.py +238 -0
- package/python/bench/chaos.py +142 -0
- package/python/bench/concurrency.py +103 -0
- package/python/bench/e2e_real_models.py +95 -0
- package/python/bench/llm_retry_test.py +248 -0
- package/python/bench/load_test.py +196 -0
- package/python/bench/vector_ivf_upgrade.py +134 -0
- package/python/docs/CHANNELS.md +195 -0
- package/python/docs/GOVERNANCE.md +164 -0
- package/python/docs/RELEASE.md +109 -0
- package/python/docs/RUN.md +75 -0
- package/python/docs/RUN_CN.md +121 -0
- package/python/docs//351/205/215/347/275/256/346/214/207/345/215/227.md +152 -0
- package/python/evals/__init__.py +0 -0
- package/python/evals/governance/README.md +61 -0
- package/python/evals/governance/__init__.py +0 -0
- package/python/evals/governance/cases.py +152 -0
- package/python/evals/governance/compare_embeddings.py +94 -0
- package/python/evals/governance/convergence.svg +11 -0
- package/python/evals/governance/report.json +371 -0
- package/python/evals/governance/run_evals.py +400 -0
- package/python/examples/governance_middleware_demo.py +77 -0
- package/python/examples/langgraph_governance_demo.py +108 -0
- package/python/examples/sample_itinerary.csv +6 -0
- package/python/examples/sample_knowledge.csv +9 -0
- package/python/examples/sample_transactions.csv +14 -0
- package/python/foxclaw/__init__.py +526 -0
- package/python/foxclaw/agents/__init__.py +12 -0
- package/python/foxclaw/agents/agents.py +35 -0
- package/python/foxclaw/agents/base.py +865 -0
- package/python/foxclaw/agents/impl/__init__.py +1 -0
- package/python/foxclaw/agents/impl/_helpers.py +78 -0
- package/python/foxclaw/agents/impl/finance.py +248 -0
- package/python/foxclaw/agents/impl/office.py +367 -0
- package/python/foxclaw/agents/impl/supervisor.py +659 -0
- package/python/foxclaw/agents/mixins/_context.py +151 -0
- package/python/foxclaw/agents/mixins/augment.py +149 -0
- package/python/foxclaw/agents/mixins/llm.py +66 -0
- package/python/foxclaw/agents/mixins/memory.py +35 -0
- package/python/foxclaw/agents/mixins/preference.py +81 -0
- package/python/foxclaw/agents/mixins/prompt.py +77 -0
- package/python/foxclaw/agents/mixins/skillbox.py +58 -0
- package/python/foxclaw/agents/mixins/tool_exec.py +443 -0
- package/python/foxclaw/api/__init__.py +283 -0
- package/python/foxclaw/api/app.py +123 -0
- package/python/foxclaw/api/middleware.py +317 -0
- package/python/foxclaw/api/routes/ahs.py +417 -0
- package/python/foxclaw/api/routes/attachments.py +117 -0
- package/python/foxclaw/api/routes/browser.py +39 -0
- package/python/foxclaw/api/routes/domain.py +314 -0
- package/python/foxclaw/api/routes/knowledge.py +163 -0
- package/python/foxclaw/api/routes/llm_admin.py +461 -0
- package/python/foxclaw/api/routes/memory.py +89 -0
- package/python/foxclaw/api/routes/orchestration.py +41 -0
- package/python/foxclaw/api/routes/recovery.py +43 -0
- package/python/foxclaw/api/routes/review.py +275 -0
- package/python/foxclaw/api/routes/search.py +37 -0
- package/python/foxclaw/api/routes/system.py +327 -0
- package/python/foxclaw/api/routes/task.py +281 -0
- package/python/foxclaw/api/routes/ws.py +68 -0
- package/python/foxclaw/api/spine.py +55 -0
- package/python/foxclaw/browser/__init__.py +13 -0
- package/python/foxclaw/browser/action/__init__.py +1 -0
- package/python/foxclaw/browser/action/action_executor.py +54 -0
- package/python/foxclaw/browser/action/action_types.py +45 -0
- package/python/foxclaw/browser/action/action_verifier.py +68 -0
- package/python/foxclaw/browser/action/url_policy.py +147 -0
- package/python/foxclaw/browser/adapter/__init__.py +1 -0
- package/python/foxclaw/browser/adapter/base_adapter.py +23 -0
- package/python/foxclaw/browser/adapter/web_adapter.py +46 -0
- package/python/foxclaw/browser/driver/__init__.py +1 -0
- package/python/foxclaw/browser/driver/base_driver.py +61 -0
- package/python/foxclaw/browser/driver/playwright_driver.py +166 -0
- package/python/foxclaw/browser/perception/__init__.py +1 -0
- package/python/foxclaw/browser/perception/dom_extractor.py +43 -0
- package/python/foxclaw/browser/perception/element_indexer.py +57 -0
- package/python/foxclaw/browser/perception/semantic_reducer.py +33 -0
- package/python/foxclaw/browser/perception/snapshot.py +29 -0
- package/python/foxclaw/browser/service.py +148 -0
- package/python/foxclaw/browser/session/__init__.py +1 -0
- package/python/foxclaw/browser/session/session_manager.py +57 -0
- package/python/foxclaw/capability/__init__.py +12 -0
- package/python/foxclaw/capability/embedding_service.py +67 -0
- package/python/foxclaw/channels/__init__.py +140 -0
- package/python/foxclaw/channels/discord.py +184 -0
- package/python/foxclaw/channels/dispatcher.py +204 -0
- package/python/foxclaw/channels/feishu.py +225 -0
- package/python/foxclaw/channels/openclaw_bridge.py +156 -0
- package/python/foxclaw/channels/signing.py +187 -0
- package/python/foxclaw/channels/telegram.py +121 -0
- package/python/foxclaw/channels/weixin.py +225 -0
- package/python/foxclaw/cli.py +154 -0
- package/python/foxclaw/cognitive/__init__.py +4 -0
- package/python/foxclaw/cognitive/reasoner.py +82 -0
- package/python/foxclaw/cognitive/state.py +147 -0
- package/python/foxclaw/core/__init__.py +9 -0
- package/python/foxclaw/core/audit_chain.py +133 -0
- package/python/foxclaw/core/audit_sink.py +100 -0
- package/python/foxclaw/core/compaction.py +283 -0
- package/python/foxclaw/core/config.py +65 -0
- package/python/foxclaw/core/confirm_nonce.py +157 -0
- package/python/foxclaw/core/dispatch.py +27 -0
- package/python/foxclaw/core/envfile.py +48 -0
- package/python/foxclaw/core/failure_policy.py +151 -0
- package/python/foxclaw/core/gateway.py +85 -0
- package/python/foxclaw/core/governance_audit.py +175 -0
- package/python/foxclaw/core/idempotency.py +289 -0
- package/python/foxclaw/core/injection_guard.py +221 -0
- package/python/foxclaw/core/memory_adapter.py +516 -0
- package/python/foxclaw/core/metrics.py +89 -0
- package/python/foxclaw/core/model_router.py +1097 -0
- package/python/foxclaw/core/paths.py +103 -0
- package/python/foxclaw/core/recovery.py +362 -0
- package/python/foxclaw/core/scheduler.py +144 -0
- package/python/foxclaw/core/secrets.py +134 -0
- package/python/foxclaw/core/sessions.py +157 -0
- package/python/foxclaw/core/settings.py +430 -0
- package/python/foxclaw/core/shared_write.py +198 -0
- package/python/foxclaw/core/steering_queue.py +153 -0
- package/python/foxclaw/core/system_bus.py +84 -0
- package/python/foxclaw/core/thought_chain.py +318 -0
- package/python/foxclaw/core/token_meter.py +235 -0
- package/python/foxclaw/core/tools.py +172 -0
- package/python/foxclaw/core/tracing.py +390 -0
- package/python/foxclaw/core/truth_pipeline.py +114 -0
- package/python/foxclaw/core/vec_recall_adapter.py +503 -0
- package/python/foxclaw/core/verify.py +181 -0
- package/python/foxclaw/core/web_search.py +242 -0
- package/python/foxclaw/finance/__init__.py +15 -0
- package/python/foxclaw/finance/evidence.py +153 -0
- package/python/foxclaw/finance/fx.py +100 -0
- package/python/foxclaw/finance/graph.py +188 -0
- package/python/foxclaw/finance/models.py +150 -0
- package/python/foxclaw/finance/nlparse.py +575 -0
- package/python/foxclaw/finance/policy.py +329 -0
- package/python/foxclaw/finance/reasoner.py +129 -0
- package/python/foxclaw/finance/reasoning.py +340 -0
- package/python/foxclaw/finance/reconcile.py +180 -0
- package/python/foxclaw/finance/runtime.py +527 -0
- package/python/foxclaw/governance/__init__.py +24 -0
- package/python/foxclaw/governance/middleware.py +184 -0
- package/python/foxclaw/harness/__init__.py +17 -0
- package/python/foxclaw/harness/_base.py +72 -0
- package/python/foxclaw/harness/_policy.py +359 -0
- package/python/foxclaw/harness/drift.py +89 -0
- package/python/foxclaw/harness/event_bus.py +31 -0
- package/python/foxclaw/harness/harness.py +446 -0
- package/python/foxclaw/harness/loop_detection.py +152 -0
- package/python/foxclaw/itinerary/__init__.py +10 -0
- package/python/foxclaw/itinerary/amap_client.py +117 -0
- package/python/foxclaw/itinerary/models.py +80 -0
- package/python/foxclaw/itinerary/reasoner.py +101 -0
- package/python/foxclaw/itinerary/reasoning.py +174 -0
- package/python/foxclaw/itinerary/runtime.py +148 -0
- package/python/foxclaw/kernel/__init__.py +13 -0
- package/python/foxclaw/kernel/capabilities.py +73 -0
- package/python/foxclaw/kernel/policy_gate.py +114 -0
- package/python/foxclaw/kernel/process_manager.py +205 -0
- package/python/foxclaw/kernel/scheduler.py +131 -0
- package/python/foxclaw/kernel/sentinel.py +453 -0
- package/python/foxclaw/kernel/sentinel_client.py +246 -0
- package/python/foxclaw/kernel/spine.py +97 -0
- package/python/foxclaw/knowledge/__init__.py +376 -0
- package/python/foxclaw/knowledge/truth_crystallizer.py +92 -0
- package/python/foxclaw/learning/__init__.py +12 -0
- package/python/foxclaw/learning/knowledge_graph.py +149 -0
- package/python/foxclaw/learning/models.py +93 -0
- package/python/foxclaw/learning/reasoner.py +70 -0
- package/python/foxclaw/learning/runtime.py +363 -0
- package/python/foxclaw/learning/spaced_repetition.py +96 -0
- package/python/foxclaw/mcp_server.py +189 -0
- package/python/foxclaw/orchestration/__init__.py +184 -0
- package/python/foxclaw/pipeline/__init__.py +23 -0
- package/python/foxclaw/pipeline/cognitive_pipeline.py +481 -0
- package/python/foxclaw/pipeline/supervisor_stages.py +230 -0
- package/python/foxclaw/planning/__init__.py +17 -0
- package/python/foxclaw/planning/llm_planner.py +232 -0
- package/python/foxclaw/planning/planner.py +99 -0
- package/python/foxclaw/search/__init__.py +24 -0
- package/python/foxclaw/search/agent_port.py +194 -0
- package/python/foxclaw/search/evidence.py +113 -0
- package/python/foxclaw/search/external_search.py +50 -0
- package/python/foxclaw/search/hkc_read_port.py +60 -0
- package/python/foxclaw/search/internal_search.py +73 -0
- package/python/foxclaw/search/models.py +129 -0
- package/python/foxclaw/search/planner.py +44 -0
- package/python/foxclaw/search/retriever.py +39 -0
- package/python/foxclaw/search/runtime.py +134 -0
- package/python/foxclaw/search/strategy.py +44 -0
- package/python/foxclaw/skills/__init__.py +613 -0
- package/python/foxclaw/skills/hooks_spec.py +73 -0
- package/python/foxclaw/skills/lifecycle.py +278 -0
- package/python/foxclaw/skills/markdown_skill.py +174 -0
- package/python/foxclaw/skills/markdown_store.py +39 -0
- package/python/foxclaw/skills/mcp_client.py +190 -0
- package/python/foxclaw/skills/mcp_store.py +43 -0
- package/python/foxclaw/skills/permission.py +180 -0
- package/python/foxclaw/skills/pipeline.py +386 -0
- package/python/foxclaw/skills/policy.py +80 -0
- package/python/foxclaw/skills/review.py +72 -0
- package/python/foxclaw/skills/store.py +57 -0
- package/python/frontend/assets/core_bg.jpg +0 -0
- package/python/frontend/deck/auth.js +32 -0
- package/python/frontend/deck/chat_model_picker.js +537 -0
- package/python/frontend/deck/domain.js +296 -0
- package/python/frontend/deck/main.js +1950 -0
- package/python/frontend/deck/markdown.js +48 -0
- package/python/frontend/deck/settings.js +324 -0
- package/python/frontend/deck/state.js +6 -0
- package/python/frontend/deck/util.js +6 -0
- package/python/frontend/deck/visuals.js +212 -0
- package/python/frontend/deck.css +855 -0
- package/python/frontend/deck.html +686 -0
- package/python/frontend/deck_v2.html +341 -0
- package/python/frontend/planet/index.html +266 -0
- package/python/frontend/planet/planet.css +579 -0
- package/python/frontend/planet/planet.js +1637 -0
- package/python/frontend/planet/vendor/three.core.min.js +6 -0
- package/python/frontend/planet/vendor/three.module.min.js +6 -0
- package/python/frontend/v3/assets/index-BD7VPbMV.js +33 -0
- package/python/frontend/v3/assets/index-CAvnYWGH.css +1 -0
- package/python/frontend/v3/index.html +15 -0
- package/python/frontend/widget_runtime.js +295 -0
- package/python/frontend/widgets.js +696 -0
- package/python/frontend_src/index.html +14 -0
- package/python/frontend_src/package-lock.json +1376 -0
- package/python/frontend_src/package.json +19 -0
- package/python/frontend_src/src/App.vue +62 -0
- package/python/frontend_src/src/api.js +89 -0
- package/python/frontend_src/src/components/ChatDock.vue +377 -0
- package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
- package/python/frontend_src/src/components/DocViewer.vue +171 -0
- package/python/frontend_src/src/components/Inspector.vue +241 -0
- package/python/frontend_src/src/components/LeftRail.vue +394 -0
- package/python/frontend_src/src/components/MenuPopover.vue +130 -0
- package/python/frontend_src/src/components/MessageStream.vue +401 -0
- package/python/frontend_src/src/components/ModelPicker.vue +355 -0
- package/python/frontend_src/src/components/QuickIngest.vue +118 -0
- package/python/frontend_src/src/components/Stage.vue +226 -0
- package/python/frontend_src/src/components/ToolMenu.vue +144 -0
- package/python/frontend_src/src/components/TopBar.vue +90 -0
- package/python/frontend_src/src/components/panels/ActivityPanel.vue +84 -0
- package/python/frontend_src/src/components/panels/AgentsPanel.vue +86 -0
- package/python/frontend_src/src/components/panels/KnowledgePanel.vue +441 -0
- package/python/frontend_src/src/components/panels/MemoryPanel.vue +118 -0
- package/python/frontend_src/src/components/panels/PanelBox.vue +51 -0
- package/python/frontend_src/src/components/panels/ReviewPanel.vue +308 -0
- package/python/frontend_src/src/components/panels/SettingsPanel.vue +254 -0
- package/python/frontend_src/src/components/panels/TasksPanel.vue +74 -0
- package/python/frontend_src/src/components/panels/ToolsPanel.vue +640 -0
- package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
- package/python/frontend_src/src/lib/fmt.js +48 -0
- package/python/frontend_src/src/lib/md.js +204 -0
- package/python/frontend_src/src/main.js +7 -0
- package/python/frontend_src/src/stores/app.js +106 -0
- package/python/frontend_src/src/stores/chat.js +484 -0
- package/python/frontend_src/src/stores/model.js +104 -0
- package/python/frontend_src/src/stores/panels.js +330 -0
- package/python/frontend_src/src/styles/base.css +138 -0
- package/python/frontend_src/src/styles/panel.css +117 -0
- package/python/frontend_src/src/styles/tokens.css +78 -0
- package/python/frontend_src/vite.config.js +46 -0
- package/python/pyproject.toml +265 -0
- package/python/skills/README.md +123 -0
- package/python/skills/execution-contract/SKILL.md +30 -0
- package/python/skills/general-assistant/SKILL.md +34 -0
- package/python/skills/planner/SKILL.md +33 -0
- package/python/skills/schedule-itinerary/SKILL.md +35 -0
- package/python/skills/skill-composition/SKILL.md +31 -0
- package/python/skills/skill-guard/SKILL.md +67 -0
- package/python/skills/skill-memory/SKILL.md +50 -0
- package/python/skills/skill-router/SKILL.md +35 -0
- package/python/tests/conftest.py +76 -0
- package/python/tests/run_all.py +263 -0
- package/python/tests/test_agent_lifecycle.py +360 -0
- package/python/tests/test_ahs_p2.py +414 -0
- package/python/tests/test_anthropic_tools.py +206 -0
- package/python/tests/test_api.py +87 -0
- package/python/tests/test_api_auth.py +83 -0
- package/python/tests/test_api_host_guard.py +69 -0
- package/python/tests/test_attachments.py +167 -0
- package/python/tests/test_audit_chain.py +121 -0
- package/python/tests/test_audit_sink.py +136 -0
- package/python/tests/test_browser.py +153 -0
- package/python/tests/test_browser_request_guard.py +184 -0
- package/python/tests/test_capabilities.py +158 -0
- package/python/tests/test_channel_signing.py +192 -0
- package/python/tests/test_channel_trust.py +109 -0
- package/python/tests/test_channels.py +184 -0
- package/python/tests/test_cli.py +46 -0
- package/python/tests/test_compaction_toolpair.py +119 -0
- package/python/tests/test_config_layering.py +65 -0
- package/python/tests/test_confirm_nonce.py +197 -0
- package/python/tests/test_context_compaction_wiring.py +130 -0
- package/python/tests/test_dispatch.py +95 -0
- package/python/tests/test_domain_evals.py +403 -0
- package/python/tests/test_domain_import.py +78 -0
- package/python/tests/test_domain_reasoners.py +148 -0
- package/python/tests/test_e2e.py +178 -0
- package/python/tests/test_emb_cache.py +107 -0
- package/python/tests/test_embedding_singleton.py +89 -0
- package/python/tests/test_envfile.py +83 -0
- package/python/tests/test_evals_smoke.py +114 -0
- package/python/tests/test_event_spine.py +182 -0
- package/python/tests/test_evidence_semantic.py +101 -0
- package/python/tests/test_evidence_sse.py +97 -0
- package/python/tests/test_file_ingest.py +146 -0
- package/python/tests/test_finance.py +153 -0
- package/python/tests/test_finance_ledger.py +252 -0
- package/python/tests/test_finance_persist.py +178 -0
- package/python/tests/test_gateway.py +144 -0
- package/python/tests/test_governance.py +468 -0
- package/python/tests/test_governance_audit.py +359 -0
- package/python/tests/test_governance_middleware.py +143 -0
- package/python/tests/test_hkc.py +286 -0
- package/python/tests/test_hkc_index_build_manager.py +339 -0
- package/python/tests/test_hkc_manifest_reuse.py +317 -0
- package/python/tests/test_hkc_search_event_sync.py +533 -0
- package/python/tests/test_import_path_guard.py +161 -0
- package/python/tests/test_injection_guard.py +211 -0
- package/python/tests/test_integration.py +141 -0
- package/python/tests/test_itinerary.py +103 -0
- package/python/tests/test_kee_semantic.py +89 -0
- package/python/tests/test_kernel.py +222 -0
- package/python/tests/test_learning.py +110 -0
- package/python/tests/test_llm_admin.py +160 -0
- package/python/tests/test_llm_planner.py +366 -0
- package/python/tests/test_markdown_skills.py +73 -0
- package/python/tests/test_markdown_skills_openclaw.py +71 -0
- package/python/tests/test_mcp_connectors.py +109 -0
- package/python/tests/test_mcp_guardian_discovery.py +91 -0
- package/python/tests/test_mcp_no_memory.py +43 -0
- package/python/tests/test_mcp_policy_gate.py +77 -0
- package/python/tests/test_mcp_server.py +95 -0
- package/python/tests/test_memory_recall.py +95 -0
- package/python/tests/test_metrics.py +66 -0
- package/python/tests/test_multi_chain.py +102 -0
- package/python/tests/test_multimodal.py +135 -0
- package/python/tests/test_negotiation.py +146 -0
- package/python/tests/test_orchestration.py +119 -0
- package/python/tests/test_orchestration_decoupled.py +109 -0
- package/python/tests/test_p3_p5_hardening.py +338 -0
- package/python/tests/test_parallel_dispatch.py +215 -0
- package/python/tests/test_pipeline.py +533 -0
- package/python/tests/test_plan_visible.py +86 -0
- package/python/tests/test_planner.py +95 -0
- package/python/tests/test_policy_gate.py +109 -0
- package/python/tests/test_reasoner.py +185 -0
- package/python/tests/test_release_check.py +263 -0
- package/python/tests/test_resume_checkpoint.py +156 -0
- package/python/tests/test_routing.py +95 -0
- package/python/tests/test_scheduler.py +141 -0
- package/python/tests/test_search.py +204 -0
- package/python/tests/test_secrets.py +159 -0
- package/python/tests/test_semantic.py +78 -0
- package/python/tests/test_sessions.py +91 -0
- package/python/tests/test_settings.py +281 -0
- package/python/tests/test_shadow_learning.py +330 -0
- package/python/tests/test_shared_write.py +207 -0
- package/python/tests/test_skill_taint_gate.py +154 -0
- package/python/tests/test_skills.py +133 -0
- package/python/tests/test_stats_and_events.py +165 -0
- package/python/tests/test_streaming.py +60 -0
- package/python/tests/test_supervisor_pipeline.py +177 -0
- package/python/tests/test_supervisor_triage.py +108 -0
- package/python/tests/test_taint.py +117 -0
- package/python/tests/test_task_stream.py +85 -0
- package/python/tests/test_thinking_sse.py +67 -0
- package/python/tests/test_thinking_stream.py +76 -0
- package/python/tests/test_tool_center.py +129 -0
- package/python/tests/test_tool_parallel.py +113 -0
- package/python/tests/test_tool_params.py +103 -0
- package/python/tests/test_tool_use.py +242 -0
- package/python/tests/test_toplevel_concurrency.py +117 -0
- package/python/tests/test_tracing.py +290 -0
- package/python/tests/test_truth_crystallizer.py +127 -0
- package/python/tests/test_truth_flow.py +93 -0
- package/python/tests/test_url_policy.py +53 -0
- package/python/tests/test_verify_evidence.py +225 -0
- package/python/tests/test_version_sync.py +71 -0
- package/python/tests/test_web_trigger.py +99 -0
- package/python/tools/check_faiss_type.py +31 -0
- package/python/tools/check_log.ps1 +46 -0
- package/python/tools/ivf_status.py +72 -0
- package/python/tools/mypy_baseline.txt +5 -0
- package/python/tools/mypy_ratchet.py +97 -0
- package/python/tools/pack.py +178 -0
- package/python/tools/release_check.py +349 -0
- package/python/tools/restart_foxclaw.ps1 +74 -0
- package/python/tools/shared_write_audit.py +204 -0
- package/python/tools/size_baseline.txt +28 -0
- package/python/tools/size_check.py +102 -0
- package/python/tools/verify_env.bat +2 -0
- package/python/tools/verify_env.py +25 -0
- package/python/vendor/HMR_LICENSE +37 -0
- package/python/vendor/HMR_README.md +65 -0
- package/python/vendor/hkc/LICENSE +37 -0
- package/python/vendor/hkc/hkc_ace/__init__.py +0 -0
- package/python/vendor/hkc/hkc_ace/ace.py +457 -0
- package/python/vendor/hkc/hkc_ace/skill_taxonomy.json +465 -0
- package/python/vendor/hkc/hkc_api/__init__.py +0 -0
- package/python/vendor/hkc/hkc_api/container.py +416 -0
- package/python/vendor/hkc/hkc_api/ingest_jobs.py +103 -0
- package/python/vendor/hkc/hkc_api/main.py +157 -0
- package/python/vendor/hkc/hkc_api/routes/__init__.py +0 -0
- package/python/vendor/hkc/hkc_api/routes/ability.py +151 -0
- package/python/vendor/hkc/hkc_api/routes/config.py +51 -0
- package/python/vendor/hkc/hkc_api/routes/knowledge.py +630 -0
- package/python/vendor/hkc/hkc_api/routes/search.py +88 -0
- package/python/vendor/hkc/hkc_api/schemas.py +153 -0
- package/python/vendor/hkc/hkc_api/synthesis.py +179 -0
- package/python/vendor/hkc/hkc_core/__init__.py +0 -0
- package/python/vendor/hkc/hkc_core/graph/__init__.py +0 -0
- package/python/vendor/hkc/hkc_core/graph/base.py +188 -0
- package/python/vendor/hkc/hkc_core/graph/sqlite_store.py +789 -0
- package/python/vendor/hkc/hkc_core/models/__init__.py +0 -0
- package/python/vendor/hkc/hkc_core/models/enums.py +65 -0
- package/python/vendor/hkc/hkc_core/models/ku.py +307 -0
- package/python/vendor/hkc/hkc_core/utils/__init__.py +0 -0
- package/python/vendor/hkc/hkc_core/utils/id_gen.py +113 -0
- package/python/vendor/hkc/hkc_crystallizer/__init__.py +44 -0
- package/python/vendor/hkc/hkc_crystallizer/adapters.py +94 -0
- package/python/vendor/hkc/hkc_crystallizer/candidate.py +145 -0
- package/python/vendor/hkc/hkc_crystallizer/crystallizer.py +179 -0
- package/python/vendor/hkc/hkc_crystallizer/ingress.py +90 -0
- package/python/vendor/hkc/hkc_kde/__init__.py +0 -0
- package/python/vendor/hkc/hkc_kde/chunker.py +197 -0
- package/python/vendor/hkc/hkc_kde/extractor.py +623 -0
- package/python/vendor/hkc/hkc_kde/kde.py +298 -0
- package/python/vendor/hkc/hkc_kde/loaders/__init__.py +0 -0
- package/python/vendor/hkc/hkc_kde/loaders/ebook_loader.py +153 -0
- package/python/vendor/hkc/hkc_kde/loaders/office_loader.py +194 -0
- package/python/vendor/hkc/hkc_kde/loaders/pdf_loader.py +162 -0
- package/python/vendor/hkc/hkc_kde/loaders/text_loader.py +157 -0
- package/python/vendor/hkc/hkc_kde/loaders/web_loader.py +91 -0
- package/python/vendor/hkc/hkc_kde/models.py +74 -0
- package/python/vendor/hkc/hkc_kde/packager.py +329 -0
- package/python/vendor/hkc/hkc_kde/relation_disc.py +151 -0
- package/python/vendor/hkc/hkc_kee/__init__.py +0 -0
- package/python/vendor/hkc/hkc_kee/dedup.py +227 -0
- package/python/vendor/hkc/hkc_kee/fingerprint.py +106 -0
- package/python/vendor/hkc/hkc_kee/kee.py +541 -0
- package/python/vendor/hkc/hkc_kep/__init__.py +0 -0
- package/python/vendor/hkc/hkc_kep/event_bus.py +205 -0
- package/python/vendor/hkc/hkc_sdk/__init__.py +63 -0
- package/python/vendor/hkc/hkc_sdk/base.py +151 -0
- package/python/vendor/hkc/hkc_sdk/direct_client.py +153 -0
- package/python/vendor/hkc/hkc_sdk/exceptions.py +39 -0
- package/python/vendor/hkc/hkc_sdk/http_client.py +166 -0
- package/python/vendor/hkc/hkc_sdk/models.py +171 -0
- package/python/vendor/hkc/hkc_search/__init__.py +0 -0
- package/python/vendor/hkc/hkc_search/bm25.py +325 -0
- package/python/vendor/hkc/hkc_search/embedding_backends.py +374 -0
- package/python/vendor/hkc/hkc_search/graph_search.py +132 -0
- package/python/vendor/hkc/hkc_search/hybrid.py +331 -0
- package/python/vendor/hkc/hkc_search/index_build_manager.py +378 -0
- package/python/vendor/hkc/hkc_search/vector_search.py +484 -0
- package/python/vendor/hmr/__init__.py +0 -0
- package/python/vendor/hmr/core/__init__.py +0 -0
- package/python/vendor/hmr/core/hmr.py +684 -0
- package/python/vendor/hmr/core/models.py +310 -0
- package/python/vendor/hmr/engines/__init__.py +0 -0
- package/python/vendor/hmr/engines/evolution.py +710 -0
- package/python/vendor/hmr/engines/jit_compiler.py +472 -0
- package/python/vendor/hmr/engines/lifecycle.py +264 -0
- package/python/vendor/hmr/engines/policy.py +625 -0
- package/python/vendor/hmr/engines/recall.py +260 -0
- package/python/vendor/hmr/engines/runtime_state.py +158 -0
- package/python/vendor/hmr/engines/scheduler.py +323 -0
- package/python/vendor/hmr/engines/semantic.py +150 -0
- package/python/vendor/hmr/engines/temporal.py +261 -0
- package/python/vendor/hmr/engines/thought_chain.py +635 -0
- package/python/vendor/hmr/graph/__init__.py +0 -0
- package/python/vendor/hmr/graph/cwg.py +338 -0
- package/python/vendor/hmr/graph/memory_graph.py +530 -0
- package/python/vendor/hmr/storage/__init__.py +0 -0
- package/python/vendor/hmr/storage/memory_fs.py +300 -0
- package/python/vendor/hmr/storage/vector_store.py +520 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Foxclaw Core — 运行中干预队列 (Steering Queue)
|
|
3
|
+
|
|
4
|
+
借鉴 OpenClaw 的"租约队列"原理,适配 Foxclaw。解决的问题:
|
|
5
|
+
子任务完成的结果,如何安全地回收给主任务——
|
|
6
|
+
· 不丢:主任务崩了/没及时取,结果不能消失(租约过期 → 重新入队)
|
|
7
|
+
· 不重复:已被某个消费者租走的结果,不会被另一个消费者再拿一次
|
|
8
|
+
|
|
9
|
+
当前用途:多 Agent 顺序执行其实用不上(直接收集即可)。
|
|
10
|
+
设计目标:为**未来并行子 Agent**预留——主管并行派发多个子任务,
|
|
11
|
+
各自完成时间不定,本队列负责"完成一个安全回收一个,不丢不重"。
|
|
12
|
+
|
|
13
|
+
租约(Lease)机制:
|
|
14
|
+
· publish:子任务完成 → 结果入队(pending)
|
|
15
|
+
· lease:消费者租走一批结果(标记 leased + 租约到期时间),拿去注入主任务
|
|
16
|
+
· ack:消费者确认处理完 → 结果移除
|
|
17
|
+
· 过期未 ack 的 leased 项 → 自动回到 pending(主任务崩溃/超时的兜底)
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
import threading
|
|
21
|
+
import time
|
|
22
|
+
import uuid
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class SteeringItem:
|
|
28
|
+
item_id: str
|
|
29
|
+
requester: str # 谁在等这个结果(主任务/会话标识)
|
|
30
|
+
source_agent: str # 哪个子 Agent 产出的
|
|
31
|
+
payload: dict # 结果内容
|
|
32
|
+
created: float = field(default_factory=time.time)
|
|
33
|
+
leased_until: float = 0.0 # 0 = 未租出;>0 = 租约到期时间戳
|
|
34
|
+
lease_id: str = "" # 当前租约持有者标识
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def is_leased(self) -> bool:
|
|
38
|
+
return self.leased_until > time.time()
|
|
39
|
+
|
|
40
|
+
def to_dict(self) -> dict:
|
|
41
|
+
return {"item_id": self.item_id, "requester": self.requester,
|
|
42
|
+
"source_agent": self.source_agent, "payload": self.payload,
|
|
43
|
+
"created": self.created, "leased": self.is_leased}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SteeringQueue:
|
|
47
|
+
"""租约队列。线程安全,支持多消费者并发租取不重复。"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, lease_seconds: float = 30.0):
|
|
50
|
+
self.lease_seconds = lease_seconds
|
|
51
|
+
self._items: dict[str, SteeringItem] = {}
|
|
52
|
+
self._lock = threading.RLock()
|
|
53
|
+
|
|
54
|
+
def publish(self, requester: str, source_agent: str, payload: dict) -> str:
|
|
55
|
+
"""子任务完成 → 结果入队。返回 item_id。"""
|
|
56
|
+
with self._lock:
|
|
57
|
+
item_id = uuid.uuid4().hex[:16]
|
|
58
|
+
self._items[item_id] = SteeringItem(
|
|
59
|
+
item_id=item_id, requester=requester,
|
|
60
|
+
source_agent=source_agent, payload=payload)
|
|
61
|
+
return item_id
|
|
62
|
+
|
|
63
|
+
def lease(self, requester: str = None, max_items: int = 10) -> list[SteeringItem]:
|
|
64
|
+
"""租走一批待处理结果(可按 requester 过滤)。
|
|
65
|
+
|
|
66
|
+
原子地:挑出未被租用(或租约已过期)的项,标记为 leased,返回。
|
|
67
|
+
同一项不会被两个消费者同时租到(锁内完成挑选+标记)。
|
|
68
|
+
"""
|
|
69
|
+
with self._lock:
|
|
70
|
+
self._reclaim_expired()
|
|
71
|
+
now = time.time()
|
|
72
|
+
leased = []
|
|
73
|
+
lease_id = uuid.uuid4().hex[:12]
|
|
74
|
+
for item in self._items.values():
|
|
75
|
+
if len(leased) >= max_items:
|
|
76
|
+
break
|
|
77
|
+
if item.is_leased:
|
|
78
|
+
continue
|
|
79
|
+
if requester is not None and item.requester != requester:
|
|
80
|
+
continue
|
|
81
|
+
item.leased_until = now + self.lease_seconds
|
|
82
|
+
item.lease_id = lease_id
|
|
83
|
+
leased.append(item)
|
|
84
|
+
return leased
|
|
85
|
+
|
|
86
|
+
def ack(self, item_id: str, lease_id: str = None) -> bool:
|
|
87
|
+
"""消费者确认处理完 → 移除该项。
|
|
88
|
+
|
|
89
|
+
校验 lease_id:只有持有当前租约的消费者能 ack(防止过期租约误删
|
|
90
|
+
已被别人重新租走的项)。lease_id=None 时跳过校验(兼容简单场景)。
|
|
91
|
+
"""
|
|
92
|
+
with self._lock:
|
|
93
|
+
item = self._items.get(item_id)
|
|
94
|
+
if item is None:
|
|
95
|
+
return False
|
|
96
|
+
if lease_id is not None and item.lease_id != lease_id:
|
|
97
|
+
return False # 租约不匹配(可能已过期被别人租走)
|
|
98
|
+
del self._items[item_id]
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
def nack(self, item_id: str):
|
|
102
|
+
"""消费者放弃处理 → 立即释放租约,回到 pending(可被重新租取)。"""
|
|
103
|
+
with self._lock:
|
|
104
|
+
item = self._items.get(item_id)
|
|
105
|
+
if item:
|
|
106
|
+
item.leased_until = 0.0
|
|
107
|
+
item.lease_id = ""
|
|
108
|
+
|
|
109
|
+
def _reclaim_expired(self):
|
|
110
|
+
"""过期未 ack 的 leased 项 → 回到 pending(主任务崩溃/超时的兜底)。"""
|
|
111
|
+
now = time.time()
|
|
112
|
+
for item in self._items.values():
|
|
113
|
+
if 0 < item.leased_until <= now:
|
|
114
|
+
item.leased_until = 0.0
|
|
115
|
+
item.lease_id = ""
|
|
116
|
+
|
|
117
|
+
def pending_count(self, requester: str = None) -> int:
|
|
118
|
+
"""待处理(未被有效租用)的项数。"""
|
|
119
|
+
with self._lock:
|
|
120
|
+
self._reclaim_expired()
|
|
121
|
+
return sum(1 for it in self._items.values()
|
|
122
|
+
if not it.is_leased
|
|
123
|
+
and (requester is None or it.requester == requester))
|
|
124
|
+
|
|
125
|
+
def stats(self) -> dict:
|
|
126
|
+
with self._lock:
|
|
127
|
+
self._reclaim_expired()
|
|
128
|
+
total = len(self._items)
|
|
129
|
+
leased = sum(1 for it in self._items.values() if it.is_leased)
|
|
130
|
+
return {"total": total, "leased": leased, "pending": total - leased,
|
|
131
|
+
"lease_seconds": self.lease_seconds}
|
|
132
|
+
|
|
133
|
+
def drain(self, requester: str) -> list[dict]:
|
|
134
|
+
"""便捷方法(顺序场景用):取出某 requester 的待处理结果并清空。
|
|
135
|
+
|
|
136
|
+
适合"主任务等所有子任务完成后一次性收集"的顺序模式。
|
|
137
|
+
尊重租约:**跳过正被 lease 租用的项**,避免与 lease/ack 混用时
|
|
138
|
+
同一结果被处理两次(drain 拿一次 + 租约消费者再拿一次)。
|
|
139
|
+
"""
|
|
140
|
+
with self._lock:
|
|
141
|
+
self._reclaim_expired()
|
|
142
|
+
out = []
|
|
143
|
+
to_del = []
|
|
144
|
+
for item_id, item in self._items.items():
|
|
145
|
+
if item.requester != requester:
|
|
146
|
+
continue
|
|
147
|
+
if item.is_leased:
|
|
148
|
+
continue # 正被租用 → 留给租约消费者,drain 不碰
|
|
149
|
+
out.append(item.payload)
|
|
150
|
+
to_del.append(item_id)
|
|
151
|
+
for item_id in to_del:
|
|
152
|
+
del self._items[item_id]
|
|
153
|
+
return out
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
系统事件总线 (SystemBus)
|
|
3
|
+
|
|
4
|
+
进程内发布订阅总线,用于系统级事件流转(执行→审计→记忆)。
|
|
5
|
+
借鉴 Codex 版 Foxclaw 的 SystemBus,提升为本项目的全局事件中枢。
|
|
6
|
+
|
|
7
|
+
与 HarnessEventBus 的区别:HarnessEventBus 局限于单个 Agent 的 Harness 内部;
|
|
8
|
+
SystemBus 是系统级的,跨 Agent / 跨模块,且保留事件历史供审计回放。
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
import time
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Callable
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class BusMessage:
|
|
18
|
+
channel: str
|
|
19
|
+
payload: dict
|
|
20
|
+
ts: float = field(default_factory=time.time)
|
|
21
|
+
|
|
22
|
+
def to_dict(self) -> dict:
|
|
23
|
+
return {"channel": self.channel, "payload": self.payload, "ts": self.ts}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SystemBus:
|
|
27
|
+
"""系统级发布订阅总线。
|
|
28
|
+
|
|
29
|
+
【P0·统一事件脊柱】SystemBus 现已退化为 EventSpine 的**薄门面**:系统级事件
|
|
30
|
+
与 harness 事件汇入同一条脊柱,被同一个 WS 投影统一观测(修复"truth/process
|
|
31
|
+
到不了前端")。对既有消费者(TruthPipeline/ProcessManager/CognitiveState/主管分发)
|
|
32
|
+
零改动:publish/subscribe/history 签名不变;handler 收到的 Event 与旧 BusMessage
|
|
33
|
+
同形(都有 .channel/.payload/.to_dict())。
|
|
34
|
+
|
|
35
|
+
【P3-6/P4-1】subscribe/history 加 exact 参数,语义显式化:
|
|
36
|
+
· exact=False(默认):前缀匹配(EventSpine 原生行为)。订阅 "execution.completed"
|
|
37
|
+
会同时收到 "execution.completed" 和 "execution.completed.extra"(如果有)。
|
|
38
|
+
旧版独立 SystemBus 是精确匹配,门面化后语义悄悄变了——精确 channel 名的
|
|
39
|
+
订阅点应显式传 exact=True,前缀意图保持默认。
|
|
40
|
+
· exact=True:包一层过滤,只放行 topic 完全相等的事件(与旧版语义一致)。
|
|
41
|
+
|
|
42
|
+
【P4-1】调用点审查结论(grep `.subscribe(` 全仓):
|
|
43
|
+
✓ truth_pipeline.py ×3(execution.completed/trace.recorded/truth.appended)→ exact=True
|
|
44
|
+
✓ foxclaw/__init__.py(truth.appended → TruthCrystallizer)→ exact=True
|
|
45
|
+
✓ cognitive/state.py ×2(execution.completed/memory.updated)→ exact=True
|
|
46
|
+
· cognitive/state.py(task.lifecycle)→ 前缀意图(注释明示),保持 exact=False
|
|
47
|
+
· core/audit_sink.py(memory.shared.write)→ 直接持 spine 引用,不经本门面,不在范围
|
|
48
|
+
· knowledge/__init__.py → HKC 自有 event bus,非本总线,不在范围
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, history_limit: int = 500, spine=None):
|
|
52
|
+
if spine is None:
|
|
53
|
+
from ..kernel.spine import EventSpine
|
|
54
|
+
spine = EventSpine(history_limit=history_limit)
|
|
55
|
+
self.spine = spine
|
|
56
|
+
|
|
57
|
+
def subscribe(self, channel: str, handler: Callable, exact: bool = False):
|
|
58
|
+
"""订阅 channel。
|
|
59
|
+
|
|
60
|
+
exact=False(默认):前缀匹配。subscribe("execution") 收到
|
|
61
|
+
execution.completed / execution.failed 等。
|
|
62
|
+
exact=True:精确匹配。subscribe("execution.completed", exact=True)
|
|
63
|
+
只收到 topic 完全等于 "execution.completed" 的事件。
|
|
64
|
+
"""
|
|
65
|
+
if exact:
|
|
66
|
+
def _exact_handler(msg):
|
|
67
|
+
if getattr(msg, "topic", None) == channel:
|
|
68
|
+
handler(msg)
|
|
69
|
+
self.spine.subscribe(channel, _exact_handler)
|
|
70
|
+
else:
|
|
71
|
+
self.spine.subscribe(channel, handler)
|
|
72
|
+
|
|
73
|
+
def publish(self, channel: str, payload: dict):
|
|
74
|
+
return self.spine.publish(channel, payload, source="system_bus")
|
|
75
|
+
|
|
76
|
+
def history(self, channel: str = None, limit: int = 50,
|
|
77
|
+
exact: bool = False) -> list:
|
|
78
|
+
"""历史事件(dict 列表)。exact=True 时只返回 topic 完全匹配的事件
|
|
79
|
+
(默认前缀匹配,与 EventSpine 原生一致)。"""
|
|
80
|
+
events = self.spine.history(channel, limit)
|
|
81
|
+
if exact and channel:
|
|
82
|
+
events = [e for e in events
|
|
83
|
+
if (e.get("topic") or e.get("channel")) == channel]
|
|
84
|
+
return events
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ThoughtChain — 带 Harness 标注的推理图
|
|
3
|
+
每个节点是一个推理步骤,边是因果依赖
|
|
4
|
+
Harness 校验结果作为节点属性附着其上
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sqlite3
|
|
9
|
+
import json
|
|
10
|
+
import uuid
|
|
11
|
+
from contextlib import contextmanager
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from dataclasses import dataclass, asdict
|
|
14
|
+
from typing import Literal, Optional
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
NodeType = Literal[
|
|
19
|
+
"goal", # 目标基线节点(根)
|
|
20
|
+
"reasoning", # 推理步骤
|
|
21
|
+
"memory_retrieval", # 记忆检索
|
|
22
|
+
"tool_intent", # 工具意图
|
|
23
|
+
"tool_use", # 工具实际调用(augment.py 已在用;此前漏登记,类型面是欠账)
|
|
24
|
+
"plan", # 显式计划(steps/flow 落在 metadata,供回显与审计)
|
|
25
|
+
"decision", # 行动决策
|
|
26
|
+
"policy_update", # Policy 候选更新
|
|
27
|
+
"consolidation", # 记忆巩固写回
|
|
28
|
+
"reflection", # 执行后反思
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
HarnessStatus = Literal["BASELINE", "PASS", "WARN", "BLOCK", "PENDING", "REGRESSION"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class ThoughtNode:
|
|
36
|
+
"""ThoughtChain 的基本单元"""
|
|
37
|
+
id: str
|
|
38
|
+
session_id: str
|
|
39
|
+
agent_id: str
|
|
40
|
+
type: NodeType
|
|
41
|
+
content: str
|
|
42
|
+
conf: float # 置信度 0.0-1.0
|
|
43
|
+
mem_refs: list[str] # 引用的 HMR 记忆 ID
|
|
44
|
+
parent_ids: list[str] # 父节点 ID(因果依赖)
|
|
45
|
+
harness_status: HarnessStatus
|
|
46
|
+
harness_reason: Optional[str] # BLOCK/WARN 时的原因
|
|
47
|
+
harness_delta: Optional[float] # 目标漂移量
|
|
48
|
+
dominant_dim: Optional[str] # 主导漂移维度
|
|
49
|
+
tool_name: Optional[str] # 如果是 tool_intent 节点
|
|
50
|
+
entities: list[str] # 涉及的实体集合
|
|
51
|
+
timestamp: str
|
|
52
|
+
metadata: dict
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def create(
|
|
56
|
+
cls,
|
|
57
|
+
session_id: str,
|
|
58
|
+
agent_id: str,
|
|
59
|
+
type: NodeType,
|
|
60
|
+
content: str,
|
|
61
|
+
conf: float = 1.0,
|
|
62
|
+
mem_refs: list[str] = None,
|
|
63
|
+
parent_ids: list[str] = None,
|
|
64
|
+
tool_name: str = None,
|
|
65
|
+
entities: list[str] = None,
|
|
66
|
+
metadata: dict = None,
|
|
67
|
+
) -> "ThoughtNode":
|
|
68
|
+
return cls(
|
|
69
|
+
id=f"tn_{uuid.uuid4().hex[:12]}",
|
|
70
|
+
session_id=session_id,
|
|
71
|
+
agent_id=agent_id,
|
|
72
|
+
type=type,
|
|
73
|
+
content=content,
|
|
74
|
+
conf=conf,
|
|
75
|
+
mem_refs=mem_refs or [],
|
|
76
|
+
parent_ids=parent_ids or [],
|
|
77
|
+
harness_status="PENDING",
|
|
78
|
+
harness_reason=None,
|
|
79
|
+
harness_delta=None,
|
|
80
|
+
dominant_dim=None,
|
|
81
|
+
tool_name=tool_name,
|
|
82
|
+
entities=entities or [],
|
|
83
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
84
|
+
metadata=metadata or {},
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def to_dict(self) -> dict:
|
|
88
|
+
return asdict(self)
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def from_dict(cls, d: dict) -> "ThoughtNode":
|
|
92
|
+
return cls(**d)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ThoughtChain:
|
|
96
|
+
"""
|
|
97
|
+
ThoughtChain 存储与查询引擎
|
|
98
|
+
底层使用 SQLite,接口设计为对外标准化可替换
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(self, db_path: str = "./foxclaw_data/thoughtchain.db"):
|
|
102
|
+
self.db_path = Path(db_path).resolve()
|
|
103
|
+
os.makedirs(self.db_path.parent, exist_ok=True)
|
|
104
|
+
self._init_db()
|
|
105
|
+
|
|
106
|
+
def _init_db(self):
|
|
107
|
+
with self._conn() as conn:
|
|
108
|
+
conn.execute("""
|
|
109
|
+
CREATE TABLE IF NOT EXISTS thought_nodes (
|
|
110
|
+
id TEXT PRIMARY KEY,
|
|
111
|
+
session_id TEXT NOT NULL,
|
|
112
|
+
agent_id TEXT NOT NULL,
|
|
113
|
+
type TEXT NOT NULL,
|
|
114
|
+
content TEXT NOT NULL,
|
|
115
|
+
conf REAL NOT NULL,
|
|
116
|
+
mem_refs TEXT NOT NULL,
|
|
117
|
+
parent_ids TEXT NOT NULL,
|
|
118
|
+
harness_status TEXT NOT NULL,
|
|
119
|
+
harness_reason TEXT,
|
|
120
|
+
harness_delta REAL,
|
|
121
|
+
dominant_dim TEXT,
|
|
122
|
+
tool_name TEXT,
|
|
123
|
+
entities TEXT NOT NULL,
|
|
124
|
+
timestamp TEXT NOT NULL,
|
|
125
|
+
metadata TEXT NOT NULL
|
|
126
|
+
)
|
|
127
|
+
""")
|
|
128
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_session ON thought_nodes(session_id)")
|
|
129
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_agent ON thought_nodes(agent_id)")
|
|
130
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_harness ON thought_nodes(harness_status)")
|
|
131
|
+
|
|
132
|
+
@contextmanager
|
|
133
|
+
def _conn(self):
|
|
134
|
+
"""新建一条 SQLite 连接(每次调用即建即弃,不跨线程复用连接对象)。
|
|
135
|
+
|
|
136
|
+
并发加固:ThoughtChain 是**五个 Agent 共享**的审计存储,而写入发生在
|
|
137
|
+
`_call_llm` 的 `asyncio.to_thread` 工作线程 + FastAPI 请求线程里,必然跨线程并发写。
|
|
138
|
+
SQLite 默认 busy_timeout=0,锁竞争时会立刻抛 `OperationalError: database is locked`。
|
|
139
|
+
· timeout=5.0 —— 即 busy_timeout,写者遇锁最多等 5s 而非立刻报错;
|
|
140
|
+
· journal_mode=WAL —— 单写多读、读写不互斥,大幅降低锁竞争(审计读多写多)。
|
|
141
|
+
WAL 是数据库级持久设置,重复设置幂等无害。
|
|
142
|
+
|
|
143
|
+
生命周期语义:`with self._conn() as conn:` 退出时——正常则 commit,异常则
|
|
144
|
+
rollback,并**无论正常/异常都 close**。不能依赖 sqlite3.Connection 自带的
|
|
145
|
+
`with`(它退出只 commit/rollback 不 close,WAL 的 -wal/-shm 句柄会残留,
|
|
146
|
+
Windows 上删库会 WinError 32);也不能只用 closing()(它只 close 不 commit,
|
|
147
|
+
会把 INSERT/UPDATE 回滚导致并发写全丢)。
|
|
148
|
+
"""
|
|
149
|
+
conn = sqlite3.connect(self.db_path, timeout=5.0)
|
|
150
|
+
try:
|
|
151
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
152
|
+
except sqlite3.OperationalError:
|
|
153
|
+
pass # 个别文件系统不支持 WAL(如网络盘)→ 退回默认日志模式,timeout 仍生效
|
|
154
|
+
try:
|
|
155
|
+
yield conn
|
|
156
|
+
conn.commit()
|
|
157
|
+
except BaseException:
|
|
158
|
+
conn.rollback()
|
|
159
|
+
raise
|
|
160
|
+
finally:
|
|
161
|
+
conn.close()
|
|
162
|
+
|
|
163
|
+
def add_node(self, node: ThoughtNode) -> ThoughtNode:
|
|
164
|
+
"""写入节点,返回写入后的节点(Harness 标注前状态)"""
|
|
165
|
+
with self._conn() as conn:
|
|
166
|
+
conn.execute("""
|
|
167
|
+
INSERT INTO thought_nodes VALUES (
|
|
168
|
+
:id, :session_id, :agent_id, :type, :content, :conf,
|
|
169
|
+
:mem_refs, :parent_ids, :harness_status, :harness_reason,
|
|
170
|
+
:harness_delta, :dominant_dim, :tool_name, :entities,
|
|
171
|
+
:timestamp, :metadata
|
|
172
|
+
)
|
|
173
|
+
""", {
|
|
174
|
+
**node.to_dict(),
|
|
175
|
+
"mem_refs": json.dumps(node.mem_refs),
|
|
176
|
+
"parent_ids": json.dumps(node.parent_ids),
|
|
177
|
+
"entities": json.dumps(node.entities),
|
|
178
|
+
"metadata": json.dumps(node.metadata),
|
|
179
|
+
})
|
|
180
|
+
return node
|
|
181
|
+
|
|
182
|
+
def update_harness(
|
|
183
|
+
self,
|
|
184
|
+
node_id: str,
|
|
185
|
+
status: HarnessStatus,
|
|
186
|
+
reason: str = None,
|
|
187
|
+
delta: float = None,
|
|
188
|
+
dominant_dim: str = None,
|
|
189
|
+
):
|
|
190
|
+
"""Harness 校验完成后更新节点标注"""
|
|
191
|
+
with self._conn() as conn:
|
|
192
|
+
conn.execute("""
|
|
193
|
+
UPDATE thought_nodes
|
|
194
|
+
SET harness_status=?, harness_reason=?, harness_delta=?, dominant_dim=?
|
|
195
|
+
WHERE id=?
|
|
196
|
+
""", (status, reason, delta, dominant_dim, node_id))
|
|
197
|
+
|
|
198
|
+
def get_node(self, node_id: str) -> Optional[ThoughtNode]:
|
|
199
|
+
with self._conn() as conn:
|
|
200
|
+
row = conn.execute(
|
|
201
|
+
"SELECT * FROM thought_nodes WHERE id=?", (node_id,)
|
|
202
|
+
).fetchone()
|
|
203
|
+
return self._row_to_node(row) if row else None
|
|
204
|
+
|
|
205
|
+
def get_chain(self, session_id: str, agent_id: str = None) -> list[ThoughtNode]:
|
|
206
|
+
"""获取一个会话的完整推理链,按时间排序"""
|
|
207
|
+
with self._conn() as conn:
|
|
208
|
+
if agent_id:
|
|
209
|
+
rows = conn.execute(
|
|
210
|
+
"SELECT * FROM thought_nodes WHERE session_id=? AND agent_id=? ORDER BY timestamp",
|
|
211
|
+
(session_id, agent_id)
|
|
212
|
+
).fetchall()
|
|
213
|
+
else:
|
|
214
|
+
rows = conn.execute(
|
|
215
|
+
"SELECT * FROM thought_nodes WHERE session_id=? ORDER BY timestamp",
|
|
216
|
+
(session_id,)
|
|
217
|
+
).fetchall()
|
|
218
|
+
return [self._row_to_node(r) for r in rows]
|
|
219
|
+
|
|
220
|
+
def get_blocked_nodes(self, agent_id: str = None, limit: int = 50) -> list[ThoughtNode]:
|
|
221
|
+
"""获取被 Harness 截断的节点,用于审计"""
|
|
222
|
+
with self._conn() as conn:
|
|
223
|
+
if agent_id:
|
|
224
|
+
rows = conn.execute(
|
|
225
|
+
"SELECT * FROM thought_nodes WHERE harness_status='BLOCK' AND agent_id=? ORDER BY timestamp DESC LIMIT ?",
|
|
226
|
+
(agent_id, limit)
|
|
227
|
+
).fetchall()
|
|
228
|
+
else:
|
|
229
|
+
rows = conn.execute(
|
|
230
|
+
"SELECT * FROM thought_nodes WHERE harness_status='BLOCK' ORDER BY timestamp DESC LIMIT ?",
|
|
231
|
+
(limit,)
|
|
232
|
+
).fetchall()
|
|
233
|
+
return [self._row_to_node(r) for r in rows]
|
|
234
|
+
|
|
235
|
+
def search_nodes(self, keyword: str, agent_id: str = None,
|
|
236
|
+
limit: int = 20) -> list[ThoughtNode]:
|
|
237
|
+
"""按关键词在推理节点内容里检索(LIKE 子串匹配)。供 HSR 的 AgentSourcePort
|
|
238
|
+
把「各 Agent 想过/判过什么」连通进内部搜索。只读。"""
|
|
239
|
+
kw = (keyword or "").strip()
|
|
240
|
+
if not kw:
|
|
241
|
+
return []
|
|
242
|
+
like = f"%{kw}%"
|
|
243
|
+
with self._conn() as conn:
|
|
244
|
+
if agent_id:
|
|
245
|
+
rows = conn.execute(
|
|
246
|
+
"SELECT * FROM thought_nodes WHERE content LIKE ? AND agent_id=? "
|
|
247
|
+
"ORDER BY timestamp DESC LIMIT ?", (like, agent_id, limit)
|
|
248
|
+
).fetchall()
|
|
249
|
+
else:
|
|
250
|
+
rows = conn.execute(
|
|
251
|
+
"SELECT * FROM thought_nodes WHERE content LIKE ? "
|
|
252
|
+
"ORDER BY timestamp DESC LIMIT ?", (like, limit)
|
|
253
|
+
).fetchall()
|
|
254
|
+
return [self._row_to_node(r) for r in rows]
|
|
255
|
+
|
|
256
|
+
def get_delta_trend(self, agent_id: str, limit: int = 100) -> list[dict]:
|
|
257
|
+
"""获取漂移趋势数据,用于前端可视化"""
|
|
258
|
+
with self._conn() as conn:
|
|
259
|
+
rows = conn.execute("""
|
|
260
|
+
SELECT timestamp, harness_delta, harness_status, dominant_dim
|
|
261
|
+
FROM thought_nodes
|
|
262
|
+
WHERE agent_id=? AND harness_delta IS NOT NULL
|
|
263
|
+
ORDER BY timestamp DESC LIMIT ?
|
|
264
|
+
""", (agent_id, limit)).fetchall()
|
|
265
|
+
return [
|
|
266
|
+
{"timestamp": r[0], "delta": r[1], "status": r[2], "dim": r[3]}
|
|
267
|
+
for r in rows
|
|
268
|
+
]
|
|
269
|
+
|
|
270
|
+
def get_summary(self, agent_id: str) -> dict:
|
|
271
|
+
"""获取 Agent 的 ThoughtChain 健康摘要"""
|
|
272
|
+
with self._conn() as conn:
|
|
273
|
+
total = conn.execute(
|
|
274
|
+
"SELECT COUNT(*) FROM thought_nodes WHERE agent_id=?", (agent_id,)
|
|
275
|
+
).fetchone()[0]
|
|
276
|
+
blocked = conn.execute(
|
|
277
|
+
"SELECT COUNT(*) FROM thought_nodes WHERE agent_id=? AND harness_status='BLOCK'",
|
|
278
|
+
(agent_id,)
|
|
279
|
+
).fetchone()[0]
|
|
280
|
+
warned = conn.execute(
|
|
281
|
+
"SELECT COUNT(*) FROM thought_nodes WHERE agent_id=? AND harness_status='WARN'",
|
|
282
|
+
(agent_id,)
|
|
283
|
+
).fetchone()[0]
|
|
284
|
+
avg_conf = conn.execute(
|
|
285
|
+
"SELECT AVG(conf) FROM thought_nodes WHERE agent_id=? AND harness_status='PASS'",
|
|
286
|
+
(agent_id,)
|
|
287
|
+
).fetchone()[0] or 0.0
|
|
288
|
+
avg_delta = conn.execute(
|
|
289
|
+
"SELECT AVG(harness_delta) FROM thought_nodes WHERE agent_id=? AND harness_delta IS NOT NULL",
|
|
290
|
+
(agent_id,)
|
|
291
|
+
).fetchone()[0] or 0.0
|
|
292
|
+
return {
|
|
293
|
+
"agent_id": agent_id,
|
|
294
|
+
"total_nodes": total,
|
|
295
|
+
"blocked_nodes": blocked,
|
|
296
|
+
"warned_nodes": warned,
|
|
297
|
+
"block_rate": blocked / total if total > 0 else 0.0,
|
|
298
|
+
"avg_confidence": round(avg_conf, 3),
|
|
299
|
+
"avg_delta": round(avg_delta, 3),
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
def _row_to_node(self, row) -> ThoughtNode:
|
|
303
|
+
cols = [
|
|
304
|
+
"id", "session_id", "agent_id", "type", "content", "conf",
|
|
305
|
+
"mem_refs", "parent_ids", "harness_status", "harness_reason",
|
|
306
|
+
"harness_delta", "dominant_dim", "tool_name", "entities",
|
|
307
|
+
"timestamp", "metadata"
|
|
308
|
+
]
|
|
309
|
+
d = dict(zip(cols, row))
|
|
310
|
+
# JSON 字段容错:单行某字段损坏时降级为默认值,而非让整个审计链读取崩溃。
|
|
311
|
+
# 审计系统的可靠性要求"一条坏数据不拖垮全部"。
|
|
312
|
+
for field, default in (("mem_refs", []), ("parent_ids", []),
|
|
313
|
+
("entities", []), ("metadata", {})):
|
|
314
|
+
try:
|
|
315
|
+
d[field] = json.loads(d[field])
|
|
316
|
+
except (json.JSONDecodeError, TypeError):
|
|
317
|
+
d[field] = default
|
|
318
|
+
return ThoughtNode(**d)
|