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,89 @@
|
|
|
1
|
+
"""Harness 三个检测维度:语义距离 / 工具意图 / 范围蔓延。"""
|
|
2
|
+
import hashlib
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from sklearn.metrics.pairwise import cosine_similarity
|
|
6
|
+
|
|
7
|
+
from ._base import TOOL_CATEGORIES
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# 文本向量由 foxclaw.capability.embedding_service 统一提供(进程内共享,同名只加载一次)。
|
|
11
|
+
# 不可用时该服务返回 None,SemanticDrift 自动降级为 n-gram 哈希嵌入。
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SemanticDrift:
|
|
15
|
+
"""维度一:语义距离。优先 sentence-transformers,不可用则降级 n-gram 哈希嵌入。"""
|
|
16
|
+
|
|
17
|
+
DIM = 256
|
|
18
|
+
|
|
19
|
+
def __init__(self, model_name: str = "all-MiniLM-L6-v2"):
|
|
20
|
+
# P0:走进程内共享 EmbeddingService,同名模型全系统只加载一次
|
|
21
|
+
# (原来每个 Harness 各 load 一份;5 Agent = 5 份 → 现 1 份共享,只读并发 encode 安全)。
|
|
22
|
+
from ..capability.embedding_service import get_text_embedder
|
|
23
|
+
self._model = get_text_embedder(model_name)
|
|
24
|
+
self._mode = "transformer" if self._model is not None else "hash"
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def mode(self) -> str:
|
|
28
|
+
return self._mode
|
|
29
|
+
|
|
30
|
+
def _encode(self, text: str) -> np.ndarray:
|
|
31
|
+
if self._mode == "transformer":
|
|
32
|
+
return self._model.encode(text)
|
|
33
|
+
return self._hash_embed(text)
|
|
34
|
+
|
|
35
|
+
def _hash_embed(self, text: str) -> np.ndarray:
|
|
36
|
+
"""字符 3-gram 哈希到固定维向量。余弦相似度数学真实,只是不带语义模型。"""
|
|
37
|
+
vec = np.zeros(self.DIM, dtype=np.float32)
|
|
38
|
+
text = (text or "").lower().strip()
|
|
39
|
+
if not text:
|
|
40
|
+
return vec
|
|
41
|
+
grams = [text[i:i+3] for i in range(max(len(text) - 2, 1))]
|
|
42
|
+
for g in grams:
|
|
43
|
+
h = int(hashlib.md5(g.encode("utf-8")).hexdigest(), 16)
|
|
44
|
+
idx = h % self.DIM
|
|
45
|
+
sign = 1.0 if (h // self.DIM) % 2 == 0 else -1.0
|
|
46
|
+
vec[idx] += sign
|
|
47
|
+
norm = np.linalg.norm(vec)
|
|
48
|
+
return vec / norm if norm > 0 else vec
|
|
49
|
+
|
|
50
|
+
def encode(self, text: str) -> np.ndarray:
|
|
51
|
+
"""把文本编码成向量(纯函数,无副作用)。每任务基线自持此向量,并发安全。"""
|
|
52
|
+
return self._encode(text)
|
|
53
|
+
|
|
54
|
+
def score(self, content: str, baseline_vec: np.ndarray) -> float:
|
|
55
|
+
"""内容相对**每任务基线向量**的语义漂移。baseline_vec 由调用方(check)显式传入,
|
|
56
|
+
不再用共享的实例级基线 → 并发顶层任务的语义基线互不覆盖。"""
|
|
57
|
+
if baseline_vec is None:
|
|
58
|
+
return 0.0
|
|
59
|
+
node_vec = self._encode(content)
|
|
60
|
+
sim = cosine_similarity([baseline_vec], [node_vec])[0][0]
|
|
61
|
+
# 余弦相似度范围 [-1, 1],哈希嵌入带符号累加可能产生负相似度,
|
|
62
|
+
# 1 - sim 会超过 1.0 → 必须双向裁剪到 [0, 1],否则漂移分超出阈值范围使判定失真。
|
|
63
|
+
return float(min(1.0, max(0.0, 1.0 - sim)))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ToolIntentDrift:
|
|
67
|
+
"""维度二:工具意图"""
|
|
68
|
+
|
|
69
|
+
def score(self, tool_name: str, allowed_categories: set[str]) -> float:
|
|
70
|
+
if not tool_name:
|
|
71
|
+
return 0.0
|
|
72
|
+
for cat, patterns in TOOL_CATEGORIES.items():
|
|
73
|
+
if any(tool_name.startswith(p) or p in tool_name for p in patterns):
|
|
74
|
+
return 0.0 if cat in allowed_categories else 1.0
|
|
75
|
+
return 0.3 # 未知工具:中等风险
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ScopeCreepDrift:
|
|
79
|
+
"""维度三:范围蔓延"""
|
|
80
|
+
|
|
81
|
+
def score(self, node_entities: set[str], baseline_entities: set[str]) -> float:
|
|
82
|
+
# 空基线返回 0 是有意设计:范围蔓延是"相对基线的偏离",没有基线参照就无从
|
|
83
|
+
# 谈蔓延。若此处对空基线返回高分,会让所有尚未建立实体基线的早期任务被误判为
|
|
84
|
+
# 范围越界(误报)。范围检测的有效性依赖基线实体提取——若该维度长期为 0,
|
|
85
|
+
# 应排查实体提取(register_goal 的 entities)是否为空,而非改动本函数。
|
|
86
|
+
if not baseline_entities:
|
|
87
|
+
return 0.0
|
|
88
|
+
new_entities = node_entities - baseline_entities
|
|
89
|
+
return min(len(new_entities) / max(len(baseline_entities), 1), 1.0)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Harness 事件总线(观测优先)。"""
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class HarnessEventBus:
|
|
6
|
+
"""阶段一:事件总线(观测优先)"""
|
|
7
|
+
|
|
8
|
+
# 高频且纯内部的信号不上脊柱,避免 WS 噪音(见审计"哪些事件不应该广播")
|
|
9
|
+
_SPINE_SKIP = {"goal_registered"}
|
|
10
|
+
|
|
11
|
+
def __init__(self, spine=None, source: str = ""):
|
|
12
|
+
self._handlers: dict[str, list[Callable]] = {}
|
|
13
|
+
self._spine = spine # 【P0·统一脊柱】非空则把 emit 同时转发进全局 EventSpine
|
|
14
|
+
self._source = source # 事件来源(agent_id),便于投影侧区分
|
|
15
|
+
|
|
16
|
+
def on(self, event: str, handler: Callable):
|
|
17
|
+
self._handlers.setdefault(event, []).append(handler)
|
|
18
|
+
|
|
19
|
+
def emit(self, event: str, data: dict):
|
|
20
|
+
# 1) 本地订阅者(向后兼容:agent.harness.bus.on / harness 自订阅 / governance)
|
|
21
|
+
for h in self._handlers.get(event, []):
|
|
22
|
+
try:
|
|
23
|
+
h(data)
|
|
24
|
+
except Exception as e:
|
|
25
|
+
print(f"[Harness EventBus] handler error: {e}")
|
|
26
|
+
# 2) 汇入全局脊柱:harness 事件与系统事件走同一条脊柱、被同一 WS 投影观测
|
|
27
|
+
if self._spine is not None and event not in self._SPINE_SKIP:
|
|
28
|
+
try:
|
|
29
|
+
self._spine.publish(event, data, source=self._source or "harness")
|
|
30
|
+
except Exception:
|
|
31
|
+
pass
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Harness — 跨层约束与校准机制
|
|
3
|
+
不是某一层的模块,而是每一层都能感知的治理张力
|
|
4
|
+
|
|
5
|
+
三个检测维度:
|
|
6
|
+
1. 语义距离 (semantic) — 节点内容 vs 目标基线
|
|
7
|
+
2. 工具意图 (tool) — 工具类别 vs 允许集合
|
|
8
|
+
3. 范围蔓延 (scope) — 当前实体 vs 初始实体集
|
|
9
|
+
|
|
10
|
+
Shadow mode:前 N 次只观测不截断,用真实数据校准阈值
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import uuid
|
|
16
|
+
import threading
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
from ..core.thought_chain import ThoughtChain, ThoughtNode, HarnessStatus
|
|
22
|
+
|
|
23
|
+
# ── S3 步骤 4:按检测维度拆分(纯搬移、零行为变更)────────────────────────
|
|
24
|
+
from ._base import (
|
|
25
|
+
HarnessResult,
|
|
26
|
+
GoalBaseline,
|
|
27
|
+
TOOL_CATEGORIES,
|
|
28
|
+
_harness_ctx_get,
|
|
29
|
+
_harness_ctx_set,
|
|
30
|
+
)
|
|
31
|
+
from .drift import SemanticDrift, ToolIntentDrift, ScopeCreepDrift
|
|
32
|
+
from .event_bus import HarnessEventBus
|
|
33
|
+
from ._policy import PolicyMixin
|
|
34
|
+
|
|
35
|
+
# 历史导入兼容:`from foxclaw.harness.harness import X` 的 X 在此 re-export。
|
|
36
|
+
__all__ = [
|
|
37
|
+
"Harness", "HarnessResult", "GoalBaseline",
|
|
38
|
+
"SemanticDrift", "ToolIntentDrift", "ScopeCreepDrift",
|
|
39
|
+
"HarnessEventBus", "TOOL_CATEGORIES",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Harness(PolicyMixin):
|
|
44
|
+
"""
|
|
45
|
+
Harness 主引擎
|
|
46
|
+
每个 Agent 实例化一个,共享 ThoughtChain 存储
|
|
47
|
+
"""
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
agent_id: str,
|
|
51
|
+
thought_chain: ThoughtChain,
|
|
52
|
+
policy_path: str = "./foxclaw_data/policy.yaml",
|
|
53
|
+
shadow_rounds: int = 20,
|
|
54
|
+
embedding_model: str = None,
|
|
55
|
+
spine=None, # 【P0】全局 EventSpine(可空:standalone/governance 场景)
|
|
56
|
+
promote_min_obs: int = None, # 【P0-1】影子规则升迁所需观察次数(None→env/默认 50)
|
|
57
|
+
):
|
|
58
|
+
# embedding 模型解析 —— 【P2·单 embedding 入口】优先级:
|
|
59
|
+
# 1. 显式传参(测试/单 Agent 切模型用)
|
|
60
|
+
# 2. EMBEDDING_MODEL(系统唯一本地 embedding 入口,.env 一处配 bge-m3 全局生效)
|
|
61
|
+
# 3. HKC_EMBEDDING_MODEL(向后兼容,EMBEDDING_MODEL 未设时回退)
|
|
62
|
+
# 4. FOXCLAW_EMBEDDING_MODEL(向后兼容)
|
|
63
|
+
# 5. all-MiniLM-L6-v2(传统兜底)
|
|
64
|
+
# 中文部署设 paraphrase-multilingual-MiniLM-L12-v2 时,语义漂移对中文硬拦率
|
|
65
|
+
# 从 0 升到 1.0(见 evals/governance/compare_embeddings.py)。
|
|
66
|
+
embedding_model = (embedding_model
|
|
67
|
+
or os.environ.get("EMBEDDING_MODEL")
|
|
68
|
+
or os.environ.get("HKC_EMBEDDING_MODEL")
|
|
69
|
+
or os.environ.get("FOXCLAW_EMBEDDING_MODEL")
|
|
70
|
+
or "all-MiniLM-L6-v2")
|
|
71
|
+
self.agent_id = agent_id
|
|
72
|
+
self.thought_chain = thought_chain
|
|
73
|
+
self.policy_path = Path(policy_path).resolve()
|
|
74
|
+
self.shadow_rounds = shadow_rounds
|
|
75
|
+
# per-实例分槽 token(目标基线走 contextvar,见文件顶部)
|
|
76
|
+
self._ctx_token = uuid.uuid4().hex
|
|
77
|
+
# 累积学习态(_delta_history/_run_count/weights/_policy/阈值)的并发锁。
|
|
78
|
+
# 为把请求 offload 到线程池提速做准备:多线程会并发调 check 打同一 harness,
|
|
79
|
+
# 昂贵的语义嵌入放锁外(线程安全、可并行),只有廉价的累积态改写进锁。RLock:
|
|
80
|
+
# _trigger_policy_learning 在锁内调用,同线程可重入。
|
|
81
|
+
self._lock = threading.RLock()
|
|
82
|
+
|
|
83
|
+
# 检测引擎
|
|
84
|
+
self.sem_drift = SemanticDrift(embedding_model)
|
|
85
|
+
self.tool_drift = ToolIntentDrift()
|
|
86
|
+
self.scope_drift = ScopeCreepDrift()
|
|
87
|
+
|
|
88
|
+
# 事件总线:本地订阅(向后兼容)+ 转发进全局脊柱(spine 非空时)
|
|
89
|
+
self.bus = HarnessEventBus(spine=spine, source=agent_id)
|
|
90
|
+
|
|
91
|
+
# 目标基线(每任务)—— 由 contextvar 承载;_baseline 是路由到 contextvar 的
|
|
92
|
+
# property,register_goal/check 里对 self._baseline 的读写代码完全不变。
|
|
93
|
+
self._baseline = None
|
|
94
|
+
|
|
95
|
+
# 权重(Policy 学习后动态调整)
|
|
96
|
+
self.weights = {"semantic": 0.5, "tool": 0.3, "scope": 0.2}
|
|
97
|
+
|
|
98
|
+
# 是否因「语义漂移」而 BLOCK。领域 Agent=True(执行业务任务时跑题要拦);
|
|
99
|
+
# 调度层(主管)=False(接收任意输入是其职责,语义距离大不算异常)。
|
|
100
|
+
self.block_on_semantic = True
|
|
101
|
+
|
|
102
|
+
# ── 降级方向修正(B 类修复)────────────────────────────────────
|
|
103
|
+
# 语义嵌入不可用时(SemanticDrift.mode == "hash"),3-gram 哈希对中文几乎
|
|
104
|
+
# 无区分度 —— 实测同域相似度 -0.17、跨域 +0.00,差异 <0.17 → 漂移分
|
|
105
|
+
# 1-sim 裁剪后**恒为 1.0**。该维度一旦参与加权(semantic 权重 0.5),
|
|
106
|
+
# Δ 恒 0.5 > block 阈值 0.25 → **所有请求一律 BLOCK**,包括评测集里
|
|
107
|
+
# ground-truth 标注为 pass 的正常请求。
|
|
108
|
+
# 治理层从「精准拦截」退化为「无差别拦截」,比不治理更危险(阻塞全部正常
|
|
109
|
+
# 业务),且失败是静默的(仅 mode 属性变化,无告警)。
|
|
110
|
+
#
|
|
111
|
+
# 正确处置:安全组件的降级方向必须是 **fail-open(失效但可见)**,
|
|
112
|
+
# 而不是 fail-closed-with-garbage(过度拦截且静默)。故降级时**停用语义
|
|
113
|
+
# 维度**,而 tool/scope 两个维度是确定性规则(工具类别匹配、实体集合运算),
|
|
114
|
+
# 不依赖模型质量,降级后仍可信,照常工作。
|
|
115
|
+
self.semantic_degraded: bool = (self.sem_drift.mode != "transformer")
|
|
116
|
+
# 降级只告警一次,避免每节点刷屏
|
|
117
|
+
self._degrade_warned: bool = False
|
|
118
|
+
|
|
119
|
+
# Shadow mode 状态
|
|
120
|
+
self._shadow_mode = True
|
|
121
|
+
self._run_count = 0
|
|
122
|
+
self._delta_history: list[float] = []
|
|
123
|
+
|
|
124
|
+
# 阈值(shadow mode 结束后用真实数据替换)
|
|
125
|
+
self._warn_threshold = 0.15
|
|
126
|
+
self._block_threshold = 0.25
|
|
127
|
+
|
|
128
|
+
# Policy
|
|
129
|
+
self._policy: dict = self._load_policy()
|
|
130
|
+
# 【P0-1】影子学习段(旧 YAML 没有 → 补空段;不重命名既有键,版本兼容)
|
|
131
|
+
self._policy.setdefault("shadow_rules", [])
|
|
132
|
+
self._policy.setdefault("history", [])
|
|
133
|
+
|
|
134
|
+
# 【P0-1】影子观察达标线:observations 凑满才做 promote/discard 判定。
|
|
135
|
+
# 显式构造参数 > 环境变量 > 默认 50(文稿建议值)。个人级低频使用下一条影子
|
|
136
|
+
# 规则可能长期 observing —— 这正是影子的目的:证据不够就不生效。
|
|
137
|
+
if promote_min_obs is not None:
|
|
138
|
+
self.promote_min_obs = promote_min_obs
|
|
139
|
+
else:
|
|
140
|
+
_env = os.environ.get("FOXCLAW_SHADOW_PROMOTE_MIN_OBS", "").strip()
|
|
141
|
+
self.promote_min_obs = int(_env) if _env.isdigit() else 50
|
|
142
|
+
|
|
143
|
+
# 把已学习规则的 action 编译成 check() 可直接应用的结构(规则级生效)
|
|
144
|
+
self._penalized_tools: set[str] = set()
|
|
145
|
+
self._scope_limit: Optional[int] = None
|
|
146
|
+
self._reindex_learned_actions()
|
|
147
|
+
|
|
148
|
+
# 注册默认事件处理
|
|
149
|
+
self._setup_default_handlers()
|
|
150
|
+
|
|
151
|
+
# ── 目标基线(每任务,contextvar 支撑,property 透明路由)──────
|
|
152
|
+
@property
|
|
153
|
+
def _baseline(self):
|
|
154
|
+
return _harness_ctx_get(self._ctx_token)
|
|
155
|
+
|
|
156
|
+
@_baseline.setter
|
|
157
|
+
def _baseline(self, value):
|
|
158
|
+
_harness_ctx_set(self._ctx_token, value)
|
|
159
|
+
|
|
160
|
+
# ── 目标基线注册 ───────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
def register_goal(
|
|
163
|
+
self,
|
|
164
|
+
goal_text: str,
|
|
165
|
+
allowed_tool_categories: set[str],
|
|
166
|
+
baseline_entities: set[str] = None,
|
|
167
|
+
) -> GoalBaseline:
|
|
168
|
+
"""接口层调用 — 注册任务目标基线"""
|
|
169
|
+
baseline = GoalBaseline(
|
|
170
|
+
goal_text=goal_text,
|
|
171
|
+
allowed_tool_categories=allowed_tool_categories,
|
|
172
|
+
baseline_entities=baseline_entities or set(),
|
|
173
|
+
)
|
|
174
|
+
# 用 encode(纯函数)算每任务自持的基线向量,不写共享的 _baseline_vec →
|
|
175
|
+
# 并发顶层任务的语义基线互不覆盖(check 用 self._baseline.goal_embedding 打分)。
|
|
176
|
+
baseline.goal_embedding = self.sem_drift.encode(goal_text)
|
|
177
|
+
self._baseline = baseline
|
|
178
|
+
self.bus.emit("goal_registered", baseline.to_dict())
|
|
179
|
+
return baseline
|
|
180
|
+
|
|
181
|
+
# ── 核心校验方法 ───────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
def check(self, node: ThoughtNode, ignore_semantic: bool = False) -> HarnessResult:
|
|
184
|
+
"""
|
|
185
|
+
对一个 ThoughtNode 做 Harness 校验
|
|
186
|
+
返回 HarnessResult,并更新节点的 harness_status
|
|
187
|
+
|
|
188
|
+
ignore_semantic=True:把语义维度置 0(只按 tool/scope 判定)。用于"工具授权"这类
|
|
189
|
+
场景 —— 合成的工具调用串对目标句的语义距离是噪声,不该作为拦截依据。默认 False,
|
|
190
|
+
不影响任何既有调用方的行为。
|
|
191
|
+
|
|
192
|
+
【B 类修复】语义嵌入降级时(semantic_degraded=True):语义维度**停用**而非
|
|
193
|
+
继续参与判定。哈希嵌入无区分度 → 漂移分恒 1.0 → 若继续加权会把所有正常
|
|
194
|
+
请求一律 BLOCK(fail-closed-with-garbage)。降级方向应为 fail-open:
|
|
195
|
+
停用不可信维度,保留可信的 tool/scope 判定,并在 reason 显式标注降级
|
|
196
|
+
(失效但可见)。走 _check_degraded 分支,正常路径逻辑逐字不变。
|
|
197
|
+
"""
|
|
198
|
+
baseline = self._baseline # contextvar 读(每任务/线程独立)
|
|
199
|
+
if baseline is None:
|
|
200
|
+
# 没有注册目标基线,只观测不截断
|
|
201
|
+
result = HarnessResult(
|
|
202
|
+
status="PASS", delta=0.0, dominant_dim="none",
|
|
203
|
+
scores={"semantic": 0.0, "tool": 0.0, "scope": 0.0},
|
|
204
|
+
)
|
|
205
|
+
self.thought_chain.update_harness(node.id, "PASS")
|
|
206
|
+
return result
|
|
207
|
+
|
|
208
|
+
if self.semantic_degraded and not ignore_semantic:
|
|
209
|
+
return self._check_degraded(node)
|
|
210
|
+
|
|
211
|
+
# 语义嵌入(昂贵、线程安全)—— **放锁外**,使请求 offload 到线程池后,并发请求的
|
|
212
|
+
# 嵌入计算真正并行(torch/ST CPU 前向会释放 GIL)。这是吞吐提速的关键点。
|
|
213
|
+
sem_score = 0.0 if ignore_semantic else \
|
|
214
|
+
self.sem_drift.score(node.content, baseline.goal_embedding)
|
|
215
|
+
|
|
216
|
+
# 以下累积态的读改写进锁(廉价):weights/_delta_history/_run_count/阈值/_policy。
|
|
217
|
+
with self._lock:
|
|
218
|
+
scores = {
|
|
219
|
+
"semantic": sem_score,
|
|
220
|
+
"tool": self.tool_drift.score(
|
|
221
|
+
node.tool_name or "", baseline.allowed_tool_categories
|
|
222
|
+
) if node.tool_name else 0.0,
|
|
223
|
+
"scope": self.scope_drift.score(
|
|
224
|
+
set(node.entities), baseline.baseline_entities),
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
# 应用已学习规则的 action(规则级生效,区别于权重级自学习):
|
|
228
|
+
# · penalize_tool —— 之前触发过 BLOCK 的工具,即便类别本在允许集里,也判最高
|
|
229
|
+
# 工具风险(1.0),让「学到某工具危险」下次真正拦得住,而非只微调 tool 权重。
|
|
230
|
+
# · restrict_scope —— 学到的实体扩张上限;新增实体超限直接判范围越界(scope=1.0)。
|
|
231
|
+
if node.tool_name and self._is_penalized_tool(node.tool_name):
|
|
232
|
+
scores["tool"] = 1.0
|
|
233
|
+
if (self._scope_limit is not None and baseline.baseline_entities):
|
|
234
|
+
new_entities = set(node.entities) - baseline.baseline_entities
|
|
235
|
+
if len(new_entities) > self._scope_limit:
|
|
236
|
+
scores["scope"] = 1.0
|
|
237
|
+
|
|
238
|
+
delta = sum(self.weights[k] * v for k, v in scores.items())
|
|
239
|
+
dominant = max(scores, key=scores.get)
|
|
240
|
+
|
|
241
|
+
# 更新 shadow mode 历史。【深审修复·长期运行泄漏】原先**无条件**每次 check 都 append,
|
|
242
|
+
# 但 _delta_history 仅在 _calibrate_thresholds(shadow 结束时跑一次)被读;shadow_mode 只在
|
|
243
|
+
# init 置 True、永不重入,故校准后继续 append 是**纯无界泄漏**(每个推理节点一个 float)。
|
|
244
|
+
# 改为**仅 shadow 期 append**:样本上限=shadow_rounds,校准看到的样本完全一致(行为不变)。
|
|
245
|
+
if self._shadow_mode:
|
|
246
|
+
self._delta_history.append(delta)
|
|
247
|
+
self._run_count += 1
|
|
248
|
+
|
|
249
|
+
# Shadow mode 结束:用真实数据校准阈值
|
|
250
|
+
if self._shadow_mode and self._run_count >= self.shadow_rounds:
|
|
251
|
+
self._calibrate_thresholds()
|
|
252
|
+
|
|
253
|
+
# 判断状态
|
|
254
|
+
if self._shadow_mode:
|
|
255
|
+
status: HarnessStatus = "PASS" # 观测期永不截断
|
|
256
|
+
reason = None
|
|
257
|
+
elif delta > self._block_threshold:
|
|
258
|
+
# 调度层(如主管)豁免「语义漂移」拦截:它的职责就是接收任意输入,
|
|
259
|
+
# 语义距离天然大,不应因此 BLOCK。但工具越权/范围越界仍然拦截。
|
|
260
|
+
if not self.block_on_semantic and dominant == "semantic":
|
|
261
|
+
status = "WARN"
|
|
262
|
+
reason = f"semantic drift Δ={delta:.3f}(调度层仅告警不拦截)"
|
|
263
|
+
else:
|
|
264
|
+
status = "BLOCK"
|
|
265
|
+
reason = f"{dominant} drift={scores[dominant]:.3f} Δ={delta:.3f}"
|
|
266
|
+
elif delta > self._warn_threshold:
|
|
267
|
+
status = "WARN"
|
|
268
|
+
reason = f"{dominant} approaching threshold Δ={delta:.3f}"
|
|
269
|
+
else:
|
|
270
|
+
status = "PASS"
|
|
271
|
+
reason = None
|
|
272
|
+
|
|
273
|
+
result = HarnessResult(
|
|
274
|
+
status=status, delta=delta, dominant_dim=dominant,
|
|
275
|
+
scores=scores, reason=reason,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# 【P0-1】影子规则双轨观察(锁内):用**已算好的 scores** 模拟应用影子规则的
|
|
279
|
+
# action,只记录 would_block/would_pass,绝不改变上面的 real result。
|
|
280
|
+
# 不重新做语义嵌入 —— 开销是 O(观察中规则数) 的纯算术,远优于文稿预估的 2x。
|
|
281
|
+
# 顺序有意**先于**下方的 BLOCK 学习:本次 check 是对**既有**候选的一次观察样本;
|
|
282
|
+
# 本次 BLOCK 诞生的新候选从**下一次** check 才开始积累证据(诞生事件≠观察样本)。
|
|
283
|
+
if self._policy.get("shadow_rules"):
|
|
284
|
+
self._observe_shadow_rules(node, result, scores)
|
|
285
|
+
|
|
286
|
+
# BLOCK:触发 Policy 学习(mutates weights/_policy,须在锁内)
|
|
287
|
+
if status == "BLOCK":
|
|
288
|
+
self._trigger_policy_learning(node, result)
|
|
289
|
+
|
|
290
|
+
# 锁外:SQLite 写(WAL 线程安全)+ 事件广播(HarnessEventBus 独立)
|
|
291
|
+
self.thought_chain.update_harness(node.id, status, reason, delta, dominant)
|
|
292
|
+
self.bus.emit(f"harness_{status.lower()}", {
|
|
293
|
+
"agent_id": self.agent_id, "node_id": node.id, "node_type": node.type,
|
|
294
|
+
"result": result.__dict__,
|
|
295
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
296
|
+
})
|
|
297
|
+
return result
|
|
298
|
+
|
|
299
|
+
def _check_degraded(self, node: ThoughtNode) -> HarnessResult:
|
|
300
|
+
"""语义嵌入降级时的判定:停用语义维度,保留 tool/scope 确定性判定。
|
|
301
|
+
|
|
302
|
+
与 check() 的差异**仅在于**:跳过语义嵌入计算(省钱省时)且 semantic 权重
|
|
303
|
+
按 0 计入。锁内累加/阈值校准/影子学习复用同一套逻辑,保证 shadow 学习与
|
|
304
|
+
阈值校准行为与正常路径一致(不因降级而产生第二套语义)。
|
|
305
|
+
|
|
306
|
+
为何不能只调阈值:无区分度时阈值怎么调都是错的 —— 高阈值漏放跑题,
|
|
307
|
+
低阈值拦截正常请求。必须**停用该维度**。
|
|
308
|
+
为何保留 tool/scope:两者是确定性规则(工具类别匹配 / 实体集合运算),
|
|
309
|
+
不依赖模型质量,降级后仍可信。
|
|
310
|
+
"""
|
|
311
|
+
baseline = self._baseline
|
|
312
|
+
if not self._degrade_warned:
|
|
313
|
+
self._degrade_warned = True
|
|
314
|
+
# 一次性告警:降级 = 治理能力缺失,属需人工知晓的事件,不能只靠属性静默。
|
|
315
|
+
import logging as _lg
|
|
316
|
+
_lg.getLogger("foxclaw.harness").warning(
|
|
317
|
+
"[harness] 语义嵌入不可用(mode=%s),语义漂移维度已停用 —— "
|
|
318
|
+
"漂移检测暂退化为 tool/scope 两维,降级期间跑题不会被拦截。"
|
|
319
|
+
"恢复:pip install sentence-transformers",
|
|
320
|
+
self.sem_drift.mode)
|
|
321
|
+
|
|
322
|
+
with self._lock:
|
|
323
|
+
# 降级期间不污染 shadow 样本:把语义分置 0 后再 append,否则恒 1.0 的
|
|
324
|
+
# 语义分会污染阈值校准,装回 ST 后阈值仍不准。
|
|
325
|
+
saved_w = self.weights.get("semantic", 0.0)
|
|
326
|
+
self.weights["semantic"] = 0.0
|
|
327
|
+
try:
|
|
328
|
+
scores = {
|
|
329
|
+
"semantic": 0.0,
|
|
330
|
+
"tool": self.tool_drift.score(
|
|
331
|
+
node.tool_name or "", baseline.allowed_tool_categories
|
|
332
|
+
) if node.tool_name else 0.0,
|
|
333
|
+
"scope": self.scope_drift.score(
|
|
334
|
+
set(node.entities), baseline.baseline_entities),
|
|
335
|
+
}
|
|
336
|
+
# 规则级学习(action)照常生效 —— 这部分不依赖 embedding
|
|
337
|
+
if node.tool_name and self._is_penalized_tool(node.tool_name):
|
|
338
|
+
scores["tool"] = 1.0
|
|
339
|
+
if (self._scope_limit is not None and baseline.baseline_entities):
|
|
340
|
+
new_entities = set(node.entities) - baseline.baseline_entities
|
|
341
|
+
if len(new_entities) > self._scope_limit:
|
|
342
|
+
scores["scope"] = 1.0
|
|
343
|
+
|
|
344
|
+
delta = sum(self.weights[k] * v for k, v in scores.items())
|
|
345
|
+
dominant = max(scores, key=scores.get)
|
|
346
|
+
if self._shadow_mode:
|
|
347
|
+
self._delta_history.append(delta)
|
|
348
|
+
self._run_count += 1
|
|
349
|
+
if self._shadow_mode and self._run_count >= self.shadow_rounds:
|
|
350
|
+
self._calibrate_thresholds()
|
|
351
|
+
|
|
352
|
+
if self._shadow_mode:
|
|
353
|
+
status: HarnessStatus = "PASS" # 观测期永不截断
|
|
354
|
+
reason = None
|
|
355
|
+
elif delta > self._block_threshold:
|
|
356
|
+
status = "BLOCK"
|
|
357
|
+
reason = f"{dominant} drift={scores[dominant]:.3f} Δ={delta:.3f}"
|
|
358
|
+
elif delta > self._warn_threshold:
|
|
359
|
+
status = "WARN"
|
|
360
|
+
reason = f"{dominant} approaching threshold Δ={delta:.3f}"
|
|
361
|
+
else:
|
|
362
|
+
status = "PASS"
|
|
363
|
+
reason = None
|
|
364
|
+
# 降级标记:让上层/前端能识别「本次判定不含语义维度」(失效但可见)
|
|
365
|
+
reason = ((reason + " [semantic-degraded:治理降级]") if reason
|
|
366
|
+
else "[semantic-degraded:语义维度停用]")
|
|
367
|
+
finally:
|
|
368
|
+
self.weights["semantic"] = saved_w
|
|
369
|
+
|
|
370
|
+
result = HarnessResult(
|
|
371
|
+
status=status, delta=delta, dominant_dim=dominant,
|
|
372
|
+
scores=scores, reason=reason,
|
|
373
|
+
)
|
|
374
|
+
if self._policy.get("shadow_rules"):
|
|
375
|
+
self._observe_shadow_rules(node, result, scores)
|
|
376
|
+
if status == "BLOCK":
|
|
377
|
+
self._trigger_policy_learning(node, result)
|
|
378
|
+
|
|
379
|
+
# 锁外:SQLite 写 + 事件广播(同 check,额外带 degraded 标记)
|
|
380
|
+
self.thought_chain.update_harness(node.id, status, reason, delta, dominant)
|
|
381
|
+
self.bus.emit(f"harness_{status.lower()}", {
|
|
382
|
+
"agent_id": self.agent_id, "node_id": node.id, "node_type": node.type,
|
|
383
|
+
"result": result.__dict__, "degraded": True,
|
|
384
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
385
|
+
})
|
|
386
|
+
return result
|
|
387
|
+
|
|
388
|
+
# ── 默认事件处理 ───────────────────────────────────────
|
|
389
|
+
|
|
390
|
+
def _setup_default_handlers(self):
|
|
391
|
+
def on_block(data):
|
|
392
|
+
from rich.console import Console
|
|
393
|
+
Console().print(
|
|
394
|
+
f"[bold red][HARNESS BLOCK][/] agent={data['agent_id']} "
|
|
395
|
+
f"node={data['node_id']} reason={data['result']['reason']}"
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
def on_policy_updated(data):
|
|
399
|
+
from rich.console import Console
|
|
400
|
+
Console().print(
|
|
401
|
+
f"[bold yellow][POLICY UPDATE][/] agent={data['agent_id']} "
|
|
402
|
+
f"rule={data['rule_id']} weights={data['new_weights']}"
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
self.bus.on("harness_block", on_block)
|
|
406
|
+
self.bus.on("policy_updated", on_policy_updated)
|
|
407
|
+
|
|
408
|
+
# ── 状态查询 ───────────────────────────────────────────
|
|
409
|
+
|
|
410
|
+
@property
|
|
411
|
+
def is_shadow_mode(self) -> bool:
|
|
412
|
+
return self._shadow_mode
|
|
413
|
+
|
|
414
|
+
@property
|
|
415
|
+
def thresholds(self) -> dict:
|
|
416
|
+
return {
|
|
417
|
+
"warn": self._warn_threshold,
|
|
418
|
+
"block": self._block_threshold,
|
|
419
|
+
"shadow_mode": self._shadow_mode,
|
|
420
|
+
"run_count": self._run_count,
|
|
421
|
+
"shadow_rounds": self.shadow_rounds,
|
|
422
|
+
# ── 降级可见性(B 类修复)────────────────────────────────
|
|
423
|
+
# 语义嵌入是否降级。semantic_degraded=True 意味着漂移检测只剩
|
|
424
|
+
# tool/scope 两维,**跑题不会被拦截** —— 这是需要人工知晓的治理
|
|
425
|
+
# 能力缺失,不能只体现在日志里。
|
|
426
|
+
"semantic_degraded": self.semantic_degraded,
|
|
427
|
+
"embedding_mode": self.sem_drift.mode,
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
def get_policy_summary(self) -> dict:
|
|
431
|
+
rules = self._policy.get("rules", [])
|
|
432
|
+
shadow = self._policy.get("shadow_rules", [])
|
|
433
|
+
return {
|
|
434
|
+
"version": self._policy.get("version", 1),
|
|
435
|
+
"total_rules": len(rules),
|
|
436
|
+
"manual_rules": sum(1 for r in rules if r.get("source") == "manual"),
|
|
437
|
+
"learned_rules": sum(1 for r in rules if r.get("source") == "harness_learned"),
|
|
438
|
+
# 【P0-1】影子学习账本:observing=观察中(不生效);discarded/rolled_back 保留供审计
|
|
439
|
+
"shadow_observing": sum(1 for r in shadow
|
|
440
|
+
if (r.get("shadow") or {}).get("status") == "observing"),
|
|
441
|
+
"shadow_total": len(shadow),
|
|
442
|
+
"history_events": len(self._policy.get("history", [])),
|
|
443
|
+
# 返回**副本**:此前直接返回 self.weights 引用 —— 调用方拿到的不是快照,
|
|
444
|
+
# 既可能被后续权重更新"隔空改写"(读到的值取决于时机),也能反向改坏内部状态。
|
|
445
|
+
"weights": dict(self.weights),
|
|
446
|
+
}
|