@furongjun1999/dsh-memory 0.4.7 → 0.4.8
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 +92 -37
- package/codebuddy/CODEBUDDY.md +195 -185
- package/docs/Pi/345/217/257/345/255/246/344/271/240/344/274/230/347/202/271_/347/201/265/346/236/242/345/244/247/350/204/221/346/224/271/350/277/233/344/272/244/346/216/245_20260915.md +144 -0
- package/docs/README.md +111 -0
- package/docs/discipline/harnesses.yaml +215 -152
- package/docs/discipline/templates/full.md.tmpl +60 -58
- package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_LoCoMo_/347/201/265/346/236/242_.md +127 -0
- package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_LoCoMo_/347/201/265/346/236/242_.png +0 -0
- package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_/347/201/265/346/236/242_vs_dejavu_/347/273/237/344/270/200/350/257/204/345/210/206_v7.md +202 -0
- package/docs/experiments/m4-role-probe/census.py +86 -0
- package/docs/experiments/m4-role-probe/probe.py +89 -0
- package/docs/experiments/mapped_confidence/bench_mapped_conf.py +216 -0
- package/docs/experiments/mapped_confidence/post_check.py +75 -0
- package/docs/experiments/mapped_confidence/repro_thirdparty_tol.py +83 -0
- package/docs/experiments/mapped_confidence/result_mapped_conf.json +215 -0
- package/docs/{ → hive/}/350/234/202/345/267/242/345/267/245/344/275/234/350/256/260/345/277/206_/351/241/271/347/233/256/350/256/241/345/210/222.md +14 -2
- package/docs/{README → mdcg/README}/350/257/246/347/273/206/347/211/210_v0.4.5.md +9 -9
- package/docs/{ → mdcg/}/344/270/273/344/273/243/347/220/206/345/255/220/344/273/243/347/220/206/350/256/260/345/277/206/346/236/266/346/236/204/350/256/276/350/256/241.md +1 -1
- package/docs/mdcg//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +261 -0
- package/docs/{ → mdcg/}/345/215/225/345/205/203/350/207/252/346/210/221/351/224/232/347/202/271_/347/263/273/347/273/237/346/217/220/347/244/272/350/257/215/346/240/207/345/207/206_v0.3.md +1 -1
- package/docs/mdcg//345/255/220/344/273/243/347/220/206/351/205/215/347/275/256/346/240/207/345/207/206_v0.4.md +148 -0
- package/docs/{ → mdcg/}/346/272/220/347/240/201/347/272/247/346/236/266/346/236/204/345/256/241/350/256/241_GPT/346/211/271/350/257/204/345/257/271/347/205/247_v1.0.md +2 -2
- package/docs/{ → mdcg/}/347/201/265/346/236/242/344/270/211/345/261/202/346/213/206/345/210/206/350/247/204/345/210/222_v0.1.md +181 -181
- package/docs/{ → mdcg/}/347/274/272/345/217/243/345/215/225_P0/346/224/266/345/217/243_v0.1.md +2 -2
- package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_G4-G8/347/274/272/345/217/243/350/243/201/345/256/232/345/215/225_v0.1.md +1 -1
- package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_/347/264/242/345/274/225/344/270/216/345/267/245/347/250/213/350/247/204/350/214/203/345/214/226_/350/256/241/345/210/222_v0.1.md +4 -4
- package/docs/{ → mdcg/}/350/256/260/345/277/206/346/223/215/344/275/234/347/263/273/347/273/237_MdCGOS/344/270/216MCP/346/216/245/345/205/245_v0.1.md +2 -2
- package/docs/plans/GridWorld/346/234/200/345/260/217/351/227/255/347/216/257/350/247/204/346/240/274_v0.1.md +176 -0
- package/docs/{ → swarm/}/350/234/202/347/276/244/344/272/222/350/201/224_v0.1.md +3 -3
- package/docs/swarm//350/234/202/347/276/244/345/220/214/351/224/231/346/243/200/346/265/213/345/256/236/351/252/214/345/215/217/350/256/256_v0.1.md +242 -0
- package/docs/theory//345/215/225/347/272/277/347/250/213/344/270/216/346/263/250/346/204/217/345/212/233/351/233/206/344/270/255_/346/227/240/344/272/211/350/256/256/347/220/206/350/256/272/346/226/207/346/241/243_v1.0.md +129 -0
- package/docs/theory//345/271/266/345/217/221/345/277/205/347/204/266/346/200/247/347/220/206/350/256/272_v0.2.md +134 -0
- package/docs/theory//346/246/202/345/277/265/345/210/206/345/261/202/345/257/271/351/275/220/350/241/250_v0.1.md +145 -0
- package/docs/{ → theory/}/347/220/206/350/256/272_/346/234/272/345/210/266_/344/273/243/347/240/201_/345/256/236/351/252/214_/347/274/272/345/217/243/347/237/251/351/230/265_v0.1.md +3 -3
- package/docs/{ → theory/}/347/220/206/350/256/272/344/273/223/346/213/206/345/210/206/344/270/216/347/231/275/347/256/261/347/237/245/350/257/206/345/272/223/345/206/205/350/277/201_v0.1.md +1 -1
- package/docs/theory//350/256/244/347/237/245/344/273/243/347/220/206/344/270/216/346/224/266/346/225/233/347/273/223/346/236/204_/346/235/241/344/273/266/350/256/272/351/207/215/346/236/204_v0.1.md +221 -0
- package/docs/world_model//344/270/226/347/225/214/346/250/241/345/236/213_/347/245/236/347/273/217/347/275/221/347/273/234/345/272/225/345/261/202/346/236/266/346/236/204_v1.0.md +237 -0
- package/docs//345/255/230/347/256/227/344/270/200/344/275/223/346/236/266/346/236/204/345/255/246/344/271/240_/345/244/226/351/203/250/347/220/206/350/256/272/345/257/271/347/205/247/344/270/216/350/267/257/347/272/277/344/272/244/346/216/245_20260916.md +98 -0
- package/docs//345/255/230/347/256/227/344/270/200/344/275/223/347/245/236/347/273/217/347/275/221/347/273/234/346/236/266/346/236/204_/346/200/273/347/272/262/344/270/216/344/272/244/346/216/245_20260916.md +109 -0
- package/docs//345/267/245/344/275/234/347/272/252/345/276/213_/350/256/244/347/237/245/345/233/276/346/235/241/347/233/256_v1.1.json +34 -6
- package/docs//347/231/275/347/256/261/345/214/226/347/272/262/351/242/206_/344/270/211/351/241/271/347/233/256/345/255/246/344/271/240/346/200/273/346/236/266/346/236/204/344/270/216Ghidra/344/272/244/346/216/245_20260916.md +104 -0
- package/docs//350/256/260/345/277/206/350/257/204/345/256/241/347/263/273/347/273/237_/347/253/213/351/241/271/350/256/276/350/256/241/344/270/216/346/226/275/345/267/245/344/272/244/346/216/245_20260915.md +183 -0
- package/dsh/README.md +1 -1
- package/lib/hooks.d.ts +5 -0
- package/lib/hooks.js +10 -1
- package/lib/lib/prompt_safety.d.ts +48 -0
- package/lib/lib/prompt_safety.js +62 -0
- package/md_cg/audit.py +76 -2
- package/md_cg/bench_membench.py +10 -5
- package/md_cg/bench_progressive.py +48 -9
- package/md_cg/branches.py +275 -0
- package/md_cg/ccgc.py +884 -0
- package/md_cg/conformance.py +662 -0
- package/md_cg/consistency.py +1 -1
- package/md_cg/corpus.py +1 -1
- package/md_cg/eval_common.py +6 -5
- package/md_cg/forgetting.py +11 -3
- package/md_cg/fsutil.py +63 -1
- package/md_cg/identity.py +2 -2
- package/md_cg/insight.py +2 -2
- package/md_cg/lifecycle.py +262 -0
- package/md_cg/mcp_server.py +547 -140
- package/md_cg/mdcg.py +206 -18
- package/md_cg/mdcos.py +512 -50
- package/md_cg/metacognition.py +2 -2
- package/md_cg/mreview/__init__.py +25 -0
- package/md_cg/mreview/__main__.py +107 -0
- package/md_cg/mreview/bundle.py +170 -0
- package/md_cg/mreview/candidates.py +253 -0
- package/md_cg/mreview/govern.py +674 -0
- package/md_cg/mreview/locate.py +905 -0
- package/md_cg/mreview/pipeline.py +701 -0
- package/md_cg/mreview/rules/duplication.json +21 -0
- package/md_cg/mreview/rules/field_coverage.json +54 -0
- package/md_cg/mreview/rules/source_license.json +21 -0
- package/md_cg/mreview/rules/template_flow.json +21 -0
- package/md_cg/mreview/ruleset.py +238 -0
- package/md_cg/nodefile.py +38 -0
- package/md_cg/predict.py +24 -6
- package/md_cg/protect.py +4 -4
- package/md_cg/refindex.py +41 -2
- package/md_cg/scrub.py +5 -1
- package/md_cg/self_state.py +1 -1
- package/md_cg/semantic/en_normalizer.py +85 -25
- package/md_cg/sustain.py +18 -0
- package/md_cg/tasks.py +447 -0
- package/md_cg/test_action_derive.py +203 -0
- package/md_cg/test_audit_rotate.py +270 -0
- package/md_cg/test_branches.py +249 -0
- package/md_cg/test_ccg_perturb.py +1 -1
- package/md_cg/test_ccgc.py +423 -0
- package/md_cg/test_conformance.py +343 -0
- package/md_cg/test_en_pipeline.py +24 -0
- package/md_cg/test_health_scale.py +173 -0
- package/md_cg/test_identity_attribution.py +6 -0
- package/md_cg/test_index_durability.py +224 -0
- package/md_cg/test_lifecycle.py +309 -0
- package/md_cg/test_mr_m1.py +679 -0
- package/md_cg/test_mr_m2.py +587 -0
- package/md_cg/test_mr_m3.py +703 -0
- package/md_cg/test_mr_m4.py +485 -0
- package/md_cg/test_p1.py +1 -1
- package/md_cg/test_p11_consistency.py +1 -1
- package/md_cg/test_p12_metacognition.py +2 -2
- package/md_cg/test_p16_self_state.py +1 -1
- package/md_cg/test_p26_refindex.py +1 -1
- package/md_cg/test_p27_docindex.py +22 -2
- package/md_cg/test_p28_refcheck.py +1 -1
- package/md_cg/test_p29_session_ingest_export.py +1 -1
- package/md_cg/test_p2_mcp.py +7 -1
- package/md_cg/test_p38_contextualize.py +1 -1
- package/md_cg/test_p39_vision_evidence.py +1 -1
- package/md_cg/test_p40_refine_worklist.py +1 -1
- package/md_cg/test_p41_evolve_patrol.py +1 -1
- package/md_cg/test_p42_provenance.py +1 -1
- package/md_cg/test_p43_pooling.py +41 -13
- package/md_cg/test_read_clip.py +137 -0
- package/md_cg/test_review_conformance.py +310 -0
- package/md_cg/test_tasks.py +409 -0
- package/md_cg/test_tool_face.py +189 -0
- package/md_cg/test_twophase.py +286 -0
- package/md_cg/test_writepipe.py +210 -0
- package/md_cg/theory.py +1 -1
- package/md_cg/tokens.py +95 -5
- package/md_cg/tool_face.py +250 -0
- package/md_cg/twophase.py +221 -0
- package/md_cg/units.py +546 -0
- package/md_cg/vision_evidence.py +1 -1
- package/md_cg/weights.py +1 -1
- package/md_cg/whitebox.py +1 -1
- package/md_cg/whitebox_kb/data/verify_cache.json +28 -0
- package/md_cg/whitebox_kb/data/verify_savings.jsonl +46 -0
- package/md_cg/whitebox_kb/wisdom/audit_log/chain_heat.json +10 -10
- package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db-shm +0 -0
- package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db-wal +0 -0
- package/md_cg/writelimit.py +19 -3
- package/md_cg/writepipe.py +372 -0
- package/package.json +1 -1
- package/src/hooks.ts +10 -1
- package/src/lib/prompt_safety.ts +62 -0
- package/zcode/AGENTS.md +195 -185
- package/docs//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +0 -221
- /package/docs/{AGI → eval/AGI}/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v1.0.md" +0 -0
- /package/docs/{AGI → eval/AGI}/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v2.0.md" +0 -0
- /package/docs/{ → eval/}/345/256/236/351/252/214/346/226/271/346/241/210_/345/255/246/344/271/240/351/227/255/347/216/257AB/344/270/216/346/250/252/350/257/204_v1.0.md" +0 -0
- /package/docs/{ → eval/}/346/250/252/350/257/204_/345/205/255/345/256/266100/351/242/230/344/270/255/350/213/261/345/217/214/346/237/245_v1.0.md" +0 -0
- /package/docs/{guardrail-charter.md → mdcg/guardrail-charter.md} +0 -0
- /package/docs/{lingshu_tutorial.html → mdcg/lingshu_tutorial.html} +0 -0
- /package/docs/{memory-assessment.html → mdcg/memory-assessment.html} +0 -0
- /package/docs/{memory_score.html → mdcg/memory_score.html} +0 -0
- /package/docs/{memory_score.png → mdcg/memory_score.png} +0 -0
- /package/docs/{release_v0.3.0.md → mdcg/release_v0.3.0.md} +0 -0
- /package/docs/{release_v0.4.5.md → mdcg/release_v0.4.5.md} +0 -0
- /package/docs/{tool_table_v0.3.0.md → mdcg/tool_table_v0.3.0.md} +0 -0
- /package/docs/{ → mdcg/}/344/273/244/347/211/214/344/270/216/350/247/222/350/211/262/346/235/203/350/201/214/345/210/206/347/246/273_v0.1.md" +0 -0
- /package/docs/{ → mdcg/}/347/201/265/346/236/24282/345/267/245/345/205/267_/345/212/237/350/203/275/346/225/264/347/220/206/344/270/216/350/277/201/347/247/273/346/230/240/345/260/204_v0.1.md" +0 -0
- /package/docs/{ → mdcg/}/347/201/265/346/236/242MCP/345/267/245/345/205/267/346/200/273/350/241/250_v3.4.md" +0 -0
- /package/docs/{ → mdcg/}/347/201/265/346/236/242_/350/207/252/346/210/221/345/261/202/345/256/232/344/271/211.md" +0 -0
- /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_MD/347/233/256/345/275/225/346/226/271/346/241/210_v0.1.md" +0 -0
- /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_/346/235/241/344/273/266/347/251/272/351/227/264/345/220/210/346/210/220/344/270/216/347/224/237/346/225/210/346/235/241/344/273/266/345/217/243/345/276/204_v0.1.md" +0 -0
- /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276/344/275/234/344/270/272/350/256/260/345/277/206/346/223/215/344/275/234/347/263/273/347/273/237_/350/257/204/344/274/260/344/270/216/350/267/257/347/272/277_v0.1.md" +0 -0
- /package/docs/{ → plans/}/345/221/275/345/220/215/346/262/273/347/220/206_/351/241/271/347/233/256/350/256/241/345/210/222.md" +0 -0
- /package/docs/{ → plans/}/347/237/245/350/257/206/345/233/276/350/260/261/351/241/271/347/233/256_/344/272/244/346/216/245/346/226/207/346/241/243_20260914.md" +0 -0
- /package/docs/{ → swarm/}/350/234/202/347/276/244/345/244/232/346/231/272/350/203/275/344/275/223_/344/275/277/347/224/250/346/212/245/345/221/212_20260913.md" +0 -0
- /package/docs/{ → swarm/}/350/234/202/347/276/244/345/244/232/346/231/272/350/203/275/344/275/223_/345/212/237/350/203/275/350/257/264/346/230/216_v0.6.md" +0 -0
- /package/docs/{ → swarm/}/350/234/202/347/276/244/347/233/262/345/214/272/346/240/207/350/256/260_v1.0.md" +0 -0
- /package/docs/{ → theory/}/344/277/241/346/201/257/345/267/256/344/270/272/344/273/200/344/271/210/345/277/205/347/204/266/345/255/230/345/234/250/344/270/224/350/207/252/347/204/266/346/211/251/345/244/247.md" +0 -0
- /package/docs/{ → theory/}/346/231/272/350/203/275/347/232/204/345/205/254/347/220/206/345/214/226/345/237/272/347/237/263.md" +0 -0
- /package/docs/{ → theory/}/346/231/272/350/203/275/347/232/204/350/256/244/347/237/245/350/277/207/347/250/213.md" +0 -0
- /package/docs/{ → theory/}/346/231/272/350/203/275/350/256/2723.4.md" +0 -0
- /package/docs/{ → theory/}/347/231/275/347/256/261/346/231/272/350/203/275/346/230/257/344/273/200/344/271/210/357/274/237.md" +0 -0
- /package/docs/{ → theory/}/347/231/275/347/256/261/346/231/272/350/203/275/347/263/273/345/210/227/302/267/347/254/254/344/272/224/347/257/207/357/274/232/350/256/251AI/347/234/237/346/255/243/350/243/205/344/270/212/350/256/260/345/277/206.md" +0 -0
package/md_cg/units.py
ADDED
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""units.py · 复核单元接线层:蜂巢优先 → 提示配置 → harness 端子代理降级。
|
|
3
|
+
|
|
4
|
+
定位(与 audit.py 同一条架构约束):**能力不在认知图内**。本模块不做智能判定,
|
|
5
|
+
只做三件事:选通道(probe/plan)→ 派发请求(submit/wait/run)→ 记账(_units.jsonl)。
|
|
6
|
+
|
|
7
|
+
使用者裁定(2026-09-16,第 17 条精神):编译器(ccgc)的复核**优先使用蜂巢的
|
|
8
|
+
反思单元 / 验证单元**(reflect / verify,与 crosscheck.REFLECT_UNIT/VERIFY_UNIT 同值,
|
|
9
|
+
不新造角色名);蜂巢不可用 → **提示使用者配置**(给出可照做的构建/启动/环境变量命令);
|
|
10
|
+
或经显式 allow_degrade=True 降级到 **harness 端子代理**(宿主 agent 派发,裁决经
|
|
11
|
+
crosscheck 同款 verdicts 通道回填)。理由:使用者的复核是昂贵的——重活交给可并行的
|
|
12
|
+
外部单元,agent 本体只做编排与记账。
|
|
13
|
+
|
|
14
|
+
蜂巢契约(真源 hive/README.md + hive/hive_mcp/mcp_server.py;**复制契约不 import**,
|
|
15
|
+
保持 md_cg 对 hive 零依赖,与 ccgc「同款语义就地实现防 import 环」惯例一致):
|
|
16
|
+
jobs 目录 = $HIVE_JOBS_DIR | <repo>/hive/jobs
|
|
17
|
+
serve 判活 = jobs/_serve.json 的 ts(毫秒)距今 < 5s (同 _serve_alive)
|
|
18
|
+
job 目录 = jobs/<job_id>/{spec.json,status.json,result.json,kill}
|
|
19
|
+
job_id = h<java_ms>_<uuid6> (同 _submit)
|
|
20
|
+
result.json = {"ok":true,"content":...} | {"ok":false,"error":...}
|
|
21
|
+
终态 = done | error | timeout | killed
|
|
22
|
+
拉起 serve = <repo>/hive/target/release/hive serve --jobs <jobs>(detached)
|
|
23
|
+
|
|
24
|
+
边界(诚实声明,不猜测):
|
|
25
|
+
· 本模块**不产生裁决**,只搬运;裁决由外部单元负责。
|
|
26
|
+
· 契约漂移:hive 侧协议若变更,本模块以「无 result / 超时」如实失败,不假装成功。
|
|
27
|
+
· 默认**不自动拉起 serve**(使用者裁定:不可用即提示配置);autostart=True 才拉起。
|
|
28
|
+
"""
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import json
|
|
32
|
+
import os
|
|
33
|
+
import re
|
|
34
|
+
import subprocess
|
|
35
|
+
import time
|
|
36
|
+
import uuid
|
|
37
|
+
|
|
38
|
+
HIVE, SUBAGENT, CONFIGURE = "hive", "subagent", "configure"
|
|
39
|
+
STATES = (HIVE, SUBAGENT, CONFIGURE)
|
|
40
|
+
|
|
41
|
+
REFLECT, VERIFY = "reflect", "verify" # 同 crosscheck.REFLECT_UNIT/VERIFY_UNIT
|
|
42
|
+
ROLES = (REFLECT, VERIFY)
|
|
43
|
+
|
|
44
|
+
SERVE_FILE, SPEC_FILE = "_serve.json", "spec.json"
|
|
45
|
+
STATUS_FILE, RESULT_FILE, KILL_FILE = "status.json", "result.json", "kill"
|
|
46
|
+
LOG_NAME = "_units.jsonl"
|
|
47
|
+
TERMINAL_STATES = ("done", "error", "timeout", "killed")
|
|
48
|
+
FRESH_S, DEFAULT_TIMEOUT_S, DEFAULT_POLL_S = 5.0, 120, 1.0
|
|
49
|
+
DEFAULT_MAX_TOKENS = 2048
|
|
50
|
+
|
|
51
|
+
ENV_JOBS_DIR, ENV_EXE = "HIVE_JOBS_DIR", "HIVE_EXE"
|
|
52
|
+
ENV_MODEL, ENV_API_KEY = "MDCG_UNIT_MODEL", "HIVE_API_KEY"
|
|
53
|
+
|
|
54
|
+
ACCEPT, REJECT, DEFER, BLINDSPOT = "ACCEPT", "REJECT", "DEFER", "BLINDSPOT"
|
|
55
|
+
_VERDICT_MAP = {
|
|
56
|
+
"accept": ACCEPT, "accepted": ACCEPT, "keep": ACCEPT, "pass": ACCEPT,
|
|
57
|
+
"ok": ACCEPT, "approve": ACCEPT,
|
|
58
|
+
"drop": REJECT, "reject": REJECT, "rejected": REJECT, "deny": REJECT, "fail": REJECT,
|
|
59
|
+
"defer": DEFER, "deferred": DEFER, "unsure": DEFER, "unclear": DEFER, "skip": DEFER,
|
|
60
|
+
"blindspot": BLINDSPOT, "blind": BLINDSPOT,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def repo_root() -> str:
|
|
65
|
+
"""仓库根(md_cg 的上一级);不用 cwd——cwd 由宿主决定,不可作判据。"""
|
|
66
|
+
return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def jobs_dir(explicit: str = "") -> str:
|
|
70
|
+
d = explicit or os.environ.get(ENV_JOBS_DIR) or os.path.join(repo_root(), "hive", "jobs")
|
|
71
|
+
return os.path.abspath(d)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def exe_path() -> str:
|
|
75
|
+
exe = os.environ.get(ENV_EXE)
|
|
76
|
+
if exe:
|
|
77
|
+
return exe
|
|
78
|
+
return os.path.join(repo_root(), "hive", "target", "release",
|
|
79
|
+
"hive.exe" if os.name == "nt" else "hive")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def model_name(explicit: str = "") -> str:
|
|
83
|
+
"""复核模型(LLM 委托型执行器的 spec.model 必填)。"""
|
|
84
|
+
return (explicit or os.environ.get(ENV_MODEL) or "").strip()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def serve_state(jobs: str = "", fresh_s: float = FRESH_S) -> dict:
|
|
88
|
+
"""serve 判活:只认 _serve.json 心跳新鲜度(同 _serve_alive),不试端口/进程名。"""
|
|
89
|
+
jd = jobs_dir(jobs)
|
|
90
|
+
p = os.path.join(jd, SERVE_FILE)
|
|
91
|
+
out = {"jobs_dir": jd, "heartbeat": p, "exists": os.path.isfile(p),
|
|
92
|
+
"alive": False, "age_s": None, "pid": None, "reason": ""}
|
|
93
|
+
if not out["exists"]:
|
|
94
|
+
out["reason"] = "未见 _serve.json 心跳(serve 未启动或 jobs 目录不对)"
|
|
95
|
+
return out
|
|
96
|
+
try:
|
|
97
|
+
with open(p, encoding="utf-8") as f:
|
|
98
|
+
hb = json.load(f)
|
|
99
|
+
except (OSError, ValueError) as exc:
|
|
100
|
+
out["reason"] = "心跳不可读:%s: %s" % (type(exc).__name__, exc)
|
|
101
|
+
return out
|
|
102
|
+
age = time.time() - ((hb.get("ts") or 0) / 1000.0)
|
|
103
|
+
out.update({"age_s": round(age, 3), "pid": hb.get("pid"), "raw": hb})
|
|
104
|
+
out["alive"] = age < float(fresh_s)
|
|
105
|
+
out["reason"] = "" if out["alive"] else "心跳过期 %.1fs(阈值 %ss)——serve 可能已退出" % (
|
|
106
|
+
age, fresh_s)
|
|
107
|
+
return out
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def setup_hint(jobs: str = "", reason: str = "") -> str:
|
|
111
|
+
"""不可用时给使用者看的**配置指引**(含降级指引)——提示即责任,须可照着做。"""
|
|
112
|
+
jd = jobs_dir(jobs)
|
|
113
|
+
return ("蜂巢不可用:%s\n"
|
|
114
|
+
" [A 配置蜂巢(推荐,第 17 条)]\n"
|
|
115
|
+
" 1) cd %s && cargo build --release\n"
|
|
116
|
+
" 2) set %s=<LLM密钥>\n 3) set %s=<复核模型名>\n"
|
|
117
|
+
" 4) 启动:%s serve --jobs \"%s\"(或 hive_spawn 自动拉起)\n"
|
|
118
|
+
" 判活判据:%s 的 ts 距今 < %ss\n"
|
|
119
|
+
" [B 降级 harness 端子代理]\n"
|
|
120
|
+
" 重发本请求并带 allow_degrade=true:本模块返回复核请求包(prompt),\n"
|
|
121
|
+
" 由宿主子代理执行后经 verdicts 回填裁决。"
|
|
122
|
+
% (reason or "serve 未存活", os.path.dirname(exe_path()), ENV_API_KEY,
|
|
123
|
+
ENV_MODEL, exe_path(), jd, os.path.join(jd, SERVE_FILE), int(FRESH_S)))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def probe(jobs: str = "", model: str = "", fresh_s: float = FRESH_S,
|
|
127
|
+
allow_degrade: bool = False, channel: str = "") -> dict:
|
|
128
|
+
"""三级能力探测:hive(可派发)→ configure(提示配置)→ subagent(显式降级)。
|
|
129
|
+
|
|
130
|
+
判据只认**心跳 + 模型名**两项硬前提;缺任一即如实降级,绝不用相似度
|
|
131
|
+
(如「hive 目录存在」)冒充可用(资格由条件证据裁决,不由相似度裁决)。
|
|
132
|
+
"""
|
|
133
|
+
st = serve_state(jobs, fresh_s)
|
|
134
|
+
mdl = model_name(model)
|
|
135
|
+
if st["alive"] and mdl:
|
|
136
|
+
return {"state": HIVE, "transport": HIVE, "model": mdl, "jobs_dir": st["jobs_dir"],
|
|
137
|
+
"serve": st, "channel": "", "hint": ""}
|
|
138
|
+
why = st["reason"] if not st["alive"] else "serve 存活但未配置复核模型(%s)" % ENV_MODEL
|
|
139
|
+
if allow_degrade:
|
|
140
|
+
return {"state": SUBAGENT, "transport": SUBAGENT, "model": mdl,
|
|
141
|
+
"jobs_dir": st["jobs_dir"], "serve": st,
|
|
142
|
+
"channel": channel or "harness-subagent",
|
|
143
|
+
"hint": "已按 allow_degrade 降级到 harness 端子代理:请把 prompt 交给宿主子代理,"
|
|
144
|
+
"裁决经 verdicts 回填(蜂巢不可用原因:%s)" % why}
|
|
145
|
+
return {"state": CONFIGURE, "transport": None, "model": mdl,
|
|
146
|
+
"jobs_dir": st["jobs_dir"], "serve": st, "channel": "",
|
|
147
|
+
"hint": setup_hint(jobs, why)}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def plan(jobs: str = "", model: str = "", fresh_s: float = FRESH_S,
|
|
151
|
+
allow_degrade: bool = False, channel: str = "") -> dict:
|
|
152
|
+
"""优先级链自描述(供 cg op=ccg action=units 与审计查看)。"""
|
|
153
|
+
p = probe(jobs, model, fresh_s, allow_degrade, channel)
|
|
154
|
+
return {"state": p["state"], "transport": p["transport"], "model": p["model"],
|
|
155
|
+
"jobs_dir": p["jobs_dir"], "serve": p["serve"], "channel": p["channel"],
|
|
156
|
+
"chain": [
|
|
157
|
+
{"order": 1, "transport": HIVE, "action": "派发 reflect/verify 单元并等待 result.json",
|
|
158
|
+
"when": "jobs/_serve.json 心跳 < %ss 且 %s 已配置" % (int(FRESH_S), ENV_MODEL)},
|
|
159
|
+
{"order": 2, "transport": CONFIGURE, "action": "返回配置指引(不自动拉起、不假装通过)",
|
|
160
|
+
"when": "蜂巢不可用"},
|
|
161
|
+
{"order": 3, "transport": SUBAGENT, "action": "返回复核请求包,由 harness 端子代理执行并回填",
|
|
162
|
+
"when": "调用方显式 allow_degrade=True"},
|
|
163
|
+
],
|
|
164
|
+
"hint": p["hint"]}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# ---------------------------------------------------------------- 派发与收取
|
|
168
|
+
|
|
169
|
+
def _job_id() -> str:
|
|
170
|
+
"""同 _submit:serve 侧按 'h' 前缀识别任务目录。"""
|
|
171
|
+
return "h%d_%s" % (int(time.time() * 1000), uuid.uuid4().hex[:6])
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _log(cg, rec: dict) -> None:
|
|
175
|
+
"""留痕 _units.jsonl(对齐 _crosscheck.jsonl / _backfill.jsonl 纪律)。"""
|
|
176
|
+
root = getattr(cg, "root", None) or getattr(getattr(cg, "cg", None), "root", None)
|
|
177
|
+
if not root:
|
|
178
|
+
return
|
|
179
|
+
try:
|
|
180
|
+
rec = dict(rec)
|
|
181
|
+
rec.setdefault("ts", int(time.time() * 1000))
|
|
182
|
+
with open(os.path.join(str(root), LOG_NAME), "a", encoding="utf-8") as f:
|
|
183
|
+
f.write(json.dumps(rec, ensure_ascii=False) + "\n")
|
|
184
|
+
except OSError:
|
|
185
|
+
pass
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def submit(*, prompt: str, role: str = REFLECT, model: str = "", system_prompt: str = "",
|
|
189
|
+
context_files=None, timeout_s: int = DEFAULT_TIMEOUT_S,
|
|
190
|
+
max_tokens: int = DEFAULT_MAX_TOKENS, temperature=None, jobs: str = "",
|
|
191
|
+
actor: str = "", extra: dict = None, cg=None) -> dict:
|
|
192
|
+
"""写一条 LLM 委托型 job(spec.json + status.json,形态同 _submit);只写文件不起进程。"""
|
|
193
|
+
if role not in ROLES:
|
|
194
|
+
return {"ok": False, "error": "未知复核角色:%r(可选 %s)" % (role, list(ROLES))}
|
|
195
|
+
mdl = model_name(model)
|
|
196
|
+
if not mdl:
|
|
197
|
+
return {"ok": False, "error": "未配置复核模型:请设 %s 或显式传 model" % ENV_MODEL}
|
|
198
|
+
if not str(prompt or "").strip():
|
|
199
|
+
return {"ok": False, "error": "prompt 为空"}
|
|
200
|
+
jd, job_id = jobs_dir(jobs), _job_id()
|
|
201
|
+
d = os.path.join(jd, job_id)
|
|
202
|
+
spec = {"model": mdl, "user_prompt": str(prompt), "system_prompt": str(system_prompt or ""),
|
|
203
|
+
"timeout_s": int(timeout_s), "max_tokens": int(max_tokens),
|
|
204
|
+
"role": role, "unit_role": role}
|
|
205
|
+
if context_files:
|
|
206
|
+
spec["context_files"] = list(context_files)
|
|
207
|
+
if temperature is not None:
|
|
208
|
+
spec["temperature"] = temperature
|
|
209
|
+
if extra:
|
|
210
|
+
spec.update(extra)
|
|
211
|
+
try:
|
|
212
|
+
os.makedirs(d, exist_ok=True)
|
|
213
|
+
with open(os.path.join(d, SPEC_FILE), "w", encoding="utf-8") as f:
|
|
214
|
+
json.dump(spec, f, ensure_ascii=False)
|
|
215
|
+
with open(os.path.join(d, STATUS_FILE), "w", encoding="utf-8") as f:
|
|
216
|
+
json.dump({"job_id": job_id, "state": "pending",
|
|
217
|
+
"created_ts": int(time.time() * 1000), "started_ts": None,
|
|
218
|
+
"heartbeat_ts": None, "elapsed_s": 0.0, "timeout_s": int(timeout_s),
|
|
219
|
+
"model": mdl, "pid": None, "error": None}, f, ensure_ascii=False)
|
|
220
|
+
except OSError as exc:
|
|
221
|
+
return {"ok": False, "job_dir": d,
|
|
222
|
+
"error": "job 写入失败:%s: %s" % (type(exc).__name__, exc)}
|
|
223
|
+
_log(cg, {"action": "submit", "job_id": job_id, "role": role, "model": mdl,
|
|
224
|
+
"timeout_s": int(timeout_s), "actor": actor, "prompt_head": str(prompt)[:200]})
|
|
225
|
+
return {"ok": True, "job_id": job_id, "job_dir": d, "spec": spec,
|
|
226
|
+
"unit_role": role, "model": mdl}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def poll(job_id: str, jobs: str = "") -> dict:
|
|
230
|
+
"""读 job 终态视图:**以 result.json 出现为终态主判据**,status.json 仅作辅助。"""
|
|
231
|
+
d = os.path.join(jobs_dir(jobs), str(job_id or ""))
|
|
232
|
+
out = {"job_id": job_id, "job_dir": d, "state": "missing", "terminal": False,
|
|
233
|
+
"ok": False, "content": None, "error": None, "status": None}
|
|
234
|
+
if not os.path.isdir(d):
|
|
235
|
+
out["error"] = "job 目录不存在:%s" % d
|
|
236
|
+
return out
|
|
237
|
+
sp = os.path.join(d, STATUS_FILE)
|
|
238
|
+
if os.path.isfile(sp):
|
|
239
|
+
try:
|
|
240
|
+
with open(sp, encoding="utf-8") as f:
|
|
241
|
+
out["status"] = json.load(f)
|
|
242
|
+
except (OSError, ValueError):
|
|
243
|
+
out["status"] = None
|
|
244
|
+
st = (out["status"] or {}).get("state")
|
|
245
|
+
if st:
|
|
246
|
+
out.update({"state": st, "terminal": st in TERMINAL_STATES})
|
|
247
|
+
rp = os.path.join(d, RESULT_FILE)
|
|
248
|
+
if os.path.isfile(rp):
|
|
249
|
+
out["state"] = st or "done"
|
|
250
|
+
out["terminal"] = True
|
|
251
|
+
try:
|
|
252
|
+
with open(rp, encoding="utf-8") as f:
|
|
253
|
+
res = json.load(f)
|
|
254
|
+
except (OSError, ValueError) as exc:
|
|
255
|
+
out["error"] = "result.json 不可读:%s" % type(exc).__name__
|
|
256
|
+
return out
|
|
257
|
+
if isinstance(res, dict):
|
|
258
|
+
out["ok"] = bool(res.get("ok"))
|
|
259
|
+
out["content"] = res.get("content")
|
|
260
|
+
out["error"] = res.get("error") or out["error"]
|
|
261
|
+
out["usage"] = res.get("usage")
|
|
262
|
+
out["model"] = res.get("model")
|
|
263
|
+
else:
|
|
264
|
+
out["ok"], out["content"] = True, res
|
|
265
|
+
return out
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def wait(job_id: str, *, jobs: str = "", timeout_s: float = DEFAULT_TIMEOUT_S,
|
|
269
|
+
poll_s: float = DEFAULT_POLL_S) -> dict:
|
|
270
|
+
"""阻塞等终态;超时如实返回(不假装成功、不强杀 job)。"""
|
|
271
|
+
t0 = time.time()
|
|
272
|
+
while True:
|
|
273
|
+
cur = poll(job_id, jobs)
|
|
274
|
+
if cur.get("terminal"):
|
|
275
|
+
cur["waited_s"] = round(time.time() - t0, 3)
|
|
276
|
+
return cur
|
|
277
|
+
if (time.time() - t0) >= float(timeout_s):
|
|
278
|
+
cur.update({"terminal": False, "timeout": True, "waited_s": round(time.time() - t0, 3),
|
|
279
|
+
"error": "等待超时 %.0fs(job 仍在 %s)" % (float(timeout_s),
|
|
280
|
+
cur.get("state"))})
|
|
281
|
+
return cur
|
|
282
|
+
time.sleep(float(poll_s))
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def run(*, prompt: str, role: str = REFLECT, cg=None, actor: str = "",
|
|
286
|
+
wait_s: float = DEFAULT_TIMEOUT_S, **kw) -> dict:
|
|
287
|
+
"""submit + wait 组合(阻塞式复核,MCP action=review 主路径)。"""
|
|
288
|
+
sub = submit(prompt=prompt, role=role, cg=cg, actor=actor, **kw)
|
|
289
|
+
if not sub.get("ok"):
|
|
290
|
+
return {"ok": False, "stage": "submit", "error": sub.get("error"), **sub}
|
|
291
|
+
res = wait(sub["job_id"], jobs=kw.get("jobs", ""), timeout_s=wait_s)
|
|
292
|
+
res.update({"stage": "wait", "job_id": sub["job_id"], "unit_role": role,
|
|
293
|
+
"model": sub.get("model")})
|
|
294
|
+
res["ok"] = bool(res.get("ok")) and bool(res.get("content"))
|
|
295
|
+
return res
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
# ---------------------------------------------------------------- 裁决解析与搬运
|
|
299
|
+
|
|
300
|
+
_FENCE_RE = re.compile(r"```(?:json)?\s*(.+?)\s*```", re.S)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _json_of(raw):
|
|
304
|
+
"""从单元输出抽第一个 JSON 对象/数组(容忍 ```json 围栏与前后噪声)。"""
|
|
305
|
+
if isinstance(raw, (dict, list)):
|
|
306
|
+
return raw
|
|
307
|
+
text = str(raw or "").strip()
|
|
308
|
+
if not text:
|
|
309
|
+
return None
|
|
310
|
+
cands = []
|
|
311
|
+
m = _FENCE_RE.search(text)
|
|
312
|
+
if m:
|
|
313
|
+
cands.append(m.group(1))
|
|
314
|
+
cands.append(text)
|
|
315
|
+
for opener, closer in (("{", "}"), ("[", "]")):
|
|
316
|
+
i, j = text.find(opener), text.rfind(closer)
|
|
317
|
+
if 0 <= i < j:
|
|
318
|
+
cands.append(text[i:j + 1])
|
|
319
|
+
for c in cands:
|
|
320
|
+
try:
|
|
321
|
+
v = json.loads(c)
|
|
322
|
+
except ValueError:
|
|
323
|
+
continue
|
|
324
|
+
if isinstance(v, (dict, list)):
|
|
325
|
+
return v
|
|
326
|
+
return None
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def verdict_of(raw) -> dict:
|
|
330
|
+
"""单元输出 → 裁决四态;解析不出 → DEFER(不猜测、不当通过)。"""
|
|
331
|
+
data = _json_of(raw)
|
|
332
|
+
if isinstance(data, list):
|
|
333
|
+
data = data[0] if data and isinstance(data[0], dict) else None
|
|
334
|
+
if not isinstance(data, dict):
|
|
335
|
+
return {"verdict": DEFER, "reason": "单元输出无法解析为裁决 JSON",
|
|
336
|
+
"slot_corrections": {}, "raw": str(raw)[:500], "parsed": False}
|
|
337
|
+
word = str(data.get("verdict") or data.get("decision") or data.get("state")
|
|
338
|
+
or data.get("result") or "").strip().lower()
|
|
339
|
+
reason = str(data.get("reason") or data.get("why") or data.get("detail") or "").strip()
|
|
340
|
+
corr = data.get("slot_corrections") or data.get("corrections") or {}
|
|
341
|
+
corr = corr if isinstance(corr, dict) else {}
|
|
342
|
+
got = _VERDICT_MAP.get(word)
|
|
343
|
+
if got is None:
|
|
344
|
+
return {"verdict": DEFER, "slot_corrections": corr, "parsed": True, "raw": str(raw)[:500],
|
|
345
|
+
"reason": "单元未给出可识别的裁决(%r)%s" % (word, (":" + reason) if reason else "")}
|
|
346
|
+
return {"verdict": got, "reason": reason, "slot_corrections": corr,
|
|
347
|
+
"checks": data.get("checks"), "raw": str(raw)[:500], "parsed": True}
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def transport_name(state: str, role: str, job_id: str = "", channel: str = "") -> str:
|
|
351
|
+
"""裁决来源标识:**结构上不可能等于编译者**,保证 A 裁定「不得自证」不被误伤。"""
|
|
352
|
+
if state == HIVE:
|
|
353
|
+
return "hive:%s:%s" % (role, job_id or "unknown")
|
|
354
|
+
if state == SUBAGENT:
|
|
355
|
+
return "subagent:%s:%s" % (role, channel or "harness")
|
|
356
|
+
return ""
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def to_attest_args(unit: dict, *, state: str = "", role: str = REFLECT, job_id: str = "",
|
|
360
|
+
channel: str = "", compiled_by: str = "") -> dict:
|
|
361
|
+
"""单元裁决 → ccgc.attest(...) 入参(纯函数,离线可测)。
|
|
362
|
+
|
|
363
|
+
诚实边界:只做搬运与命名;裁决正确性由外部单元负责,本模块不为其背书。
|
|
364
|
+
"""
|
|
365
|
+
unit = unit or {}
|
|
366
|
+
verdict = unit.get("verdict") or DEFER
|
|
367
|
+
verifier = transport_name(state, role, job_id, channel)
|
|
368
|
+
evidence = str(unit.get("reason") or "")
|
|
369
|
+
if unit.get("raw") and not evidence:
|
|
370
|
+
evidence = str(unit["raw"])[:300]
|
|
371
|
+
if job_id:
|
|
372
|
+
evidence = "%s(job=%s)" % (evidence, job_id)
|
|
373
|
+
args = {"verdict": verdict, "verifier": verifier, "evidence": evidence}
|
|
374
|
+
if unit.get("slot_corrections"):
|
|
375
|
+
args["slot_corrections"] = unit["slot_corrections"]
|
|
376
|
+
if compiled_by and verifier and verifier == compiled_by:
|
|
377
|
+
args["self_verify"] = True
|
|
378
|
+
return args
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
# ---------------------------------------------------------------- 复核请求包
|
|
382
|
+
|
|
383
|
+
_REFLECT_TPL = """你是认知图记忆节点的**反思单元**(reflect)。下面是一份由 ccgc 编译出的
|
|
384
|
+
CCG 六要素候选(功能名/生效条件/子功能/执行/验证方式/不适用条件)。请逐项反思:
|
|
385
|
+
|
|
386
|
+
1) 六要素是否齐备,是否都能在「对话记录」里找到字面依据;
|
|
387
|
+
2) 「生效条件」四槽(场景/时窗/对象/前置)是否与实际适用范围一致,有无过度泛化;
|
|
388
|
+
3) 「不适用条件」(拒绝域)是否漏掉了明显该拒的情形;
|
|
389
|
+
4) 若有问题,给出**最小修正**(只改该字段,不重写)。
|
|
390
|
+
|
|
391
|
+
编译产物(JSON):
|
|
392
|
+
{digest}
|
|
393
|
+
|
|
394
|
+
原始对话记录(真源,引用必须出自此处):
|
|
395
|
+
{dialog}
|
|
396
|
+
|
|
397
|
+
只输出一个 JSON 对象,不要任何解释文字:
|
|
398
|
+
{{"verdict":"accept|drop|defer","reason":"一句话依据","slot_corrections":{{}},
|
|
399
|
+
"checks":[{{"element":"功能名","ok":true,"note":""}}]}}"""
|
|
400
|
+
|
|
401
|
+
_VERIFY_TPL = """你是独立**验证单元**(verify)。对下面这份 CCG 六要素候选与反思单元的结论
|
|
402
|
+
逐条复核:来源是否真实可追溯、结论是否被对话记录支持、修正是否越权(不得新增事实)。
|
|
403
|
+
|
|
404
|
+
**你只能否决或存疑,不得新增主张、不得改写事实**(可给出 slot_corrections 修正已有槽位)。
|
|
405
|
+
|
|
406
|
+
候选(JSON):
|
|
407
|
+
{digest}
|
|
408
|
+
|
|
409
|
+
反思单元结论:
|
|
410
|
+
{reflect_rows}
|
|
411
|
+
|
|
412
|
+
对话记录(真源):
|
|
413
|
+
{dialog}
|
|
414
|
+
|
|
415
|
+
只输出一个 JSON 对象:
|
|
416
|
+
{{"verdict":"accept|drop|defer","reason":"一句话依据","slot_corrections":{{}}}}"""
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _digest_of(digest) -> str:
|
|
420
|
+
if digest is None:
|
|
421
|
+
return "{}"
|
|
422
|
+
if isinstance(digest, str):
|
|
423
|
+
return digest
|
|
424
|
+
for attr in ("to_dict", "as_dict"):
|
|
425
|
+
fn = getattr(digest, attr, None)
|
|
426
|
+
if callable(fn):
|
|
427
|
+
try:
|
|
428
|
+
return json.dumps(fn(), ensure_ascii=False, indent=1)
|
|
429
|
+
except Exception: # noqa: BLE001
|
|
430
|
+
break
|
|
431
|
+
if isinstance(digest, dict):
|
|
432
|
+
return json.dumps(digest, ensure_ascii=False, indent=1)
|
|
433
|
+
return json.dumps({k: v for k, v in vars(digest).items() if not k.startswith("_")},
|
|
434
|
+
ensure_ascii=False, indent=1, default=str)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def prompt_for(role: str, digest=None, *, dialog: str = "", reflect_rows="") -> str:
|
|
438
|
+
"""按角色生成复核请求包正文(reflect/verify 共用一处模板真源)。"""
|
|
439
|
+
tpl = _VERIFY_TPL if role == VERIFY else _REFLECT_TPL
|
|
440
|
+
rows = reflect_rows if isinstance(reflect_rows, str) else json.dumps(
|
|
441
|
+
reflect_rows or [], ensure_ascii=False, indent=1)
|
|
442
|
+
return tpl.format(digest=_digest_of(digest), dialog=str(dialog or "(未提供)")[:6000],
|
|
443
|
+
reflect_rows=rows[:3000])
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def review(*, prompt: str = "", role: str = REFLECT, digest=None, dialog: str = "",
|
|
447
|
+
reflect_rows="", node_id: str = "", jobs: str = "", model: str = "",
|
|
448
|
+
timeout_s: int = DEFAULT_TIMEOUT_S, allow_degrade: bool = False,
|
|
449
|
+
channel: str = "", wait_s: float = DEFAULT_TIMEOUT_S, blocking: bool = True,
|
|
450
|
+
cg=None, actor: str = "", autostart: bool = False) -> dict:
|
|
451
|
+
"""复核主入口:**蜂巢优先 → 提示配置 → 端子代理降级**(三态各自如实返回)。
|
|
452
|
+
|
|
453
|
+
返回统一形态:{state, role, node_id, transport, unit, attest, prompt, job_id, hint}
|
|
454
|
+
- state=hive :已派发;blocking=True 时等终态并给出 unit/attest;否则只给 job_id
|
|
455
|
+
- state=configure:**未派发**,hint 为配置指引(使用者裁定:不可用即提示配置)
|
|
456
|
+
- state=subagent :**未派发**,返回 prompt 包供宿主子代理执行并回填
|
|
457
|
+
"""
|
|
458
|
+
text = prompt or prompt_for(role, digest, dialog=dialog, reflect_rows=reflect_rows)
|
|
459
|
+
pr = probe(jobs, model, allow_degrade=allow_degrade, channel=channel)
|
|
460
|
+
base = {"role": role, "node_id": node_id, "prompt": text,
|
|
461
|
+
"transport": None, "unit": None, "attest": None,
|
|
462
|
+
"job_id": None, "hint": pr.get("hint") or "", "channel": pr.get("channel") or "",
|
|
463
|
+
"degrade": {"channel": channel or "harness-subagent",
|
|
464
|
+
"how": "宿主子代理执行 prompt → 裁决经 verdicts 回填 attest"}}
|
|
465
|
+
if pr["state"] == SUBAGENT:
|
|
466
|
+
return {"state": SUBAGENT, **base,
|
|
467
|
+
"degrade": {"available": True, "channel": channel or "harness-subagent",
|
|
468
|
+
"how": "把 prompt 交给宿主子代理,取其 JSON 裁决后调 "
|
|
469
|
+
"cg(op=ccg, action=attest, verdict=<accept|drop|defer>, "
|
|
470
|
+
"verifier=subagent:<role>:<channel>)"}}
|
|
471
|
+
if pr["state"] == CONFIGURE:
|
|
472
|
+
return {"state": CONFIGURE, **base,
|
|
473
|
+
"degrade": {"available": True, "channel": channel or "harness-subagent",
|
|
474
|
+
"how": "① 按 hint 配置蜂巢后重试;② 或带 allow_degrade=true 降级子代理"}}
|
|
475
|
+
if autostart and not serve_state(jobs)["alive"]:
|
|
476
|
+
pr["autostart"] = autostart_serve(jobs)
|
|
477
|
+
sub = submit(prompt=text, role=role, jobs=jobs, model=model, timeout_s=timeout_s,
|
|
478
|
+
cg=cg, actor=actor)
|
|
479
|
+
if not sub.get("ok"):
|
|
480
|
+
return {"state": CONFIGURE, **base, "hint": (pr.get("hint") or "") + "\n" + str(sub.get("error"))}
|
|
481
|
+
out = {"state": HIVE, **base, "job_id": sub["job_id"], "unit_role": role,
|
|
482
|
+
"model": sub.get("model")}
|
|
483
|
+
if not blocking:
|
|
484
|
+
return out
|
|
485
|
+
res = wait(sub["job_id"], jobs=jobs, timeout_s=wait_s)
|
|
486
|
+
unit = verdict_of(res.get("content")) if res.get("ok") else {
|
|
487
|
+
"verdict": DEFER, "reason": res.get("error") or "单元未返回可用结果",
|
|
488
|
+
"slot_corrections": {}, "parsed": False}
|
|
489
|
+
out["unit"] = unit
|
|
490
|
+
out["transport"] = transport_name(HIVE, role, sub["job_id"])
|
|
491
|
+
out["attest"] = to_attest_args(unit, state=HIVE, role=role, job_id=sub["job_id"])
|
|
492
|
+
out["result_ok"] = bool(res.get("ok"))
|
|
493
|
+
out["waited_s"] = res.get("waited_s")
|
|
494
|
+
if not res.get("ok"):
|
|
495
|
+
out["hint"] = "蜂巢 job 未产出有效结果:%s(裁决按 DEFER 处理,未假装通过)" % res.get("error")
|
|
496
|
+
_log(cg, {"action": "review", "job_id": sub["job_id"], "role": role,
|
|
497
|
+
"node_id": node_id, "actor": actor, "unit_verdict": unit.get("verdict"),
|
|
498
|
+
"result_ok": bool(res.get("ok"))})
|
|
499
|
+
return out
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
# ---------------------------------------------------------------- serve 拉起(显式)
|
|
503
|
+
|
|
504
|
+
def autostart_serve(jobs: str = "", wait_s: float = 5.0) -> dict:
|
|
505
|
+
"""**显式**拉起 serve(默认不启用;使用者裁定:不可用即提示配置)。
|
|
506
|
+
|
|
507
|
+
形态同 hive_mcp._ensure_serve:detached + 独立日志,不阻塞调用方。
|
|
508
|
+
"""
|
|
509
|
+
jd = jobs_dir(jobs)
|
|
510
|
+
if serve_state(jd)["alive"]:
|
|
511
|
+
return {"started": False, "note": "serve 存活"}
|
|
512
|
+
exe = exe_path()
|
|
513
|
+
if not os.path.isfile(exe):
|
|
514
|
+
return {"started": False, "note": "未找到可执行文件 %s——先 cargo build --release" % exe}
|
|
515
|
+
kwargs = {}
|
|
516
|
+
if os.name == "nt":
|
|
517
|
+
kwargs["creationflags"] = subprocess.DETACHED_PROCESS | subprocess.CREATE_NO_WINDOW
|
|
518
|
+
else:
|
|
519
|
+
kwargs["start_new_session"] = True
|
|
520
|
+
try:
|
|
521
|
+
os.makedirs(jd, exist_ok=True)
|
|
522
|
+
with open(os.path.join(jd, "_serve.log"), "ab") as logf:
|
|
523
|
+
subprocess.Popen([exe, "serve", "--jobs", jd], stdout=logf, stderr=logf,
|
|
524
|
+
stdin=subprocess.DEVNULL, **kwargs)
|
|
525
|
+
except OSError as exc:
|
|
526
|
+
return {"started": False, "note": "拉起失败:%s: %s" % (type(exc).__name__, exc)}
|
|
527
|
+
t0 = time.time()
|
|
528
|
+
while time.time() - t0 < float(wait_s):
|
|
529
|
+
if serve_state(jd)["alive"]:
|
|
530
|
+
return {"started": True, "note": "serve 已拉起"}
|
|
531
|
+
time.sleep(0.1)
|
|
532
|
+
return {"started": True, "note": "serve 已拉起(心跳未就绪,稍后自愈)"}
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def doctor(jobs: str = "") -> dict:
|
|
536
|
+
"""能力体检(形态对齐 hive_doctor):判活 + exe + 模型 + 优先级链。"""
|
|
537
|
+
jd = jobs_dir(jobs)
|
|
538
|
+
p = plan(jobs)
|
|
539
|
+
return {"ok": p["state"] == HIVE, "state": p["state"], "jobs_dir": jd,
|
|
540
|
+
"serve_alive": p["serve"]["alive"], "serve_age_s": p["serve"]["age_s"],
|
|
541
|
+
"exe_found": os.path.isfile(exe_path()), "exe_path": exe_path(),
|
|
542
|
+
"model_set": bool(model_name()), "model": p["model"],
|
|
543
|
+
"api_key_set": bool(os.environ.get(ENV_API_KEY)),
|
|
544
|
+
"env": {"HIVE_JOBS_DIR": os.environ.get(ENV_JOBS_DIR, ""),
|
|
545
|
+
"MDCG_UNIT_MODEL": os.environ.get(ENV_MODEL, "")},
|
|
546
|
+
"chain": p["chain"], "hint": p["hint"]}
|
package/md_cg/vision_evidence.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
裁定依据
|
|
5
5
|
--------
|
|
6
|
-
`docs/认知图_G4-G8缺口裁定单_v0.1.md` §三(四态 = ACCEPT,条件 = 脱敏 + 只读 AEIS)。
|
|
6
|
+
`docs/mdcg/认知图_G4-G8缺口裁定单_v0.1.md` §三(四态 = ACCEPT,条件 = 脱敏 + 只读 AEIS)。
|
|
7
7
|
缺口根因(库外只读观测):产出侧 `vision_pipeline.cg_ingest` 只取
|
|
8
8
|
`p.get("model_evidence")`,白箱 `geometry_parts` 部件不带该键 → 部件节点证据面
|
|
9
9
|
恒为 `{}`。即「证据口径未定义」,不是「没有证据」。
|
package/md_cg/weights.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
理论出处(本仓原文,非外部知识)
|
|
5
5
|
--------------------------------
|
|
6
|
-
`docs/智能的公理化基石.md` §4.3:信任合成是
|
|
6
|
+
`docs/theory/智能的公理化基石.md` §4.3:信任合成是
|
|
7
7
|
P_trust = f(一致性, 位置可预测性, 版本对齐度)
|
|
8
8
|
其中 `版本对齐度` 已由 `theory` + `links.version_alignment` 提供,
|
|
9
9
|
`位置可预测性` 见 `predict`,`一致性` 见 `consistency`。本模块只补一件
|
package/md_cg/whitebox.py
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
才回退外部 MCP stdio 子进程(legacy 路径)。
|
|
18
18
|
· 调用结果:验证结论写回认知图(self 层,tags 含 `whitebox:verify`),可追溯。
|
|
19
19
|
|
|
20
|
-
显式调用映射(详见 docs/功能调用映射表_v0.1.md)
|
|
20
|
+
显式调用映射(详见 docs/mdcg/功能调用映射表_v0.1.md)
|
|
21
21
|
------------------------------------------------
|
|
22
22
|
功能 → 代码 → MCP
|
|
23
23
|
白箱问答 → md_cg.whitebox.ask → cg(op=whitebox, action=ask)
|
|
@@ -1869,5 +1869,33 @@
|
|
|
1869
1869
|
"reason": "",
|
|
1870
1870
|
"verified_at": "2026-09-10 20:11:01",
|
|
1871
1871
|
"version": 6
|
|
1872
|
+
},
|
|
1873
|
+
"83a381e1f246fb91a048afe63cd6067b": {
|
|
1874
|
+
"ok": true,
|
|
1875
|
+
"checks": [
|
|
1876
|
+
{
|
|
1877
|
+
"level": "L1语法",
|
|
1878
|
+
"ok": true,
|
|
1879
|
+
"evidence": "语法通过(1 个函数定义)"
|
|
1880
|
+
},
|
|
1881
|
+
{
|
|
1882
|
+
"level": "L2样例",
|
|
1883
|
+
"ok": true,
|
|
1884
|
+
"evidence": "3 组样例全部通过"
|
|
1885
|
+
},
|
|
1886
|
+
{
|
|
1887
|
+
"level": "L3边界",
|
|
1888
|
+
"ok": true,
|
|
1889
|
+
"evidence": "无额外边界用例"
|
|
1890
|
+
},
|
|
1891
|
+
{
|
|
1892
|
+
"level": "规范符合性",
|
|
1893
|
+
"ok": true,
|
|
1894
|
+
"evidence": "结构规范 + 语义对齐 + 条件论注释通过"
|
|
1895
|
+
}
|
|
1896
|
+
],
|
|
1897
|
+
"reason": "",
|
|
1898
|
+
"verified_at": "2026-09-16 10:51:36",
|
|
1899
|
+
"version": 6
|
|
1872
1900
|
}
|
|
1873
1901
|
}
|