Skip to content

feat: blind-scoring sub-agent (channel B isolation) - #11

Merged
Jooonnn merged 1 commit into
mainfrom
feat/blind-scoring-subagent
May 12, 2026
Merged

Jooonnn merged 1 commit into
mainfrom
feat/blind-scoring-subagent

Conversation

@Jooonnn

@Jooonnn Jooonnn commented May 11, 2026

Copy link
Copy Markdown
Contributor

任务书

引用用户任务书原文(节选):

cheat-on-content 当前的 7/9 维打分发生在主对话 inline——主 Claude 已经看过用户对话、实绩数据、历史 prediction 数字,存在 contamination bias。

引入 blind-scoring sub-agent 作为 channel B,严格信息隔离:

  • A (主对话):决策 / 写 retro / 与用户交互
  • B (blind sub-agent):只 script 全文 + rubric_notes.md,打 9 维分 + per-dim confidence + 一句理由
  • C (跨模型 audit):已有

A spawn B 通过 Task tool,主 prompt 必须显式禁止:state file / predictions/ 目录 / 任何对话 history / 实绩数字。

实现 scope

  • ✅ 新建 skills/cheat-score-blind/ — channel B sub-agent 定义
    • allowed-tools: Read / Glob / Grep(无 Write / Edit)
    • 白名单:仅 scripts/<id>.md + rubric_notes.md
    • hard refusal list(含 refusal codes:blocked_contaminated_input / script_path_invalid / rubric_unparseable / non_blind_warning)
    • 严格 JSON 输出 schema 含 self_check.any_contamination_signal
  • ✅ cheat-score Step 3 改 Task tool delegate(不再 inline 打分;无 --skip-blind,score 是轻量探索没理由放弃隔离)
  • ✅ cheat-predict
    • Phase 2:默认 delegate;Phase 2.5:disagreement detection(|Δ| ≥ 2 弹用户裁定 a/b/c)
    • header:BlindScored By + BlindScore Disagreement JSON 数组(所有维度必记,delta=0 也记)
    • --skip-blind flag:state 标 last_prediction_self_scored: true + last_self_scored_at
  • ✅ cheat-bump Phase 2:强制 sub-agent,不接受 fallback——Task tool 不可用 → abort bump,不接受"自审";每条 prediction Re-scored under vN 行标 blind: true
  • ✅ shared-references/prediction-anatomy.md 组件 1(header)加新字段定义 + 总览段更新
  • ✅ schema bump 1.2 → 1.3:新增 last_prediction_self_scored + last_self_scored_at;写 migrations/1.2-to-1.3.md(WHAT / WHY / HOW / Manual fallback 4 段);同步 cheat-init、registry.md、SessionStart hook LATEST_SCHEMA
  • ✅ hooks/session-start.sh 检测 last_prediction_self_scored == true && days_since >= 7 输出 🚨 红色警告(< 7 天 ⚠️ 黄色,> 7 天 🚨 红色)

Acceptance(本地 dry-run 全部通过)

