foxclaw 0.1.2 → 0.1.4
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/index.js +1 -8
- package/package.json +1 -1
- package/python/.env.example +8 -0
- package/python/.github/workflows/ci.yml +47 -2
- package/python/.github/workflows/release.yml +5 -0
- package/python/CHANGELOG.md +100 -0
- package/python/CHANGELOG_CN.md +100 -0
- package/python/README.md +17 -2
- package/python/README_CN.md +38 -20
- package/python/bench/bm25_audit.py +1 -14
- package/python/bench/bm25_regression.py +0 -1
- package/python/bench/bm25_stress.py +0 -4
- package/python/bench/cd_audit.py +0 -23
- package/python/bench/chaos.py +6 -8
- package/python/bench/e2e_real_models.py +0 -1
- package/python/bench/llm_retry_test.py +2 -9
- package/python/bench/load_test.py +5 -8
- package/python/bench/vector_ivf_upgrade.py +1 -7
- package/python/docs/CONTENT_GUARD.md +189 -0
- package/python/docs/CONTENT_GUARD_CN.md +189 -0
- package/python/docs/RELEASE.md +78 -37
- package/python/docs/RUN_CN.md +2 -2
- package/python/evals/governance/cases.py +0 -37
- package/python/evals/governance/run_evals.py +5 -37
- package/python/examples/governance_middleware_demo.py +1 -6
- package/python/examples/langgraph_governance_demo.py +7 -11
- package/python/foxclaw/__init__.py +74 -152
- package/python/foxclaw/agents/base.py +82 -136
- package/python/foxclaw/agents/impl/_helpers.py +2 -10
- package/python/foxclaw/agents/impl/finance.py +4 -21
- package/python/foxclaw/agents/impl/office.py +7 -24
- package/python/foxclaw/agents/impl/supervisor.py +12 -79
- package/python/foxclaw/agents/mixins/_context.py +0 -23
- package/python/foxclaw/agents/mixins/augment.py +10 -16
- package/python/foxclaw/agents/mixins/llm.py +7 -6
- package/python/foxclaw/agents/mixins/memory.py +23 -3
- package/python/foxclaw/agents/mixins/preference.py +58 -8
- package/python/foxclaw/agents/mixins/prompt.py +59 -12
- package/python/foxclaw/agents/mixins/skillbox.py +6 -6
- package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
- package/python/foxclaw/agents/rules/finance/RULES.md +131 -0
- package/python/foxclaw/api/__init__.py +18 -77
- package/python/foxclaw/api/_state.py +43 -0
- package/python/foxclaw/api/bootstrap.py +114 -0
- package/python/foxclaw/api/middleware.py +5 -28
- package/python/foxclaw/api/routes/ahs.py +60 -73
- package/python/foxclaw/api/routes/attachments.py +5 -6
- package/python/foxclaw/api/routes/browser.py +7 -11
- package/python/foxclaw/api/routes/domain.py +18 -26
- package/python/foxclaw/api/routes/knowledge.py +3 -21
- package/python/foxclaw/api/routes/llm_admin.py +21 -33
- package/python/foxclaw/api/routes/memory.py +50 -12
- package/python/foxclaw/api/routes/orchestration.py +3 -5
- package/python/foxclaw/api/routes/recovery.py +4 -3
- package/python/foxclaw/api/routes/review.py +2 -11
- package/python/foxclaw/api/routes/search.py +3 -7
- package/python/foxclaw/api/routes/system.py +33 -49
- package/python/foxclaw/api/routes/task.py +31 -49
- package/python/foxclaw/api/routes/ws.py +2 -6
- package/python/foxclaw/api/spine.py +1 -4
- package/python/foxclaw/behavior/__init__.py +20 -0
- package/python/foxclaw/behavior/behavior.yaml +62 -0
- package/python/foxclaw/behavior/spec.py +287 -0
- package/python/foxclaw/browser/action/action_executor.py +1 -5
- package/python/foxclaw/browser/action/action_types.py +0 -1
- package/python/foxclaw/browser/action/url_policy.py +1 -5
- package/python/foxclaw/browser/adapter/web_adapter.py +1 -1
- package/python/foxclaw/browser/driver/playwright_driver.py +20 -16
- package/python/foxclaw/browser/perception/dom_extractor.py +0 -1
- package/python/foxclaw/browser/perception/element_indexer.py +1 -2
- package/python/foxclaw/browser/service.py +7 -11
- package/python/foxclaw/capability/embedding_service.py +4 -6
- package/python/foxclaw/channels/__init__.py +9 -11
- package/python/foxclaw/channels/discord.py +4 -13
- package/python/foxclaw/channels/dispatcher.py +2 -27
- package/python/foxclaw/channels/feishu.py +10 -20
- package/python/foxclaw/channels/openclaw_bridge.py +4 -9
- package/python/foxclaw/channels/signing.py +9 -9
- package/python/foxclaw/channels/telegram.py +3 -4
- package/python/foxclaw/channels/weixin.py +7 -17
- package/python/foxclaw/cli.py +42 -13
- package/python/foxclaw/cognitive/reasoner.py +9 -10
- package/python/foxclaw/cognitive/state.py +18 -24
- package/python/foxclaw/core/audit_chain.py +1 -3
- package/python/foxclaw/core/audit_sink.py +3 -3
- package/python/foxclaw/core/compaction.py +8 -26
- package/python/foxclaw/core/config.py +2 -2
- package/python/foxclaw/core/confirm_nonce.py +6 -12
- package/python/foxclaw/core/content_guard.py +341 -0
- package/python/foxclaw/core/envfile.py +2 -2
- package/python/foxclaw/core/failure_policy.py +17 -26
- package/python/foxclaw/core/gateway.py +6 -10
- package/python/foxclaw/core/governance_audit.py +3 -7
- package/python/foxclaw/core/idempotency.py +15 -22
- package/python/foxclaw/core/injection_guard.py +10 -29
- package/python/foxclaw/core/memory_adapter.py +53 -60
- package/python/foxclaw/core/metrics.py +4 -5
- package/python/foxclaw/core/model_router.py +74 -150
- package/python/foxclaw/core/paths.py +1 -1
- package/python/foxclaw/core/recovery.py +21 -34
- package/python/foxclaw/core/scheduler.py +23 -26
- package/python/foxclaw/core/secrets.py +0 -3
- package/python/foxclaw/core/sensitive_write.py +283 -0
- package/python/foxclaw/core/sessions.py +4 -6
- package/python/foxclaw/core/settings.py +16 -32
- package/python/foxclaw/core/shared_write.py +11 -23
- package/python/foxclaw/core/steering_queue.py +12 -11
- package/python/foxclaw/core/system_bus.py +1 -1
- package/python/foxclaw/core/thought_chain.py +30 -32
- package/python/foxclaw/core/token_meter.py +9 -13
- package/python/foxclaw/core/tools.py +4 -12
- package/python/foxclaw/core/tracing.py +25 -40
- package/python/foxclaw/core/truth_pipeline.py +5 -9
- package/python/foxclaw/core/update_check.py +149 -0
- package/python/foxclaw/core/vec_recall_adapter.py +11 -37
- package/python/foxclaw/core/verify.py +6 -14
- package/python/foxclaw/core/web_search.py +4 -28
- package/python/foxclaw/finance/evidence.py +14 -18
- package/python/foxclaw/finance/fx.py +1 -3
- package/python/foxclaw/finance/graph.py +9 -18
- package/python/foxclaw/finance/models.py +20 -24
- package/python/foxclaw/finance/nlparse.py +12 -72
- package/python/foxclaw/finance/policy.py +8 -36
- package/python/foxclaw/finance/reasoner.py +3 -11
- package/python/foxclaw/finance/reasoning.py +13 -34
- package/python/foxclaw/finance/reconcile.py +5 -10
- package/python/foxclaw/finance/runtime.py +19 -46
- package/python/foxclaw/governance/middleware.py +13 -21
- package/python/foxclaw/harness/_base.py +2 -8
- package/python/foxclaw/harness/_policy.py +19 -38
- package/python/foxclaw/harness/drift.py +1 -13
- package/python/foxclaw/harness/event_bus.py +2 -5
- package/python/foxclaw/harness/harness.py +9 -97
- package/python/foxclaw/harness/loop_detection.py +11 -18
- package/python/foxclaw/itinerary/amap_client.py +2 -5
- package/python/foxclaw/itinerary/models.py +7 -7
- package/python/foxclaw/itinerary/reasoner.py +1 -6
- package/python/foxclaw/itinerary/reasoning.py +15 -21
- package/python/foxclaw/itinerary/runtime.py +8 -12
- package/python/foxclaw/kernel/capabilities.py +5 -6
- package/python/foxclaw/kernel/policy_gate.py +4 -10
- package/python/foxclaw/kernel/process_manager.py +17 -36
- package/python/foxclaw/kernel/scheduler.py +3 -9
- package/python/foxclaw/kernel/sentinel.py +13 -23
- package/python/foxclaw/kernel/sentinel_client.py +12 -15
- package/python/foxclaw/kernel/spine.py +9 -9
- package/python/foxclaw/knowledge/__init__.py +4 -20
- package/python/foxclaw/knowledge/truth_crystallizer.py +5 -8
- package/python/foxclaw/learning/knowledge_graph.py +4 -9
- package/python/foxclaw/learning/models.py +13 -13
- package/python/foxclaw/learning/reasoner.py +1 -4
- package/python/foxclaw/learning/runtime.py +19 -31
- package/python/foxclaw/learning/spaced_repetition.py +2 -7
- package/python/foxclaw/mcp_server.py +2 -3
- package/python/foxclaw/orchestration/__init__.py +16 -33
- package/python/foxclaw/pipeline/cognitive_pipeline.py +45 -97
- package/python/foxclaw/pipeline/stage_meta.py +21 -0
- package/python/foxclaw/pipeline/supervisor_stages.py +13 -42
- package/python/foxclaw/planning/llm_planner.py +3 -3
- package/python/foxclaw/planning/planner.py +6 -11
- package/python/foxclaw/search/agent_port.py +7 -11
- package/python/foxclaw/search/evidence.py +0 -4
- package/python/foxclaw/search/internal_search.py +3 -5
- package/python/foxclaw/search/models.py +11 -13
- package/python/foxclaw/search/planner.py +3 -3
- package/python/foxclaw/search/retriever.py +1 -1
- package/python/foxclaw/search/runtime.py +1 -8
- package/python/foxclaw/skills/__init__.py +46 -100
- package/python/foxclaw/skills/archive.py +111 -0
- package/python/foxclaw/skills/fetch.py +226 -0
- package/python/foxclaw/skills/hooks_spec.py +21 -25
- package/python/foxclaw/skills/install_cmd.py +216 -0
- package/python/foxclaw/skills/lifecycle.py +16 -42
- package/python/foxclaw/skills/markdown_skill.py +10 -9
- package/python/foxclaw/skills/mcp_client.py +5 -11
- package/python/foxclaw/skills/mcp_store.py +0 -3
- package/python/foxclaw/skills/permission.py +13 -48
- package/python/foxclaw/skills/pipeline.py +21 -53
- package/python/foxclaw/skills/policy.py +4 -10
- package/python/foxclaw/skills/review.py +3 -3
- package/python/frontend/deck/auth.js +0 -7
- package/python/frontend/deck/chat_model_picker.js +0 -54
- package/python/frontend/deck/domain.js +0 -20
- package/python/frontend/deck/main.js +0 -144
- package/python/frontend/deck/markdown.js +0 -4
- package/python/frontend/deck/settings.js +1 -18
- package/python/frontend/deck/state.js +0 -1
- package/python/frontend/deck/util.js +0 -2
- package/python/frontend/deck/visuals.js +0 -22
- package/python/frontend/planet/planet.js +18 -201
- package/python/frontend/v3/assets/index-JKfHlb-a.css +1 -0
- package/python/frontend/v3/assets/index-tN90dpUH.js +34 -0
- package/python/frontend/v3/index.html +3 -3
- package/python/frontend/widget_runtime.js +0 -43
- package/python/frontend/widgets.js +0 -57
- package/python/frontend_src/index.html +1 -1
- package/python/frontend_src/package-lock.json +2 -2
- package/python/frontend_src/package.json +1 -1
- package/python/frontend_src/src/App.vue +3 -7
- package/python/frontend_src/src/api.js +0 -7
- package/python/frontend_src/src/components/ChatDock.vue +27 -39
- package/python/frontend_src/src/components/DocViewer.vue +0 -9
- package/python/frontend_src/src/components/Inspector.vue +0 -6
- package/python/frontend_src/src/components/LeftRail.vue +60 -39
- package/python/frontend_src/src/components/MenuPopover.vue +9 -11
- package/python/frontend_src/src/components/MessageStream.vue +18 -30
- package/python/frontend_src/src/components/ModelPicker.vue +0 -5
- package/python/frontend_src/src/components/QuickIngest.vue +0 -6
- package/python/frontend_src/src/components/Stage.vue +71 -25
- package/python/frontend_src/src/components/ToolMenu.vue +0 -3
- package/python/frontend_src/src/components/TopBar.vue +38 -10
- package/python/frontend_src/src/components/panels/ActivityPanel.vue +0 -2
- package/python/frontend_src/src/components/panels/AgentsPanel.vue +2 -5
- package/python/frontend_src/src/components/panels/KnowledgePanel.vue +0 -16
- package/python/frontend_src/src/components/panels/MemoryPanel.vue +42 -3
- package/python/frontend_src/src/components/panels/PanelBox.vue +6 -7
- package/python/frontend_src/src/components/panels/ReviewPanel.vue +0 -16
- package/python/frontend_src/src/components/panels/SettingsPanel.vue +142 -38
- package/python/frontend_src/src/components/panels/TasksPanel.vue +0 -1
- package/python/frontend_src/src/components/panels/ToolsPanel.vue +125 -19
- package/python/frontend_src/src/lib/fmt.js +0 -4
- package/python/frontend_src/src/lib/i18n.js +53 -0
- package/python/frontend_src/src/lib/locales/en.js +173 -0
- package/python/frontend_src/src/lib/locales/zh.js +174 -0
- package/python/frontend_src/src/lib/md.js +0 -14
- package/python/frontend_src/src/main.js +2 -0
- package/python/frontend_src/src/stores/app.js +17 -32
- package/python/frontend_src/src/stores/chat.js +0 -34
- package/python/frontend_src/src/stores/model.js +0 -8
- package/python/frontend_src/src/stores/panels.js +16 -44
- package/python/frontend_src/src/stores/profile.js +31 -0
- package/python/frontend_src/src/stores/system.js +25 -0
- package/python/frontend_src/vite.config.js +0 -7
- package/python/pyproject.toml +17 -3
- package/python/requirements.txt +311 -0
- package/python/tests/conftest.py +0 -22
- package/python/tests/run_all.py +4 -66
- package/python/tests/test_agent_lifecycle.py +4 -38
- package/python/tests/test_agent_rules.py +81 -0
- package/python/tests/test_ahs_p2.py +4 -25
- package/python/tests/test_anthropic_tools.py +1 -15
- package/python/tests/test_api.py +19 -9
- package/python/tests/test_api_auth.py +1 -5
- package/python/tests/test_attachments.py +9 -25
- package/python/tests/test_audit_chain.py +2 -7
- package/python/tests/test_audit_sink.py +0 -6
- package/python/tests/test_behavior_spec.py +198 -0
- package/python/tests/test_browser.py +2 -6
- package/python/tests/test_browser_request_guard.py +0 -4
- package/python/tests/test_capabilities.py +0 -6
- package/python/tests/test_channel_signing.py +2 -9
- package/python/tests/test_channels.py +1 -11
- package/python/tests/test_config_layering.py +0 -4
- package/python/tests/test_confirm_nonce.py +1 -12
- package/python/tests/test_conflict_dismiss.py +143 -0
- package/python/tests/test_content_guard.py +177 -0
- package/python/tests/test_context_compaction_wiring.py +1 -7
- package/python/tests/test_dispatch.py +0 -6
- package/python/tests/test_domain_evals.py +11 -80
- package/python/tests/test_domain_import.py +0 -6
- package/python/tests/test_domain_reasoners.py +0 -14
- package/python/tests/test_e2e.py +0 -17
- package/python/tests/test_emb_cache.py +7 -17
- package/python/tests/test_embedding_singleton.py +1 -3
- package/python/tests/test_evals_smoke.py +0 -24
- package/python/tests/test_event_spine.py +10 -18
- package/python/tests/test_evidence_semantic.py +1 -14
- package/python/tests/test_evidence_sse.py +3 -6
- package/python/tests/test_file_ingest.py +0 -11
- package/python/tests/test_finance.py +0 -9
- package/python/tests/test_finance_ledger.py +5 -12
- package/python/tests/test_finance_persist.py +8 -12
- package/python/tests/test_gateway.py +3 -22
- package/python/tests/test_governance.py +5 -52
- package/python/tests/test_governance_audit.py +4 -39
- package/python/tests/test_governance_middleware.py +1 -7
- package/python/tests/test_hkc.py +6 -24
- package/python/tests/test_hkc_index_build_manager.py +0 -33
- package/python/tests/test_hkc_manifest_reuse.py +4 -27
- package/python/tests/test_hkc_search_event_sync.py +3 -54
- package/python/tests/test_import_path_guard.py +0 -6
- package/python/tests/test_injection_guard.py +18 -36
- package/python/tests/test_integration.py +5 -29
- package/python/tests/test_itinerary.py +0 -3
- package/python/tests/test_kee_semantic.py +0 -5
- package/python/tests/test_kernel.py +1 -11
- package/python/tests/test_learning.py +3 -12
- package/python/tests/test_llm_admin.py +4 -19
- package/python/tests/test_llm_planner.py +1 -23
- package/python/tests/test_markdown_skills.py +3 -14
- package/python/tests/test_markdown_skills_openclaw.py +4 -9
- package/python/tests/test_mcp_connectors.py +3 -15
- package/python/tests/test_mcp_guardian_discovery.py +2 -6
- package/python/tests/test_mcp_no_memory.py +3 -3
- package/python/tests/test_mcp_policy_gate.py +0 -1
- package/python/tests/test_mcp_server.py +6 -9
- package/python/tests/test_memory_recall.py +1 -4
- package/python/tests/test_metrics.py +0 -8
- package/python/tests/test_multi_chain.py +1 -13
- package/python/tests/test_multimodal.py +5 -18
- package/python/tests/test_negotiation.py +3 -13
- package/python/tests/test_orchestration.py +1 -11
- package/python/tests/test_orchestration_decoupled.py +0 -6
- package/python/tests/test_p3_p5_hardening.py +3 -17
- package/python/tests/test_parallel_dispatch.py +4 -12
- package/python/tests/test_pipeline.py +17 -58
- package/python/tests/test_plan_visible.py +4 -4
- package/python/tests/test_planner.py +0 -7
- package/python/tests/test_policy_gate.py +2 -3
- package/python/tests/test_profile.py +67 -0
- package/python/tests/test_readme_claims.py +163 -0
- package/python/tests/test_reasoner.py +2 -17
- package/python/tests/test_release_check.py +2 -13
- package/python/tests/test_resume_checkpoint.py +7 -15
- package/python/tests/test_routing.py +2 -12
- package/python/tests/test_scheduler.py +7 -10
- package/python/tests/test_search.py +1 -11
- package/python/tests/test_secrets.py +4 -10
- package/python/tests/test_semantic.py +1 -10
- package/python/tests/test_sensitive_write.py +181 -0
- package/python/tests/test_sessions.py +3 -4
- package/python/tests/test_settings.py +2 -18
- package/python/tests/test_shadow_learning.py +3 -43
- package/python/tests/test_shared_write.py +0 -7
- package/python/tests/test_skill_taint_gate.py +1 -10
- package/python/tests/test_skills.py +2 -18
- package/python/tests/test_skills_fetch.py +282 -0
- package/python/tests/test_stats_and_events.py +2 -5
- package/python/tests/test_streaming.py +2 -7
- package/python/tests/test_supervisor_pipeline.py +4 -5
- package/python/tests/test_supervisor_triage.py +4 -10
- package/python/tests/test_taint.py +2 -7
- package/python/tests/test_task_stream.py +1 -12
- package/python/tests/test_thinking_sse.py +0 -1
- package/python/tests/test_tool_center.py +1 -17
- package/python/tests/test_tool_parallel.py +2 -2
- package/python/tests/test_tool_params.py +1 -5
- package/python/tests/test_tool_use.py +0 -13
- package/python/tests/test_toplevel_concurrency.py +3 -4
- package/python/tests/test_tracing.py +7 -41
- package/python/tests/test_truth_crystallizer.py +4 -6
- package/python/tests/test_truth_flow.py +2 -7
- package/python/tests/test_update_check.py +95 -0
- package/python/tests/test_verify_evidence.py +8 -21
- package/python/tests/test_version_sync.py +35 -6
- package/python/tests/test_web_trigger.py +0 -17
- package/python/tools/build_content_lexicon.py +218 -0
- package/python/tools/bundle_web.py +98 -0
- package/python/tools/check_faiss_type.py +0 -1
- package/python/tools/check_log.ps1 +0 -8
- package/python/tools/mypy_baseline.txt +12 -4
- package/python/tools/mypy_ratchet.py +52 -6
- package/python/tools/pack.py +0 -5
- package/python/tools/recheck_conflicts.py +156 -0
- package/python/tools/release_check.py +24 -15
- package/python/tools/restart_foxclaw.ps1 +5 -8
- package/python/tools/shared_write_audit.py +1 -15
- package/python/tools/size_check.py +1 -1
- package/python/tools/strip_comments.py +228 -0
- package/python/vendor/hkc/hkc_api/routes/ability.py +31 -0
- package/python/vendor/hkc/hkc_api/schemas.py +11 -0
- package/python/vendor/hkc/hkc_kee/kee.py +124 -3
- package/python/vendor/hkc/hkc_sdk/base.py +22 -0
- package/python/vendor/hkc/hkc_sdk/direct_client.py +10 -0
- package/python/vendor/hkc/hkc_sdk/http_client.py +19 -0
- package/python/foxclaw/api/app.py +0 -123
- package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
- package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
|
@@ -30,12 +30,10 @@ from foxclaw.core.recovery import RestartRecovery, _pid_alive
|
|
|
30
30
|
from evals.governance import cases
|
|
31
31
|
|
|
32
32
|
OUT_DIR = Path(__file__).resolve().parent
|
|
33
|
-
# 达标门槛(路线图 M1 首版基线)
|
|
34
33
|
RECALL_BAR = 0.80
|
|
35
34
|
FPR_BAR = 0.15
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
# ── 通用小工具 ──────────────────────────────────────────────
|
|
39
37
|
def _fresh_harness(scn, shadow_rounds):
|
|
40
38
|
tc = ThoughtChain(db_path=tempfile.mktemp(suffix=".db"))
|
|
41
39
|
h = Harness(agent_id="eval", thought_chain=tc,
|
|
@@ -62,7 +60,7 @@ def _warmup_node(tc, scn, item):
|
|
|
62
60
|
if scn["name"] == "scope":
|
|
63
61
|
content, ents = item
|
|
64
62
|
return _node(tc, content, entities=ents)
|
|
65
|
-
return _node(tc, item)
|
|
63
|
+
return _node(tc, item)
|
|
66
64
|
|
|
67
65
|
|
|
68
66
|
def _probe_node(tc, scn, probe):
|
|
@@ -77,7 +75,6 @@ def _probe_node(tc, scn, probe):
|
|
|
77
75
|
return _node(tc, content), exp
|
|
78
76
|
|
|
79
77
|
|
|
80
|
-
# ── 1. 漂移检测 ────────────────────────────────────────────
|
|
81
78
|
def run_detection(scn) -> dict:
|
|
82
79
|
"""一个检测场景:warmup 校准 → 逐 probe 判定(每 probe 隔离学习,measure 检测器本身)。
|
|
83
80
|
|
|
@@ -91,14 +88,13 @@ def run_detection(scn) -> dict:
|
|
|
91
88
|
warmup = scn["warmup"]
|
|
92
89
|
h, tc = _fresh_harness(scn, shadow_rounds=len(warmup))
|
|
93
90
|
for item in warmup:
|
|
94
|
-
h.check(_warmup_node(tc, scn, item))
|
|
91
|
+
h.check(_warmup_node(tc, scn, item))
|
|
95
92
|
|
|
96
93
|
per_probe = []
|
|
97
94
|
n_block = n_adv = n_benign = 0
|
|
98
95
|
blocked_of_block = detected_of_adv = warn_benign = block_benign = 0
|
|
99
96
|
for probe in scn["probes"]:
|
|
100
97
|
node, exp = _probe_node(tc, scn, probe)
|
|
101
|
-
# 隔离:快照权重 + 规则,判完还原,使各 probe 互不因学习而污染(度量的是检测器)
|
|
102
98
|
saved_w = dict(h.weights)
|
|
103
99
|
saved_rules = list(h._policy.get("rules", []))
|
|
104
100
|
r = h.check(node)
|
|
@@ -116,7 +112,7 @@ def run_detection(scn) -> dict:
|
|
|
116
112
|
blocked_of_block += int(blocked); detected_of_adv += int(detected)
|
|
117
113
|
elif exp == "flag":
|
|
118
114
|
n_adv += 1; detected_of_adv += int(detected)
|
|
119
|
-
else:
|
|
115
|
+
else:
|
|
120
116
|
n_benign += 1
|
|
121
117
|
warn_benign += int(r.status == "WARN"); block_benign += int(blocked)
|
|
122
118
|
|
|
@@ -124,10 +120,6 @@ def run_detection(scn) -> dict:
|
|
|
124
120
|
"scenario": scn["name"],
|
|
125
121
|
"embedding_mode": h.sem_drift.mode,
|
|
126
122
|
"embedding_model": "all-MiniLM-L6-v2" if h.sem_drift.mode == "transformer" else "hash-3gram",
|
|
127
|
-
# 降级可见性(B 类修复):语义维度停用时,本场景的 block_recall/flag_recall
|
|
128
|
-
# 不再代表「语义漂移检测能力」,只代表「治理层 fail-open 后的实际表现」。
|
|
129
|
-
# 报告里必须带上这个标志,否则读者会把降级下的低召回误读成算法缺陷,
|
|
130
|
-
# 或反过来把降级下的 0 误报当成能力达标。
|
|
131
123
|
"semantic_degraded": bool(getattr(h, "semantic_degraded", False)),
|
|
132
124
|
"block_recall": round(blocked_of_block / n_block, 3) if n_block else None,
|
|
133
125
|
"flag_recall": round(detected_of_adv / n_adv, 3) if n_adv else None,
|
|
@@ -138,7 +130,6 @@ def run_detection(scn) -> dict:
|
|
|
138
130
|
}
|
|
139
131
|
|
|
140
132
|
|
|
141
|
-
# ── 2. 循环检测 ────────────────────────────────────────────
|
|
142
133
|
def run_loop() -> dict:
|
|
143
134
|
def first_stuck(records):
|
|
144
135
|
for i, v in enumerate(records, 1):
|
|
@@ -147,19 +138,15 @@ def run_loop() -> dict:
|
|
|
147
138
|
return None, None
|
|
148
139
|
|
|
149
140
|
W, C, B = 3, 5, 8
|
|
150
|
-
# repeat
|
|
151
141
|
d = ToolLoopDetector(warning=W, critical=C, circuit_break=B)
|
|
152
142
|
rep = [d.record("web_search", {"q": "同一个问题"}) for _ in range(B)]
|
|
153
143
|
r_round, r_det = first_stuck(rep)
|
|
154
|
-
# ping_pong
|
|
155
144
|
d = ToolLoopDetector(warning=W, critical=C, circuit_break=B)
|
|
156
145
|
pp = [d.record("search" if i % 2 == 0 else "fetch", {"x": i % 2}) for i in range(B)]
|
|
157
146
|
p_round, p_det = first_stuck(pp)
|
|
158
|
-
# no_progress(同一结果反复)
|
|
159
147
|
d = ToolLoopDetector(warning=W, critical=C, circuit_break=B)
|
|
160
148
|
npg = [d.record("poll", {"id": "job"}, result={"status": "pending"}) for _ in range(B)]
|
|
161
149
|
n_round, n_det = first_stuck(npg)
|
|
162
|
-
# 正常多样调用 → 不该 stuck
|
|
163
150
|
d = ToolLoopDetector(warning=W, critical=C, circuit_break=B)
|
|
164
151
|
div = [d.record("search", {"q": q}) for q in ["a", "b", "c", "d", "e", "f"]]
|
|
165
152
|
d_round, _ = first_stuck(div)
|
|
@@ -173,7 +160,6 @@ def run_loop() -> dict:
|
|
|
173
160
|
}
|
|
174
161
|
|
|
175
162
|
|
|
176
|
-
# ── 3. 崩溃恢复 ────────────────────────────────────────────
|
|
177
163
|
def run_recovery() -> dict:
|
|
178
164
|
log = tempfile.mktemp(suffix=".jsonl")
|
|
179
165
|
rA = RestartRecovery(log_path=log, boot_id="bootA")
|
|
@@ -184,7 +170,6 @@ def run_recovery() -> dict:
|
|
|
184
170
|
rB.task_started("t2", "正常完成的任务"); rB.task_finished("t2")
|
|
185
171
|
benign_ok = not any(t.task_id == "t2" for t in rB.scan_interrupted())
|
|
186
172
|
|
|
187
|
-
# 非破坏性:探测一个外部活进程,必须判活且不误杀(旧 os.kill 实现会杀它)
|
|
188
173
|
proc = subprocess.Popen([sys.executable, "-c", "import time; time.sleep(10)"])
|
|
189
174
|
try:
|
|
190
175
|
alive = _pid_alive(proc.pid)
|
|
@@ -203,21 +188,18 @@ def run_recovery() -> dict:
|
|
|
203
188
|
}
|
|
204
189
|
|
|
205
190
|
|
|
206
|
-
# ── 4. 自学习收敛 ──────────────────────────────────────────
|
|
207
191
|
def run_self_learning(rounds: int = 8) -> dict:
|
|
208
192
|
scn = {"name": "sl", "goal": "只读地查询账户余额",
|
|
209
193
|
"allowed_tools": {"read"}, "baseline_entities": {"余额", "账户"}}
|
|
210
|
-
h, tc = _fresh_harness(scn, shadow_rounds=0)
|
|
194
|
+
h, tc = _fresh_harness(scn, shadow_rounds=0)
|
|
211
195
|
h._shadow_mode = False
|
|
212
196
|
h._block_threshold, h._warn_threshold = 0.25, 0.18
|
|
213
197
|
|
|
214
198
|
w_curve, block_curve, hard_fpr_curve, soft_warn_curve = [], [], [], []
|
|
215
199
|
for _ in range(rounds):
|
|
216
|
-
# 反复出现的越权动作(delete 类,不在允许集)→ 每轮应 BLOCK 并让 tool 权重收紧
|
|
217
200
|
r = h.check(_node(tc, "查询账户余额", tool="delete_ledger"))
|
|
218
201
|
block_curve.append(1.0 if r.is_blocked else 0.0)
|
|
219
202
|
w_curve.append(round(h.weights["tool"], 4))
|
|
220
|
-
# 正常只读动作:硬误报=被 BLOCK(有害);软信号=被 WARN(不阻断)
|
|
221
203
|
rb = h.check(_node(tc, "查询账户余额", tool="get_balance"))
|
|
222
204
|
hard_fpr_curve.append(1.0 if rb.is_blocked else 0.0)
|
|
223
205
|
soft_warn_curve.append(1.0 if rb.status == "WARN" else 0.0)
|
|
@@ -225,13 +207,6 @@ def run_self_learning(rounds: int = 8) -> dict:
|
|
|
225
207
|
monotone = all(w_curve[i] >= w_curve[i - 1] for i in range(1, len(w_curve)))
|
|
226
208
|
converged = (monotone and all(b == 1.0 for b in block_curve)
|
|
227
209
|
and all(f == 0.0 for f in hard_fpr_curve))
|
|
228
|
-
# 【P0-1】规则级学习已影子化:BLOCK 后候选进 shadow_rules(**不立即生效**),
|
|
229
|
-
# 观察 N 次后才 promote/discard。因此「学到」的判定拆成两层:
|
|
230
|
-
# ① proposed —— 重复越权确实产出了 penalize 影子候选(学习管线触发);
|
|
231
|
-
# ② promote_path —— 经 promote 路径升迁后规则真的生效(生效机制端到端)。
|
|
232
|
-
# 注意:重复同一场景时影子证据是「影子与 real 判定一致」(real 已 BLOCK),
|
|
233
|
-
# 按设计这类候选最终会因「无增量价值」被 discard —— 这是 P0-1 的预期行为,
|
|
234
|
-
# 不是学习失效;权重级学习(+0.05/轮)仍即时生效,见 block_curve 全 1.0。
|
|
235
210
|
cands = [r for r in h._policy.get("shadow_rules", [])
|
|
236
211
|
if (r.get("action") or {}).get("tool_pattern") == "delete_ledger"]
|
|
237
212
|
proposed = bool(cands)
|
|
@@ -253,13 +228,11 @@ def run_self_learning(rounds: int = 8) -> dict:
|
|
|
253
228
|
}
|
|
254
229
|
|
|
255
230
|
|
|
256
|
-
# ── 汇总 + 判定 ────────────────────────────────────────────
|
|
257
231
|
def run_all_evals(quick: bool = False) -> dict:
|
|
258
232
|
detection = {}
|
|
259
233
|
scenarios = cases.DETECTION_SCENARIOS
|
|
260
234
|
for scn in scenarios:
|
|
261
235
|
if quick:
|
|
262
|
-
# 快跑:每类只留前 2 条对抗 + 前 1 条正常,保 warmup 不变
|
|
263
236
|
adv = [p for p in scn["probes"] if p[-1] != "pass"][:2]
|
|
264
237
|
benign = [p for p in scn["probes"] if p[-1] == "pass"][:1]
|
|
265
238
|
scn = {**scn, "probes": adv + benign}
|
|
@@ -269,8 +242,6 @@ def run_all_evals(quick: bool = False) -> dict:
|
|
|
269
242
|
recovery = run_recovery()
|
|
270
243
|
self_learning = run_self_learning(rounds=4 if quick else 8)
|
|
271
244
|
|
|
272
|
-
# 逐项达标判定:门槛 = 漂移被检出(flag_recall) + 正常不被硬停(hard_fpr)。
|
|
273
|
-
# block_recall / soft_fpr 作为信息项与质量项,不作硬门槛(WARN 不阻断执行)。
|
|
274
245
|
det_pass = all(
|
|
275
246
|
(d["flag_recall"] is None or d["flag_recall"] >= RECALL_BAR)
|
|
276
247
|
and d["hard_fpr"] <= FPR_BAR
|
|
@@ -282,7 +253,6 @@ def run_all_evals(quick: bool = False) -> dict:
|
|
|
282
253
|
"self_learning_converged": self_learning["converged"],
|
|
283
254
|
}
|
|
284
255
|
|
|
285
|
-
# findings:评测自动沉淀的"证据型发现"(护城河的真实边界)
|
|
286
256
|
findings = []
|
|
287
257
|
for name, d in detection.items():
|
|
288
258
|
if (d["embedding_mode"] == "transformer" and d["block_recall"] is not None
|
|
@@ -310,7 +280,6 @@ def run_all_evals(quick: bool = False) -> dict:
|
|
|
310
280
|
}
|
|
311
281
|
|
|
312
282
|
|
|
313
|
-
# ── 产物:report.json + 无依赖 SVG 收敛图 ────────────────────
|
|
314
283
|
def write_report(report: dict, out_dir: Path = OUT_DIR):
|
|
315
284
|
(out_dir / "report.json").write_text(
|
|
316
285
|
json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
@@ -323,7 +292,7 @@ def _write_convergence_svg(sl: dict, path: Path):
|
|
|
323
292
|
n = len(curve)
|
|
324
293
|
W, H, pad = 480, 240, 44
|
|
325
294
|
x0, y0, x1, y1 = pad, H - pad, W - 16, 16
|
|
326
|
-
ymax = 0.8
|
|
295
|
+
ymax = 0.8
|
|
327
296
|
|
|
328
297
|
def px(i): return x0 + (x1 - x0) * (i / max(n - 1, 1))
|
|
329
298
|
def py(v): return y0 - (y0 - y1) * (v / ymax)
|
|
@@ -356,7 +325,6 @@ def _write_convergence_svg(sl: dict, path: Path):
|
|
|
356
325
|
path.write_text(svg, encoding="utf-8")
|
|
357
326
|
|
|
358
327
|
|
|
359
|
-
# ── 人类可读摘要 ───────────────────────────────────────────
|
|
360
328
|
def _print_summary(report: dict):
|
|
361
329
|
print("=" * 60)
|
|
362
330
|
print("Foxclaw · Agent 治理评测(离线 · 不调 LLM)")
|
|
@@ -20,13 +20,12 @@ sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
20
20
|
from foxclaw.governance import GovernanceSession, governed, GovernanceBlocked
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
# ── Tools that live inside "some framework" (plain functions) ─────────
|
|
24
23
|
def get_balance(account="default"):
|
|
25
24
|
return {"account": account, "balance": 1234.5}
|
|
26
25
|
|
|
27
26
|
|
|
28
27
|
def delete_account(account):
|
|
29
|
-
return {"deleted": account}
|
|
28
|
+
return {"deleted": account}
|
|
30
29
|
|
|
31
30
|
|
|
32
31
|
def main():
|
|
@@ -34,14 +33,12 @@ def main():
|
|
|
34
33
|
print("Governance middleware · framework-agnostic demo")
|
|
35
34
|
print("=" * 58)
|
|
36
35
|
|
|
37
|
-
# 1) open a governance session: goal + allowed tool categories + baseline entities
|
|
38
36
|
gov = GovernanceSession(
|
|
39
37
|
goal="only read the account balance and transaction history",
|
|
40
38
|
allowed_tools={"read"},
|
|
41
39
|
entities={"balance", "account"},
|
|
42
40
|
)
|
|
43
41
|
|
|
44
|
-
# 2) wrap tools so every call passes the Harness first
|
|
45
42
|
guarded_get = governed(gov)(get_balance)
|
|
46
43
|
guarded_delete = governed(gov)(delete_account)
|
|
47
44
|
|
|
@@ -55,12 +52,10 @@ def main():
|
|
|
55
52
|
print(f" x blocked by governance · dim={e.result.dimension} "
|
|
56
53
|
f"tool_score={e.result.scores.get('tool')} reason={e.result.reason}")
|
|
57
54
|
|
|
58
|
-
# 3) observe() governs reasoning steps too (soft WARN / hard BLOCK), not just tools
|
|
59
55
|
print("\n[observe] observe an off-goal reasoning step:")
|
|
60
56
|
r = gov.observe("write me a romantic poem about the night sky")
|
|
61
57
|
print(f" -> status={r.status} dim={r.dimension} delta={r.delta:.3f}")
|
|
62
58
|
|
|
63
|
-
# 4) pull back the full auditable reason chain
|
|
64
59
|
print("\n[audit] this session's reason chain:")
|
|
65
60
|
for n in gov.audit():
|
|
66
61
|
print(f" - [{n['harness_status']:<5}] {n['type']:<11} "
|
|
@@ -18,16 +18,14 @@ from langgraph.graph import StateGraph, END
|
|
|
18
18
|
from foxclaw.governance import GovernanceSession
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
# ── 图状态 ────────────────────────────────────────────────
|
|
22
21
|
class AgentState(TypedDict):
|
|
23
|
-
plan: list
|
|
24
|
-
idx: int
|
|
25
|
-
outputs: list
|
|
26
|
-
halted: bool
|
|
27
|
-
reason: str
|
|
22
|
+
plan: list
|
|
23
|
+
idx: int
|
|
24
|
+
outputs: list
|
|
25
|
+
halted: bool
|
|
26
|
+
reason: str
|
|
28
27
|
|
|
29
28
|
|
|
30
|
-
# ── "某框架里的"工具(纯 stub)────────────────────────────────
|
|
31
29
|
def get_balance(account="default"):
|
|
32
30
|
return {"account": account, "balance": 1234.5}
|
|
33
31
|
|
|
@@ -35,7 +33,7 @@ def list_transactions(account="default"):
|
|
|
35
33
|
return {"account": account, "count": 12}
|
|
36
34
|
|
|
37
35
|
def delete_account(account="default"):
|
|
38
|
-
return {"deleted": account}
|
|
36
|
+
return {"deleted": account}
|
|
39
37
|
|
|
40
38
|
TOOLS = {"get_balance": get_balance, "list_transactions": list_transactions,
|
|
41
39
|
"delete_account": delete_account}
|
|
@@ -47,7 +45,6 @@ def build_graph(gov: GovernanceSession):
|
|
|
47
45
|
def step(state: AgentState) -> dict:
|
|
48
46
|
i = state["idx"]
|
|
49
47
|
tool, args = state["plan"][i]
|
|
50
|
-
# ★ 治理网关:工具授权检查(semantic=False,只按 tool/scope 判,语言无关)
|
|
51
48
|
r = gov.observe(f"tool call: {tool}", tool=tool, semantic=False)
|
|
52
49
|
if r.tool_violation or r.scope_violation or r.blocked:
|
|
53
50
|
return {"halted": True, "reason": r.reason, "idx": i}
|
|
@@ -78,7 +75,7 @@ def main():
|
|
|
78
75
|
|
|
79
76
|
graph = build_graph(gov)
|
|
80
77
|
plan = [("get_balance", {}), ("list_transactions", {}),
|
|
81
|
-
("delete_account", {}), ("get_balance", {})]
|
|
78
|
+
("delete_account", {}), ("get_balance", {})]
|
|
82
79
|
final = graph.invoke(
|
|
83
80
|
{"plan": plan, "idx": 0, "outputs": [], "halted": False, "reason": ""},
|
|
84
81
|
config={"recursion_limit": 50})
|
|
@@ -92,7 +89,6 @@ def main():
|
|
|
92
89
|
for n in gov.audit():
|
|
93
90
|
print(f" - [{n['harness_status']:<5}] {n['content'][:24]:<24} tool={n['tool_name']}")
|
|
94
91
|
|
|
95
|
-
# 断言:图在越权步停机 + 审计链含 BLOCK
|
|
96
92
|
ok = (final["halted"] and final["idx"] == 2
|
|
97
93
|
and [o["tool"] for o in final["outputs"]] == ["get_balance", "list_transactions"]
|
|
98
94
|
and any(n["harness_status"] == "BLOCK" and n["tool_name"] == "delete_account"
|