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,196 @@
|
|
|
1
|
+
"""
|
|
2
|
+
本地大并发压测(真实 uvicorn 服务)
|
|
3
|
+
|
|
4
|
+
起一个真实的 uvicorn(Foxclaw,stub LLM / 离线),用 httpx 异步客户端打高并发
|
|
5
|
+
`POST /api/task`,量:
|
|
6
|
+
· 吞吐(req/s)、延迟分位(p50/p90/p95/p99/max)、错误率;
|
|
7
|
+
· **负载下正确性**:每个请求拿到独立 session_id(无串味)、无 database is locked、无 5xx。
|
|
8
|
+
|
|
9
|
+
这把 M2 加锁 + 顶层 contextvars 的并发修复放到**真实 HTTP 服务**下验证。
|
|
10
|
+
纯本地、离线、零基建。用法:HF_HUB_OFFLINE=1 python bench/load_test.py [总请求数] [并发数]
|
|
11
|
+
"""
|
|
12
|
+
import asyncio
|
|
13
|
+
import os
|
|
14
|
+
import statistics
|
|
15
|
+
import sys
|
|
16
|
+
import tempfile
|
|
17
|
+
import threading
|
|
18
|
+
import time
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
22
|
+
|
|
23
|
+
PORT = int(os.environ.get("FOXCLAW_BENCH_PORT", "8231"))
|
|
24
|
+
BASE = f"http://127.0.0.1:{PORT}"
|
|
25
|
+
|
|
26
|
+
# 一批覆盖不同分诊路径的任务模板(每个请求附唯一编号,便于校验会话唯一性)
|
|
27
|
+
TEMPLATES = [
|
|
28
|
+
"分析本月理财预算与收支", # finance
|
|
29
|
+
"总结这篇论文的核心要点", # office
|
|
30
|
+
"安排明天上午的会议日程", # admin
|
|
31
|
+
"整理并汇总这批资料", # assistant
|
|
32
|
+
"你能做什么", # supervisor 自答(meta)
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _pct(xs, p):
|
|
37
|
+
if not xs:
|
|
38
|
+
return 0.0
|
|
39
|
+
xs = sorted(xs)
|
|
40
|
+
k = max(0, min(len(xs) - 1, int(round((p / 100) * (len(xs) - 1)))))
|
|
41
|
+
return xs[k]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _start_server():
|
|
45
|
+
"""在后台线程起真实 uvicorn。返回 (server, thread)。"""
|
|
46
|
+
import uvicorn
|
|
47
|
+
os.environ.setdefault("FOXCLAW_DATA_DIR", tempfile.mkdtemp())
|
|
48
|
+
os.environ["FOXCLAW_SHADOW_ROUNDS"] = "50"
|
|
49
|
+
# 不配任何 LLM key → Router 自动降级 stub(不联网、瞬时返回)
|
|
50
|
+
for k in ("ANTHROPIC_API_KEY", "OPENAI_API_KEY", "DEEPSEEK_API_KEY",
|
|
51
|
+
"GOOGLE_API_KEY", "DASHSCOPE_API_KEY"):
|
|
52
|
+
os.environ.pop(k, None)
|
|
53
|
+
config = uvicorn.Config("foxclaw.api:app", host="127.0.0.1", port=PORT,
|
|
54
|
+
log_level="warning", access_log=False)
|
|
55
|
+
server = uvicorn.Server(config)
|
|
56
|
+
th = threading.Thread(target=server.run, daemon=True)
|
|
57
|
+
th.start()
|
|
58
|
+
return server, th
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async def _wait_ready(timeout=120):
|
|
62
|
+
import httpx
|
|
63
|
+
t0 = time.time()
|
|
64
|
+
async with httpx.AsyncClient() as c:
|
|
65
|
+
while time.time() - t0 < timeout:
|
|
66
|
+
try:
|
|
67
|
+
r = await c.get(f"{BASE}/api/status", timeout=2)
|
|
68
|
+
if r.status_code == 200:
|
|
69
|
+
return True
|
|
70
|
+
except Exception:
|
|
71
|
+
pass
|
|
72
|
+
await asyncio.sleep(0.3)
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
async def _run_load(total: int, concurrency: int):
|
|
77
|
+
import httpx
|
|
78
|
+
sem = asyncio.Semaphore(concurrency)
|
|
79
|
+
latencies, statuses, sessions, errors = [], [], [], []
|
|
80
|
+
lock = asyncio.Lock()
|
|
81
|
+
|
|
82
|
+
async def one(i: int):
|
|
83
|
+
task = f"{TEMPLATES[i % len(TEMPLATES)]}(REQ{i:05d})"
|
|
84
|
+
async with sem:
|
|
85
|
+
t0 = time.perf_counter()
|
|
86
|
+
try:
|
|
87
|
+
async with httpx.AsyncClient(timeout=60) as c:
|
|
88
|
+
r = await c.post(f"{BASE}/api/task",
|
|
89
|
+
json={"task": task, "agent_id": "supervisor"})
|
|
90
|
+
dt = (time.perf_counter() - t0) * 1000
|
|
91
|
+
async with lock:
|
|
92
|
+
latencies.append(dt)
|
|
93
|
+
statuses.append(r.status_code)
|
|
94
|
+
if r.status_code == 200:
|
|
95
|
+
body = r.json()
|
|
96
|
+
sid = body.get("session_id")
|
|
97
|
+
if sid:
|
|
98
|
+
sessions.append(sid)
|
|
99
|
+
if isinstance(body, dict) and body.get("error") == "主管处理失败":
|
|
100
|
+
errors.append(body)
|
|
101
|
+
except Exception as e:
|
|
102
|
+
async with lock:
|
|
103
|
+
errors.append(repr(e))
|
|
104
|
+
|
|
105
|
+
t0 = time.perf_counter()
|
|
106
|
+
await asyncio.gather(*[one(i) for i in range(total)])
|
|
107
|
+
wall = time.perf_counter() - t0
|
|
108
|
+
return {
|
|
109
|
+
"wall_s": wall, "throughput": total / wall if wall else 0,
|
|
110
|
+
"latencies": latencies, "statuses": statuses,
|
|
111
|
+
"sessions": sessions, "errors": errors,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
async def _server_log_has_locked():
|
|
116
|
+
"""负载后查一次 /api/status 确认服务仍活;database is locked 会体现在 errors/5xx。"""
|
|
117
|
+
import httpx
|
|
118
|
+
try:
|
|
119
|
+
async with httpx.AsyncClient() as c:
|
|
120
|
+
r = await c.get(f"{BASE}/api/status", timeout=5)
|
|
121
|
+
return r.status_code == 200
|
|
122
|
+
except Exception:
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
async def main_async(total, concurrency):
|
|
127
|
+
print("=" * 62)
|
|
128
|
+
print(f"Foxclaw · 本地大并发压测 (总请求={total} 并发={concurrency})")
|
|
129
|
+
print("=" * 62)
|
|
130
|
+
ok = await _wait_ready()
|
|
131
|
+
if not ok:
|
|
132
|
+
print("✗ 服务未就绪"); return 1
|
|
133
|
+
print("服务就绪。先测单请求基线(concurrency=1)...")
|
|
134
|
+
|
|
135
|
+
# 基线:串行 10 个请求,量"单请求真实成本"(无并发争用),用于解释并发下的延迟来源
|
|
136
|
+
base = await _run_load(10, 1)
|
|
137
|
+
base_lat = base["latencies"]
|
|
138
|
+
base_p50 = _pct(base_lat, 50)
|
|
139
|
+
base_tps = base["throughput"]
|
|
140
|
+
print(f" 单请求 p50={base_p50:.0f}ms 串行吞吐={base_tps:.1f} req/s\n")
|
|
141
|
+
|
|
142
|
+
print(f"开始并发加载(concurrency={concurrency})...\n")
|
|
143
|
+
res = await _run_load(total, concurrency)
|
|
144
|
+
lat, st = res["latencies"], res["statuses"]
|
|
145
|
+
n2xx = sum(1 for s in st if 200 <= s < 300)
|
|
146
|
+
n5xx = sum(1 for s in st if s >= 500)
|
|
147
|
+
healthy = await _server_log_has_locked()
|
|
148
|
+
|
|
149
|
+
print("【吞吐 / 延迟】")
|
|
150
|
+
print(f" 墙钟 : {res['wall_s']:.2f} s")
|
|
151
|
+
print(f" 吞吐 : {res['throughput']:.1f} req/s")
|
|
152
|
+
print(f" 延迟 p50/p90 : {_pct(lat,50):.0f} / {_pct(lat,90):.0f} ms")
|
|
153
|
+
print(f" 延迟 p95/p99 : {_pct(lat,95):.0f} / {_pct(lat,99):.0f} ms")
|
|
154
|
+
print(f" 延迟 max : {max(lat):.0f} ms" if lat else " 无延迟样本")
|
|
155
|
+
ratio = _pct(lat, 50) / base_p50 if base_p50 else 0
|
|
156
|
+
speedup = base_tps and res["throughput"] / base_tps
|
|
157
|
+
print("\n【规模化分析】")
|
|
158
|
+
print(f" 单请求成本(串行 p50) : {base_p50:.0f} ms")
|
|
159
|
+
print(f" 并发吞吐 / 串行吞吐 : {speedup:.2f}× "
|
|
160
|
+
f"(>1 = 请求已部分并行;offload 到线程后嵌入计算并行)")
|
|
161
|
+
print(f" 并发下 p50 / 串行 p50 : {ratio:.1f}× "
|
|
162
|
+
f"(< 并发数 {concurrency} 越多 → 并行度越高;=并发数则完全串行)")
|
|
163
|
+
print(f" 现状: /api/task 已 offload 到线程,harness 语义嵌入在锁外并行(torch 释放 GIL);")
|
|
164
|
+
print(f" 非线性加速的余量卡在 GIL(Python 侧串行)+ 本机核数。")
|
|
165
|
+
print(f" 进一步提速: ① uvicorn --workers N 多进程(需先做状态外置) ② 更轻的 embedding。")
|
|
166
|
+
print("\n【正确性 · 负载下】")
|
|
167
|
+
print(f" 2xx / 总数 : {n2xx}/{total} 5xx: {n5xx} 异常: {len(res['errors'])}")
|
|
168
|
+
uniq = len(set(res["sessions"]))
|
|
169
|
+
print(f" session 唯一 : {uniq}/{len(res['sessions'])} "
|
|
170
|
+
f"→ {'✓ 无串味(顶层并发隔离成立)' if uniq == len(res['sessions']) and uniq > 0 else '✗ 有会话碰撞'}")
|
|
171
|
+
print(f" 服务负载后仍健康: {'✓' if healthy else '✗'}")
|
|
172
|
+
if res["errors"]:
|
|
173
|
+
print(f" 异常样例: {res['errors'][:2]}")
|
|
174
|
+
|
|
175
|
+
passed = (n5xx == 0 and not res["errors"] and healthy
|
|
176
|
+
and uniq == len(res["sessions"]) and uniq > 0)
|
|
177
|
+
print("\n" + "=" * 62)
|
|
178
|
+
print("结论:", "✓ 高并发下吞吐可测、零错误、审计无串味" if passed else "✗ 存在问题(见上)")
|
|
179
|
+
print("=" * 62)
|
|
180
|
+
return 0 if passed else 1
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def main():
|
|
184
|
+
total = int(sys.argv[1]) if len(sys.argv) > 1 else 200
|
|
185
|
+
concurrency = int(sys.argv[2]) if len(sys.argv) > 2 else 32
|
|
186
|
+
server, th = _start_server()
|
|
187
|
+
try:
|
|
188
|
+
rc = asyncio.run(main_async(total, concurrency))
|
|
189
|
+
finally:
|
|
190
|
+
server.should_exit = True
|
|
191
|
+
th.join(timeout=10)
|
|
192
|
+
sys.exit(rc)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
if __name__ == "__main__":
|
|
196
|
+
main()
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""vector 索引升级 + 性能对比:Flat vs IVF(1500 mock KU, stub backend)
|
|
2
|
+
|
|
3
|
+
目的:验证 HKC_VECTOR_IVF_THRESHOLD 触发的 Flat → IVF 自动升级,
|
|
4
|
+
并对比两种索引在 500 / 1500 mock KU 下的检索延迟。
|
|
5
|
+
|
|
6
|
+
用法:
|
|
7
|
+
python bench/vector_ivf_upgrade.py
|
|
8
|
+
|
|
9
|
+
可调参数:
|
|
10
|
+
HKC_VECTOR_IVF_THRESHOLD 默认 500(本脚本覆盖)
|
|
11
|
+
IVF_NLIST 自定义 nlist(可选)
|
|
12
|
+
TOTAL_KUS 总 mock KU 数,默认 1500
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import statistics
|
|
18
|
+
import sys
|
|
19
|
+
import tempfile
|
|
20
|
+
import time
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from types import SimpleNamespace
|
|
23
|
+
|
|
24
|
+
# 必须在 import VectorIndex 之前设,否则模块级常量已固化
|
|
25
|
+
os.environ["HKC_VECTOR_IVF_THRESHOLD"] = "500"
|
|
26
|
+
|
|
27
|
+
# 让 vendor.hkc.* 可导入(脚本放在 bench/,不在 vendor 树内)
|
|
28
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
29
|
+
sys.path.insert(0, str(ROOT))
|
|
30
|
+
|
|
31
|
+
from vendor.hkc.hkc_search.vector_search import VectorIndex
|
|
32
|
+
from vendor.hkc.hkc_search.embedding_backends import StubBackend
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def make_ku(i: int) -> SimpleNamespace:
|
|
36
|
+
"""构造 mock KU,字段匹配 vector_search._ku_text() 期望的 4 项: name/summary/tags/statement。"""
|
|
37
|
+
return SimpleNamespace(
|
|
38
|
+
ku_id = f"ku_{i:06d}",
|
|
39
|
+
name = f"entity_{i}",
|
|
40
|
+
summary = f"summary of entity {i} about topic {i % 50}",
|
|
41
|
+
tags = [f"tag_{i % 10}", f"category_{i % 5}"],
|
|
42
|
+
statement = f"fact {i}: something interesting about subject {i}",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def bench_search(idx, query: str, n: int = 100, top_k: int = 10) -> tuple[float, float, float]:
|
|
47
|
+
"""跑 n 次 search,返 (median_ms, min_ms, max_ms)。"""
|
|
48
|
+
times = []
|
|
49
|
+
for _ in range(n):
|
|
50
|
+
t0 = time.perf_counter()
|
|
51
|
+
idx.search(query, top_k=top_k)
|
|
52
|
+
times.append((time.perf_counter() - t0) * 1000)
|
|
53
|
+
return statistics.median(times), min(times), max(times)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def main() -> None:
|
|
57
|
+
backend = StubBackend(dim=384)
|
|
58
|
+
threshold = int(os.environ["HKC_VECTOR_IVF_THRESHOLD"])
|
|
59
|
+
|
|
60
|
+
print("=" * 70)
|
|
61
|
+
print(f"backend : {backend.name}")
|
|
62
|
+
print(f"threshold : HKC_VECTOR_IVF_THRESHOLD = {threshold}")
|
|
63
|
+
print(f"upgrade to : IndexIVFFlat(nlist≈√N, nprobe=16)")
|
|
64
|
+
print("=" * 70)
|
|
65
|
+
|
|
66
|
+
N = 5000
|
|
67
|
+
print(f"\n构造 {N} 条 mock KU...")
|
|
68
|
+
kus = [make_ku(i) for i in range(N)]
|
|
69
|
+
|
|
70
|
+
idx = VectorIndex(backend=backend)
|
|
71
|
+
print(f"index type 初始: {type(idx._index).__name__}")
|
|
72
|
+
|
|
73
|
+
# ── Phase 1: append < threshold,保持 Flat ──────────────────
|
|
74
|
+
phase1_n = threshold - 1
|
|
75
|
+
print(f"\n[Phase 1] append {phase1_n} 条(< {threshold},应保持 Flat)...")
|
|
76
|
+
for ku in kus[:phase1_n]:
|
|
77
|
+
idx.append_ku(ku)
|
|
78
|
+
print(f" index type: {type(idx._index).__name__}, ntotal={idx._index.ntotal}")
|
|
79
|
+
med, mn, mx = bench_search(idx, "find something about topic 7")
|
|
80
|
+
print(f" search ×100: median={med:.2f}ms min={mn:.2f}ms max={mx:.2f}ms")
|
|
81
|
+
|
|
82
|
+
# ── Phase 2: append 触发升级 Flat → IVF ──────────────────
|
|
83
|
+
print(f"\n[Phase 2] append 第 {threshold} 条(应触发升级)...")
|
|
84
|
+
t0 = time.perf_counter()
|
|
85
|
+
idx.append_ku(kus[phase1_n])
|
|
86
|
+
upgrade_ms = (time.perf_counter() - t0) * 1000
|
|
87
|
+
print(f" append(含升级)耗时: {upgrade_ms:.1f}ms")
|
|
88
|
+
print(f" index type: {type(idx._index).__name__}, ntotal={idx._index.ntotal}")
|
|
89
|
+
if hasattr(idx._index, "nlist"):
|
|
90
|
+
print(f" IVF nlist={idx._index.nlist}, nprobe={idx._index.nprobe}")
|
|
91
|
+
med, mn, mx = bench_search(idx, "find something about topic 7")
|
|
92
|
+
print(f" search ×100: median={med:.2f}ms min={mn:.2f}ms max={mx:.2f}ms")
|
|
93
|
+
|
|
94
|
+
# ── Phase 3: append 1000 条,验证 IVF 追加正常 ──────────────────
|
|
95
|
+
rest = N - threshold
|
|
96
|
+
print(f"\n[Phase 3] append {rest} 条(应直接 add 到 IVF)...")
|
|
97
|
+
t0 = time.perf_counter()
|
|
98
|
+
for ku in kus[threshold:]:
|
|
99
|
+
idx.append_ku(ku)
|
|
100
|
+
bulk_ms = (time.perf_counter() - t0) * 1000
|
|
101
|
+
print(f" {rest} 条 append 耗时: {bulk_ms:.1f}ms(平均 {bulk_ms/rest:.2f}ms/条)")
|
|
102
|
+
print(f" index type: {type(idx._index).__name__}, ntotal={idx._index.ntotal}")
|
|
103
|
+
med, mn, mx = bench_search(idx, "find something about topic 7")
|
|
104
|
+
print(f" search ×100: median={med:.2f}ms min={mn:.2f}ms max={mx:.2f}ms")
|
|
105
|
+
|
|
106
|
+
# ── Phase 4: save/load roundtrip(IVF 必须正确持久化) ──────────────────
|
|
107
|
+
print(f"\n[Phase 4] save/load roundtrip(IVF 持久化)...")
|
|
108
|
+
with tempfile.TemporaryDirectory() as td:
|
|
109
|
+
p = str(Path(td) / "vec")
|
|
110
|
+
t0 = time.perf_counter()
|
|
111
|
+
idx.save(p)
|
|
112
|
+
save_ms = (time.perf_counter() - t0) * 1000
|
|
113
|
+
idx2 = VectorIndex(backend=backend)
|
|
114
|
+
t0 = time.perf_counter()
|
|
115
|
+
ok = idx2.load(p)
|
|
116
|
+
load_ms = (time.perf_counter() - t0) * 1000
|
|
117
|
+
n_loaded = idx2._index.ntotal if idx2._index else 0
|
|
118
|
+
type_loaded = type(idx2._index).__name__ if idx2._index else "None"
|
|
119
|
+
print(f" save: {save_ms:.1f}ms load: {load_ms:.1f}ms ok={ok}")
|
|
120
|
+
print(f" loaded ntotal={n_loaded}, type={type_loaded}")
|
|
121
|
+
if n_loaded != N:
|
|
122
|
+
print(f" [FAIL] 数据丢失! 期望 {N} 实际 {n_loaded}")
|
|
123
|
+
elif "FlatIP" in type_loaded: # 严格判:IndexFlatIP 才算 Flat(IndexIVFFlat 含 "Flat" 字符串,要排除)
|
|
124
|
+
print(f" [FAIL] IVF 没保存,降回 FlatIP")
|
|
125
|
+
else:
|
|
126
|
+
print(f" [OK] IVF 完整保存/加载(ntotal={n_loaded}, type={type_loaded})")
|
|
127
|
+
|
|
128
|
+
print("\n" + "=" * 70)
|
|
129
|
+
print("[OK] benchmark 完成")
|
|
130
|
+
print("=" * 70)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
main()
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Foxclaw 消息平台接入说明
|
|
2
|
+
|
|
3
|
+
把 Foxclaw 接到消息平台,让用户在微信 / 飞书 / Telegram / Discord 里直接和你的认知系统对话。
|
|
4
|
+
|
|
5
|
+
## 架构总览
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
用户在某渠道发消息
|
|
9
|
+
→ 渠道适配器 normalize 成标准消息(InboundMessage)
|
|
10
|
+
→ ChannelDispatcher 交给主管 Agent 处理
|
|
11
|
+
→ 主管 run_task 思考、调度子 Agent
|
|
12
|
+
→ 回复通过同一渠道发回
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
三个角色各司其职:**适配器**只管收发(翻译平台格式 ↔ 标准格式),**主管**只管思考,**调度器(ChannelDispatcher)**把两者接起来。
|
|
16
|
+
|
|
17
|
+
所有适配器都遵循同一套接口(`normalize` / `send` / `reply`),但**内部各按平台官方规范实现**——微信是长轮询、飞书是事件订阅 webhook、Telegram 是 getUpdates、Discord 是 Gateway WebSocket,互不相同。
|
|
18
|
+
|
|
19
|
+
## 通用启动方式
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from foxclaw import Foxclaw
|
|
23
|
+
from foxclaw.channels.dispatcher import ChannelDispatcher
|
|
24
|
+
|
|
25
|
+
os_ = Foxclaw(data_dir="./data")
|
|
26
|
+
dispatcher = ChannelDispatcher(os_.supervisor)
|
|
27
|
+
|
|
28
|
+
# 注册渠道(下面每个平台示例都用这个 dispatcher)
|
|
29
|
+
dispatcher.register_channel(adapter) # adapter 见下文各平台
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`register_channel` 会自动把适配器的入站消息接到主管闭环——之后该渠道收到的每条消息都会经主管处理并自动回复。
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Telegram(最易跑通,建议先用它验证)
|
|
37
|
+
|
|
38
|
+
**拿 token:** Telegram 里找 `@BotFather` → 发 `/newbot` → 按提示拿到 bot token。
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from foxclaw.channels.telegram import TelegramAdapter
|
|
42
|
+
|
|
43
|
+
tg = TelegramAdapter(bot_token="123456:ABC-DEF...")
|
|
44
|
+
dispatcher.register_channel(tg)
|
|
45
|
+
|
|
46
|
+
# 验证 token
|
|
47
|
+
print(tg.verify_token().ok)
|
|
48
|
+
|
|
49
|
+
# 持续收消息(轮询):放进循环
|
|
50
|
+
import time
|
|
51
|
+
while True:
|
|
52
|
+
tg.poll_once() # 拉一批新消息,自动经主管处理并回复
|
|
53
|
+
time.sleep(1)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
收发模型:**getUpdates 长轮询**,无需公网回调地址,本地直接能跑。
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 微信(iLink Bot API)
|
|
61
|
+
|
|
62
|
+
照 Tencent 官方开源插件 [`Tencent/openclaw-weixin`](https://github.com/Tencent/openclaw-weixin) 公开的 iLink Bot API 协议实现。
|
|
63
|
+
|
|
64
|
+
**拿 token:** token 需用微信官方那套**扫码登录**获得(扫码登录在微信侧,Foxclaw 不复现登录,只复现登录后的消息收发)。可用 OpenClaw 官方 CLI 扫码:
|
|
65
|
+
```bash
|
|
66
|
+
openclaw channels login --channel openclaw-weixin # 扫码后本地拿到 token
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from foxclaw.channels.weixin import WeixinAdapter
|
|
71
|
+
|
|
72
|
+
wx = WeixinAdapter(token="<扫码登录得到的 token>")
|
|
73
|
+
dispatcher.register_channel(wx)
|
|
74
|
+
|
|
75
|
+
# 持续收消息(长轮询)
|
|
76
|
+
while True:
|
|
77
|
+
wx.poll_once() # getUpdates 长轮询,自动处理并回复
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
收发模型:**getUpdates 长轮询**。回复时会自动带回 `context_token`(微信要求),已内部处理。`verify_token(ilink_user_id)` 需提供真实用户 ID 才能探测。
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 飞书 / Lark(事件订阅 webhook)
|
|
85
|
+
|
|
86
|
+
**拿凭证:** 飞书开放平台 → 创建企业自建应用 → 拿 `App ID` + `App Secret`;在「事件订阅」里配置回调地址,记下 `Verification Token` 和 `Encrypt Key`(若开启加密)。事件订阅勾选 `im.message.receive_v1`。
|
|
87
|
+
|
|
88
|
+
飞书是**平台主动推事件到你的回调地址**,所以需要一个公网可达的 webhook 端点(FastAPI 路由)。
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from foxclaw.channels.feishu import FeishuAdapter
|
|
92
|
+
|
|
93
|
+
fs = FeishuAdapter(
|
|
94
|
+
app_id="cli_xxx",
|
|
95
|
+
app_secret="xxx",
|
|
96
|
+
verification_token="xxx", # webhook 模式需要
|
|
97
|
+
encrypt_key="xxx", # 若开启加密
|
|
98
|
+
)
|
|
99
|
+
dispatcher.register_channel(fs)
|
|
100
|
+
|
|
101
|
+
# FastAPI 路由接飞书事件回调
|
|
102
|
+
from fastapi import FastAPI, Request
|
|
103
|
+
app = FastAPI()
|
|
104
|
+
|
|
105
|
+
@app.post("/feishu/events")
|
|
106
|
+
async def feishu_events(request: Request):
|
|
107
|
+
raw_body = await request.body()
|
|
108
|
+
payload = await request.json()
|
|
109
|
+
# handle_event 内部完成:URL验证 / 解密 / 验签 / 标准化 / 主管处理
|
|
110
|
+
return fs.handle_event(payload, headers=dict(request.headers), raw_body=raw_body)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
收发模型:**事件订阅 webhook**。`handle_event` 自动处理 URL 验证(challenge)、AES 解密、签名验签。发消息用 `tenant_access_token`(自动缓存 + 过期前刷新)。
|
|
114
|
+
|
|
115
|
+
> 注意:飞书加密事件解密需要 `cryptography` 库(或 pycryptodome)。环境一般已自带 cryptography。
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Discord(Gateway WebSocket)
|
|
120
|
+
|
|
121
|
+
**拿 token:** Discord Developer Portal → 创建 Application → Bot → 拿 bot token。**务必在 Bot 设置里开启 `MESSAGE CONTENT INTENT`**(否则收不到消息文本)。把 Bot 邀请进你的服务器。
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from foxclaw.channels.discord import DiscordAdapter
|
|
125
|
+
|
|
126
|
+
dc = DiscordAdapter(bot_token="...")
|
|
127
|
+
dispatcher.register_channel(dc)
|
|
128
|
+
|
|
129
|
+
# 验证 token(同时记录 bot 自身 ID,用于忽略自己的消息)
|
|
130
|
+
print(dc.verify_token().ok)
|
|
131
|
+
|
|
132
|
+
# 持续收消息(Gateway WebSocket,异步)
|
|
133
|
+
import asyncio
|
|
134
|
+
asyncio.run(dc.connect_gateway()) # 需要 websockets 库
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
收发模型:**Gateway WebSocket 长连接**(收) + **REST API**(发)。`connect_gateway` 实现了 HELLO→IDENTIFY→心跳→消息分发的最小闭环。生产环境也可换成成熟的 discord.py,再把消息喂给 `dc.normalize()`。
|
|
138
|
+
|
|
139
|
+
> 需要 `websockets` 库:`pip install websockets`
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 备选:对接 OpenClaw 网关
|
|
144
|
+
|
|
145
|
+
若你已在用 OpenClaw(它接了某些渠道),可让 Foxclaw 通过 OpenClaw 网关收发,而不必自己实现那些渠道。
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from foxclaw.channels.openclaw_bridge import OpenClawBridgeAdapter
|
|
149
|
+
|
|
150
|
+
bridge = OpenClawBridgeAdapter(gateway_url="http://127.0.0.1:8080", api_token="...")
|
|
151
|
+
dispatcher.register_channel(bridge)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
OpenClaw 负责渠道接入,Foxclaw 负责认知处理,两者通过 HTTP 桥接。
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 多渠道同时运行
|
|
159
|
+
|
|
160
|
+
一个 dispatcher 可注册多个渠道,各自独立收发,共用同一个主管:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
dispatcher.register_channel(tg) # Telegram(轮询)
|
|
164
|
+
dispatcher.register_channel(wx) # 微信(轮询)
|
|
165
|
+
dispatcher.register_channel(dc) # Discord(WebSocket)
|
|
166
|
+
# 飞书走 FastAPI webhook 路由
|
|
167
|
+
|
|
168
|
+
# 轮询类渠道各起一个循环/线程;WebSocket/webhook 类按各自方式跑
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**会话隔离:** 默认按 `渠道:会话:发送者` 隔离(不同渠道的对话互不串)。若想做"跨渠道同一记忆"(同一用户在飞书和微信共享上下文),可改 `dispatcher._session_key` 只用 `sender_id`。
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 依赖小结
|
|
176
|
+
|
|
177
|
+
| 渠道 | 必需依赖 | 凭证 |
|
|
178
|
+
|---|---|---|
|
|
179
|
+
| Telegram | httpx | bot token(@BotFather) |
|
|
180
|
+
| 微信 | httpx | 扫码登录 token |
|
|
181
|
+
| 飞书 | httpx, cryptography | App ID/Secret + (加密时)Encrypt Key |
|
|
182
|
+
| Discord | httpx, websockets | bot token + Message Content Intent |
|
|
183
|
+
| OpenClaw 桥 | httpx | OpenClaw 网关地址 + token |
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
pip install httpx cryptography websockets
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 安全提示
|
|
192
|
+
|
|
193
|
+
- **凭证不要硬编码**:token / app_secret 用环境变量或配置文件注入,别写进代码或提交到 Git。
|
|
194
|
+
- 飞书 webhook 建议保留**签名验签**(开启 Encrypt Key),适配器会自动验。
|
|
195
|
+
- 各渠道的访问控制(谁能跟 bot 对话)目前在主管侧统一处理;如需更细的白名单,可在 `dispatcher.handle` 里按 `msg.sender_id` 加判断。
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Foxclaw 运行时治理能力说明
|
|
2
|
+
|
|
3
|
+
这组能力借鉴 OpenClaw 等成熟 Agent 系统的设计原理,用 Python 适配 Foxclaw 实现,目标是让系统在真实运行(消息重发、进程崩溃、长任务、死循环)中保持**可靠、可审计、不丢不重**。
|
|
4
|
+
|
|
5
|
+
六项能力分两类:**已接入主路径、现在就生效**的(幂等、重启恢复、循环检测),和**基础设施、按需启用**的(上下文压缩、钩子标准化、干预队列)。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 一、幂等守卫(Idempotency)— 已生效
|
|
10
|
+
|
|
11
|
+
**解决:** 消息平台天生会重发(网络抖动、平台重试、轮询游标边界)。没有幂等,用户会收到重复回复、同一操作被记两次。
|
|
12
|
+
|
|
13
|
+
**原理:** 给"只应发生一次"的操作算一个稳定 key,执行前**原子查重**——处理过就跳过,没处理才执行并记录。
|
|
14
|
+
|
|
15
|
+
**核心接口**(`foxclaw/core/idempotency.py`):
|
|
16
|
+
```python
|
|
17
|
+
from foxclaw.core.idempotency import IdempotencyGuard, make_key
|
|
18
|
+
|
|
19
|
+
guard = IdempotencyGuard(ttl_seconds=3600) # 可选 persist_path 跨重启
|
|
20
|
+
key = make_key("message", channel, message_id, sender_id)
|
|
21
|
+
if guard.check_and_mark(key): # 原子:首次返回 True(执行),重复返回 False(跳过)
|
|
22
|
+
do_the_work()
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**已接入:** `ChannelDispatcher.handle` 自动按 `channel+message_id+sender` 去重;无 message_id 的消息退化为"渠道+发送者+内容+10秒时间窗"兜底。
|
|
26
|
+
|
|
27
|
+
**设计要点:**
|
|
28
|
+
- `check_and_mark` 在同一把锁里完成查重+标记(防并发重发都去执行,50线程压测验证恰好1个成功)
|
|
29
|
+
- TTL 过期清理 + 容量上限 LRU 淘汰(防无限膨胀)
|
|
30
|
+
- 可选落盘(原子替换写),跨进程重启仍识别"处理过了"
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 二、重启恢复(Restart Recovery)— 已生效
|
|
35
|
+
|
|
36
|
+
**解决:** 长任务(多 Agent 链、财务分析)跑到一半进程崩了/重启,任务凭空消失,记忆可能留下半完成的脏数据。
|
|
37
|
+
|
|
38
|
+
**原理:** 启动时主动识别"上次还在进行、但持有它的进程已不在"的任务,**诚实标记为中断**,而不是装无事。契合 Foxclaw"可审计、可信"的定位。
|
|
39
|
+
|
|
40
|
+
**核心接口**(`foxclaw/core/recovery.py`):
|
|
41
|
+
```python
|
|
42
|
+
from foxclaw.core.recovery import RestartRecovery
|
|
43
|
+
|
|
44
|
+
rec = RestartRecovery(log_path="data/task_log.jsonl")
|
|
45
|
+
rec.task_started(task_id, description) # 任务开始(落 in_progress)
|
|
46
|
+
rec.task_finished(task_id, status="done") # 结束(done/failed)
|
|
47
|
+
interrupted = rec.scan_interrupted() # 启动时扫描:返回被中断的任务列表
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**已接入:** `Foxclaw` 启动时自动扫描,检测到中断任务会打印提示;主管任务经 base 模板的
|
|
51
|
+
生命周期钩子(`_on_session_started`/`_on_task_settled`,P1-1)自动记录开始/结束
|
|
52
|
+
(正常终态标 done、未兜底异常标 failed;`run_task` 与 `run_task_stream` 两条路径都记账)。
|
|
53
|
+
|
|
54
|
+
**设计要点:**
|
|
55
|
+
- **boot_id 防 pid 复用**:每次启动生成唯一 ID,判据是"非本次启动的 in_progress 任务=被中断"。比单纯靠 pid 可靠——pid 会被 OS 复用,旧任务的 pid 被新进程占用会导致漏判。
|
|
56
|
+
- JSONL 追加写 + fsync(崩溃安全:单行 append 原子,损坏行自动跳过)
|
|
57
|
+
- `compact_log` 防日志无限膨胀
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 三、工具循环检测(Loop Detection)— 已生效
|
|
62
|
+
|
|
63
|
+
**解决:** 断路器管"失败",但管不了"无进展死循环"——Agent 不报错却陷入重复/横跳/轮询卡死,白烧 token。
|
|
64
|
+
|
|
65
|
+
**原理:** 监控工具调用历史,检测三种循环模式,多级阈值预警→熔断。
|
|
66
|
+
|
|
67
|
+
**核心接口**(`foxclaw/harness/loop_detection.py`):
|
|
68
|
+
```python
|
|
69
|
+
from foxclaw.harness.loop_detection import ToolLoopDetector
|
|
70
|
+
|
|
71
|
+
det = ToolLoopDetector(warning=6, critical=12, circuit_break=20)
|
|
72
|
+
verdict = det.record(tool_name, args, result) # 每次工具调用后
|
|
73
|
+
if verdict.stuck:
|
|
74
|
+
print(verdict.detector, verdict.message) # repeat/ping_pong/no_progress
|
|
75
|
+
if det.should_abort():
|
|
76
|
+
abort() # 达熔断阈值
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**已接入:** `SkillAdapter` 每次工具调用后自动检测,卡死时在结果上附 `loop_warning` 并触发 `on_loop` 钩子。
|
|
80
|
+
|
|
81
|
+
**三种模式:** `repeat`(同调用连续重复)、`ping_pong`(两调用交替)、`no_progress`(轮询结果无变化,优先级最高,语义最精确)。
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 四、上下文压缩(Compaction)— 基础设施
|
|
86
|
+
|
|
87
|
+
**解决:** 长对话撑爆上下文窗口。
|
|
88
|
+
|
|
89
|
+
**原理:** 渐进降级压缩,绝不崩——LLM 完整摘要 → 排除超大消息的部分摘要 → 截断兜底。始终保留系统提示 + 最近 N 轮 + 关键标识符(ID/金额)。
|
|
90
|
+
|
|
91
|
+
**核心接口**(`foxclaw/core/compaction.py`):
|
|
92
|
+
```python
|
|
93
|
+
from foxclaw.core.compaction import Compactor
|
|
94
|
+
|
|
95
|
+
c = Compactor(router=model_router, max_tokens=6000, keep_recent=4)
|
|
96
|
+
if c.needs_compaction(messages):
|
|
97
|
+
result = c.compact(messages) # result.messages 是压缩后的列表
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**设计要点:** 压缩后校验是否真变小(省不到 10% 就放弃,method=`skipped_ineffective`),避免系统消息过大时压缩反而变大。`router=None` 时直接走截断兜底。
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 五、生命周期钩子标准化(Hooks)— 基础设施
|
|
105
|
+
|
|
106
|
+
**解决:** 钩子点命名零散、阻断权不清晰。
|
|
107
|
+
|
|
108
|
+
**原理:** 标准生命周期钩子 + 明确的阻断权 + 向后兼容。
|
|
109
|
+
|
|
110
|
+
**标准钩子点**(`foxclaw/skills/hooks_spec.py`):
|
|
111
|
+
- 会话级:`session_start` → `agent_start` → `agent_end` → `session_end`
|
|
112
|
+
- `agent_start`(别名 **`pre_reasoning`**)/ `agent_end`(别名 **`task_complete`**)由**认知管线 `run_task`** 触发(E2);`agent_start` 可返回 `additional_context` 注入本次推理(非阻断)。`session_start/end` 由 AHS skill 编排链触发。
|
|
113
|
+
- 工具级:`before_tool_call`(唯一可阻断)→ `after_tool_call` / `on_error` / `on_loop`
|
|
114
|
+
- 消息级:`before_message_write`
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from foxclaw.skills.lifecycle import HookManager
|
|
118
|
+
hm = HookManager()
|
|
119
|
+
hm.register("before_tool_call", my_hook) # 旧名 pre_skill 也兼容,自动规整
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**设计要点:** 只有 `before_tool_call` 有阻断权(返回 `{block:True}` 生效);旧名(pre_skill/post_skill)自动规整为标准名且互通;非法钩子名会记警告(防拼错静默失效)。
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 六、干预队列(Steering Queue)— 基础设施(为并行预留)
|
|
127
|
+
|
|
128
|
+
**解决:** 并行子 Agent 的结果如何安全回收——不丢、不重复。
|
|
129
|
+
|
|
130
|
+
**原理:** 租约机制。当前多 Agent 顺序执行用不上(直接收集即可);这是为**未来并行子 Agent** 预留的基础设施。
|
|
131
|
+
|
|
132
|
+
**核心接口**(`foxclaw/core/steering_queue.py`):
|
|
133
|
+
```python
|
|
134
|
+
from foxclaw.core.steering_queue import SteeringQueue
|
|
135
|
+
|
|
136
|
+
q = SteeringQueue(lease_seconds=30)
|
|
137
|
+
q.publish(requester, source_agent, payload) # 子任务完成入队
|
|
138
|
+
items = q.lease(requester) # 消费者租走(不会被重复租)
|
|
139
|
+
q.ack(item.item_id, item.lease_id) # 确认处理完移除
|
|
140
|
+
# 过期未 ack 的项自动回到 pending(主任务崩溃兜底)
|
|
141
|
+
# 顺序场景便捷:q.drain(requester) 一次性取走(尊重租约,跳过被租用项)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**设计要点:** 锁内挑选+标记(并发不重复租用);租约过期自动重入队(结果不丢);ack 校验 lease_id(防过期租约误删别人重新租走的项)。
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 测试
|
|
149
|
+
|
|
150
|
+
全部纳入回归(`python tests/run_all.py`):
|
|
151
|
+
- `test_governance.py` — 六项治理能力(幂等并发、boot_id 防复用、租约不重复等)
|
|
152
|
+
- `test_channels.py` — 渠道适配器 + 闭环 + 幂等去重
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 设计原则小结
|
|
157
|
+
|
|
158
|
+
这组能力反复体现几个原则,可供后续开发参考:
|
|
159
|
+
|
|
160
|
+
- **操作要校验是否真达到目的**:压缩后检查真变小了吗、幂等并发下真只执行一次吗——不假设操作一定有效。
|
|
161
|
+
- **静默失效最危险**:拼错钩子名、解密缺库、消息处理失败被吞——都要让问题可见(警告/last_error),而非默默不工作。
|
|
162
|
+
- **崩溃后不装没事**:重启恢复主动交代中断的任务,契合"可信"定位。
|
|
163
|
+
- **别只信 pid**:进程恢复要用 boot_id 这类不复用的标识,pid 会被 OS 复用。
|
|
164
|
+
- **便捷接口别破坏核心保证**:drain 要尊重租约,否则破坏"不重复"。
|