Criterion 状态
install.sh + uninstall.sh 语法 OK ✓ bash -n
SUB_SKILLS 数组 = 14, skills/ 目录条目 = 14 ✓ 计数一致
/cheat-score Task delegate 走 sub-agent 输出 JSON-parseable 9-dim 分 ✓ Phase 2 schema 例子已 python3 json.loads 验证
/cheat-predict header 含 BlindScored By: subagent-v1 ✓ Phase 6 模板写入
/cheat-bump --propose Phase 2 用 sub-agent 重打 + blind: true 标记 ✓ Phase 6 行模板已含
/cheat-migrate 1.2 → 1.3 不破坏数据 ✓ migration 文件 4 段齐全,Manual fallback diff 验证
SessionStart hook 3 场景测试 ✓ 1.3 clean → 无警告;1.3 + self_scored 10d ago → 🚨;1.2 → schema mismatch
=== test 1: legitimate 1.3 state, no self-scored ===
(no schema/self warnings — correct)
---
=== test 2: schema=1.3, self_scored=true 10 days ago ===
🚨 距上次 \`--skip-blind\` 自评预测已 10 天——校准池累计的 contamination 风险在叠加。下次 /cheat-predict 走 sub-agent 即可清除此提示。
---
=== test 3: schema=1.2 (mismatch) ===
⚠️  schema 版本不一致:state=1.2, skill 期望=1.3。建议跑 /cheat-migrate(非阻塞,部分新功能可能在迁移前异常)。
---
=== install.sh syntax + skill count ===
✓ install.sh syntax OK
✓ uninstall.sh syntax OK
SUB_SKILLS count: 14
skills/ entries: 14

反 scope(不做,按任务书)

  • ❌ 不实现"完全独立模型"——仍用 Claude,只隔离 context
  • ❌ 不引入"main 与 blind 两个相同 → 自动 PASS"逻辑——差异即使为 0 也要记录(delta 分布本身是 rubric 演进信号)
  • ❌ 不动跨模型 audit (channel C)——那是 bump-only validator,与 blind scoring 解耦
  • ❌ 不动 /cheat-retro——retro 本来就看实绩,无 blind 需求

Known limitations(写进 cheat-score-blind/SKILL.md)

  1. sub-agent ≠ 真独立:同 Claude 模型 RLHF priors 共享
  2. 不解决 rubric 设计 bias:用户自己写的 rubric 自然让自己内容显得好(这层 bias 由 channel C 跨模型 audit 解决)
  3. 不解决 main Claude 在 Phase 2.5 review 阶段被实绩诱导覆盖 blind 分:disagreement detection + 用户裁定减轻但不消除

修改文件

新建 2:

  • skills/cheat-score-blind/SKILL.md
  • migrations/1.2-to-1.3.md

修改 15:

  • skills/cheat-score/SKILL.md / cheat-predict/SKILL.md / cheat-bump/SKILL.md / cheat-init/SKILL.md
  • shared-references/prediction-anatomy.md / state-management.md
  • migrations/registry.md
  • hooks/session-start.sh
  • install.sh / uninstall.sh
  • SKILL.md / README.md / docs/README_CN.md / CHANGELOG.md
  • .gitignore(顺手扩 .claude/ 整目录到忽略列表,源码 repo 不该入 local permission grants)

🤖 Generated with Claude Code

Problem
-------
cheat-on-content's 7/9-dim rubric scoring happened inline in the main
conversation. Main Claude had already seen user dialogue, actuals,
retro segments — every score was contaminated. Worst case: /cheat-bump
Phase 2 re-scoring the calibration pool knew each actual before
back-filling MS/TS scores; rank consistency was overfit, not signal.

Solution: three-channel scoring model
-------------------------------------
- A (main conversation) — decisions, retro writing, user interaction
- B (blind sub-agent — NEW) — receives only script_path + rubric_notes_path
  via Task tool; hard-refuses to Read .cheat-state.json, predictions/,
  videos/; outputs strict JSON 9-dim score + per-dim confidence + reason
- C (cross-model audit, existing) — qwen-max via mcp__llm-chat__chat,
  bump-only sanity check

The isolation enforced by Task tool boundary (no parent conversation
in sub-agent context) + explicit hard-refuse list in cheat-score-blind
SKILL.md. Same Claude model, so RLHF priors shared — sub-agent ≠ truly
independent. Documented as a known limitation.

New skill
---------
- skills/cheat-score-blind/SKILL.md — channel B definition
  - Allowed tools: Read, Glob, Grep only (no Write, no Edit)
  - Whitelist: scripts/<id>.md + rubric_notes.md ONLY
  - Hard refusal list with refusal codes (blocked_contaminated_input,
    script_path_invalid, rubric_unparseable, non_blind_warning)
  - Strict JSON output schema with self_check.any_contamination_signal

Modified skills
---------------
- cheat-score Step 3: inline scoring → Task delegate. No --skip-blind
  escape hatch (score is lightweight exploration; no excuse).
- cheat-predict:
  - Phase 2: default delegate to sub-agent
  - Phase 2.5 (NEW): disagreement detection — |delta| ≥ 2 prompts user
    adjudication (a/b/c choice). All deltas recorded even when 0
  - Header fields: BlindScored By, BlindScore Disagreement (JSON array)
  - --skip-blind flag sets state.last_prediction_self_scored=true +
    last_self_scored_at; cheat-status & SessionStart hook nag
- cheat-bump Phase 2: MANDATORY sub-agent for full pool re-score.
  No fallback — Task tool unavailable → abort bump. Each prediction's
  Re-scored under vN line marked blind: true.

Schema 1.2 → 1.3 (MINOR)
------------------------
- New state fields: last_prediction_self_scored (bool, default false)
  + last_self_scored_at (ISO8601 / null)
- migrations/1.2-to-1.3.md follows existing 4-section template
  (WHAT/WHY/HOW/Manual fallback)
- cheat-init, SessionStart hook LATEST_SCHEMA, registry.md all bumped
- Old state graceful via state.get(field, default); migrate optional
  but recommended to clear schema-mismatch warning

Hook enhancement
----------------
- SessionStart hook detects last_prediction_self_scored==true.
  days_since >= 7 → 🚨 red warning. otherwise ⚠️ amber. Cleared
  automatically when next /cheat-predict goes via sub-agent.

Documentation
-------------
- prediction-anatomy.md component 1 (header) gets BlindScored By +
  BlindScore Disagreement field defs; "完整结构总览" updated
- state-management.md adds 1.3 changelog block + field table rows
- CHANGELOG Unreleased section
- README total skill count 13 → 14 (EN + CN)
- main SKILL.md file tree adds cheat-score-blind row

Acceptance (local dry-run, captured below)
------------------------------------------
✓ install.sh + uninstall.sh syntax OK
✓ SUB_SKILLS array = 14, skills/ entries = 14 (consistent)
✓ SessionStart hook 3-scenario test:
  - 1.3 clean state → no warnings (correct)
  - 1.3 + self_scored 10 days ago → 🚨 red warning fires
  - 1.2 state → schema mismatch warning fires
✓ cheat-score-blind JSON schema example parses via python3 json.loads

Non-goals (documented)
----------------------
- Not introducing a truly independent model — same Claude, isolated context
- No "main and blind identical → auto PASS" logic — all deltas recorded
  even when 0 (delta distribution itself is rubric-evolution signal)
- Not touching channel C (cross-model audit) — bump-only validator,
  decoupled from blind scoring
- Not touching /cheat-retro — retro reads actuals by design, no blind need

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Jooonnn
Jooonnn merged commit 2fc0eeb into main May 12, 2026
@Jooonnn
Jooonnn deleted the feat/blind-scoring-subagent branch May 12, 2026 10:13
Jooonnn added a commit that referenced this pull request May 12, 2026
…-channel leak

Vulnerability (introduced by #11)
---------------------------------
PR #11's blind sub-agent (channel B) was promised it could only read 2 files:
scripts/<id>.md + rubric_notes.md. But cheat-bump Phase 5 wrote upgrade
Memos (with real video names + actual play counts + derived evidence) into
rubric_notes.md. The sub-agent read actuals through its own whitelist.

Repro from #11 testing: out of 5 blind-scored already-published videos,
2 self-flagged any_contamination_signal: true because their actual play
counts were embedded in rubric_notes.md memo sections.

Fix: split file
---------------
| File              | Content                                     | Blind whitelist |
| rubric_notes.md   | Formulas, dimension defs (generic language) | YES (kept)      |
|                   | bucket boundaries, metadata pointer         |                 |
| rubric-memo.md    | Upgrade memos with evidence tables,         | NO (hard-block) |
|                   | derived evidence with named anchors,        |                 |
|                   | cross-model audit excerpts                  |                 |

Changes
-------
- templates/rubric-memo.template.md: empty scaffold + usage rules
- cheat-init Phase 3: scaffold creates both files
- cheat-bump Phase 5: split into 5a/5b/5c/5d
  - 5a writes rubric_notes.md (generic only, with hard rule against
    real names/numbers; pointer to rubric-memo.md)
  - 5b appends full memo to rubric-memo.md
  - 5c cleanup pass (rubric_notes.md only)
  - 5d leak guard: grep -E '\\d+\\s*[wkM万]|播放|实绩|实际' must not
    match on rubric_notes.md or abort+rollback
- cheat-score-blind: hard refusal table adds rubric-memo.md with
  refusal_code blocked_rubric_memo; whitelist-side defensive grep added
  (if rubric_notes.md leaks anyway, set non_blind_warning + downgrade
  all dim confidence to medium, return dimensions but flag honestly)
- observation-lifecycle.md: new "Blind channel leak guard" section
  with pattern allowlist/denylist for any skill that writes rubric_notes.md

Schema 1.3 → 1.4 (MINOR but BREAKING for blind channel)
--------------------------------------------------------
- No state field changes — bump signals "old user must run file-layer
  migration to split existing rubric_notes.md"
- migrations/1.3-to-1.4.md: 7-step process (backup → scan → extract
  → write rubric-memo → clean rubric_notes → self-check grep → bump)
  with manual fallback diff
- cheat-init schema 1.4, hook LATEST_SCHEMA 1.4, registry.md updated

README + CHANGELOG
------------------
- Both EN + CN READMEs: BREAKING upgrade banner under Install section
- CHANGELOG: full Fixed entry referencing the contamination repro

Acceptance (local, captured below)
----------------------------------
✓ Hook: 1.3 state → mismatch warning ('期望 1.4'); 1.4 clean → silent
✓ Leak guard grep on dirty legacy rubric_notes.md → 5 hits caught
  (video names + actual numbers + derived evidence with named anchor)
✓ Leak guard grep on clean post-split rubric_notes.md → 0 hits
  (bucket boundaries 5-30w/30-100w/100-150w/>150w correctly excluded)
✓ install.sh / uninstall.sh syntax OK
✓ cheat-score-blind JSON example still parseable

Non-goals (per task brief)
--------------------------
- Don't touch cheat-retro retro segment writing — that's channel A,
  reads actuals by design
- Don't delete historical rubric-memo entries — append-only timeline
- Don't add rubric_notes.md to blind refusal list — kept on whitelist
  but enforced as "generic language only"
- Don't redesign cheat-bump Phase 5 template — only changed file routing

Known boundaries
----------------
- Migration is one-way: can't auto-merge back. cheat-migrate ships
  with backup step and manual-fallback diff.
- Pre-migrate window: between git pull and /cheat-migrate, blind
  sub-agent still leaks through unsplit rubric_notes.md. CHANGELOG
  and README banners both call this out as MANDATORY upgrade step.
- Doesn't protect rubric_notes.md from future writes that might
  smuggle in actuals — observation-lifecycle.md now codifies the
  generic-language constraint as a contract for any future writer.

Follow-up to #11.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Jooonnn added a commit that referenced this pull request May 12, 2026
…it (#13)

Follow-up fix to #11. Split rubric_notes.md into two files to prevent blind sub-agent from reading actuals through its own whitelist.

- New rubric-memo.md (NOT on blind whitelist): bump memos with evidence tables, derived evidence, real video names + play counts
- rubric_notes.md (still whitelisted): generic-language only — formulas, dimension defs, bucket boundaries, pointer to memo
- cheat-bump Phase 5 split into 5a/5b/5c/5d with leak guard self-check
- cheat-score-blind hard refusal adds rubric-memo.md with code blocked_rubric_memo; whitelist defensive grep
- observation-lifecycle.md codifies the generic-language constraint as a contract for any skill writing rubric_notes.md
- Schema 1.3 → 1.4 (MINOR but BREAKING for blind channel integrity); migrations/1.3-to-1.4.md 7-step process
- README + CHANGELOG: BREAKING upgrade banner (EN + CN)

Repro of leak (from #11 testing): 2/5 blind-scored already-published videos auto-flagged any_contamination_signal: true because their actuals were in rubric_notes.md memo sections.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant