作成日: 2026-04-02
ステータス: Draft
目的: essential_hard / evidence_hard / soft / uncertain_evidence を持つ contract 設計の試験例
関連:
docs/capabilities/wiki/仕様書.mddocs/capabilities/implement/仕様書.mdIssue #10 で議論した「verifier の hard/soft 分割」を、実際の contract に落とした試験例。
抽象論ではなく、wiki Phase 1-a という具体的な task に対して
essential_hard / evidence_hard / soft / uncertain_evidence がどう機能するかを示す。
設計の良し悪しはこの例を見ながら判断する。
task_id: wiki-phase1a-implement-v1
intent: implement
target:
service: local
delegation_mode: known_only
requested_by: brain
requested_at: "2026-04-02T00:00:00Z"
constraints:
implicit_scope:
- repo:akira/butler2
require_approval_for: []
max_minutes: 30
# Brain が実装目標を渡す
payload:
goal: >
wiki Phase 1-a を実装する。
sync_docs_to_wiki の dry-run 最小経路のみを対象とする。
API 書き込みはこの task には含めない。
spec:
- path: docs/capabilities/wiki/仕様書.md
sections:
- "4. Intent 定義"
- "5. sync_docs_to_wiki の役割"
- "7. path mapping 方針"
- "8. Payload 仕様"
- "12. evidence 方針"
- "16. 実装段階 Phase 1"
target_paths:
- butler/maids/wiki_worker.py
- tests/test_wiki_non_llm_maid.py
- config/intent_maid_routes.yml
- config/maid_registry.yml
context_files:
- butler/runtime.py # intent route の解決方法を確認するため
- butler/maids/ # 既存 worker の命名・構造を参照するため
deliverables:
- code # butler/maids/wiki_worker.py
- tests # tests/test_wiki_non_llm_maid.py
- config # intent_maid_routes.yml / maid_registry.yml
notes: >
- WIKIJS_URL / WIKIJS_TOKEN は .env から読む経路を用意するだけでよい。API 疎通は不要。
- dry_run=true 時に candidate_files と mapped_pages が evidence で返れば十分。
- .bak ファイルは作業中に生じてもよいが、最終状態に残さないこと。
- 既存 worker の命名規則(maid_id は snake_case)に合わせること。
# ここが今回の設計ポイント: 3層の条件
verification:
# essential_hard: これが 1 つでも落ちれば clear-fail / retry 対象
# 「本質未達が明確」なケースのみを入れる
# 観測のしやすさより、機能の実現そのものを問う
hard_conditions:
essential:
- id: unit_tests_pass
type: test_command
command: "uv run python -m unittest tests.test_wiki_non_llm_maid -v"
description: >
wiki worker の単体テストが全件通ること。
SyntaxError / ImportError / assertion error は即 clear-fail。
- id: wiki_intent_routed
type: config_check
description: >
wiki intent で dry-run を実行したとき、route エラーで落ちないこと。
route が引けなければ何も動かないため本質条件とする。
# acceptance_test で代用:
command: >
uv run python -c "
from butler.runtime import execute_task;
from butler.models import TaskContract, Constraints;
task = TaskContract(task_id='test', intent='wiki',
target={'service': 'wiki'}, delegation_mode='known_only',
constraints=Constraints(implicit_scope=['repo:akira/butler2'], require_approval_for=[], max_minutes=5),
payload={'operation': 'sync_docs_to_wiki', 'repo_path': '.', 'repo_slug': 'butler2',
'committed_files': ['docs/test.md'], 'dry_run': True},
success_criteria=[], requested_by='test', requested_at='2026-04-02T00:00:00Z');
r = execute_task(task);
assert r.status != 'need_input' or 'route' not in r.summary, r
"
- id: dryrun_evidence_shape
type: output_check
description: >
dry_run=true 時に evidence が少なくとも triggered / candidate_files / mapped_pages
を含むこと。これが返らなければ Phase 1-a の完了条件を満たさない。
command: >
uv run python -c "
from butler.maids.wiki_worker import WikiWorker;
from butler.models import TaskContract, Constraints, MaidDefinition;
task = TaskContract(task_id='test', intent='wiki',
target={'service': 'wiki'}, delegation_mode='known_only',
constraints=Constraints(implicit_scope=['repo:akira/butler2'], require_approval_for=[], max_minutes=5),
payload={'operation': 'sync_docs_to_wiki', 'repo_path': '.', 'repo_slug': 'butler2',
'committed_files': ['docs/setup/install.md'], 'dry_run': True},
success_criteria=[], requested_by='test', requested_at='2026-04-02T00:00:00Z');
maid = MaidDefinition(maid_id='wiki_worker', kind='script', label='wiki worker',
provider='local_script', model='n/a', role='wiki_sync_ops', enabled=True,
launch_config={}, verification_profile={});
w = WikiWorker();
r = w.execute(task, maid);
assert r.evidence.get('triggered') is not None, r.evidence;
assert 'candidate_files' in r.evidence, r.evidence;
assert 'mapped_pages' in r.evidence, r.evidence
"
# evidence_hard: file/registry の観測条件
# essential_hard が通っていても evidence_hard が落ちる場合は uncertain 扱いにする
# (機能は動くが観測条件が噛み合わない → Brain review で判断)
# ここに入れるものは「route / import / registry の不整合を起こしうるもの」に限定する
evidence:
- id: wiki_worker_file_exists
type: file_exists
path: butler/maids/wiki_worker.py
description: >
worker ファイルが正しいパスに存在すること。
essential テストが通っていてもファイルパスが違えば import 不整合になりうる。
- id: wiki_worker_in_registry
type: config_entry
file: config/maid_registry.yml
key: maid_id
value: wiki_worker
description: >
maid_registry に wiki_worker が登録されていること。
登録がなければ runtime が worker を見つけられない。
- id: wiki_intent_in_routes
type: config_entry
file: config/intent_maid_routes.yml
key: intent
value: wiki
description: >
intent_maid_routes に wiki が存在すること。
これがなければ route 解決で落ちる。
# scope_guard: hard_conditions / soft_conditions とは独立した安全境界
# 違反は常に clear-fail。機能が正しく動いていても retry せず Brain に返す。
# (再試行しても同じ scope 違反が繰り返されるだけのため)
scope_guard:
- id: no_scope_violation
type: scope_check
description: >
implicit_scope 外のファイルが変更されていないこと。
Git 差分から Butler が機械的に検査する。違反があれば即 clear-fail とし、retry しない。
# soft_conditions: 失敗しても uncertain 扱い(retry しない)
# cleanup 系・命名揺れ・スタイルを入れる
soft_conditions:
- id: no_bak_files
type: file_absent
pattern: "config/*.bak"
description: >
作業中の .bak が最終状態に残っていないこと。
残っていても機能には影響しないが、cleanup 未完了の証拠となる。
- id: naming_consistency
type: convention_check
description: >
maid_id / ファイル名 / import が wiki_worker で一貫していること。
不一致でも essential_hard が通っていれば uncertain 扱いとする。
# uncertain_evidence: Butler が uncertain と判定したとき Brain に渡す構造
# Brain が読むコストを下げるために、Butler が最低限これを整形して返す
uncertain_evidence:
fields:
- changed_files # Git 差分から算出したファイル一覧
- hard_condition_results # essential / evidence それぞれの pass/fail と出力
- soft_condition_results # soft 条件の pass/fail
- stdout_tail # 最後 50 行
- stderr_tail # 最後 20 行
- test_results # unit test の pass/fail サマリ
- why_uncertain # Butler が 1 文で埋める(例: "unit test は通過したが registry に wiki_worker が見つからない")
success_criteria:
- "unit_tests_pass が通ること"
- "wiki intent で dry-run を実行したとき候補ファイルと wiki path 対応が evidence で返ること"
- "WIKIJS_URL / WIKIJS_TOKEN の読取り経路が存在すること"
verifier 実行後:
0. scope_guard を最初に確認
└─ 違反あり → 即 clear-fail (retry しない。再試行しても同じ違反が繰り返されるため)
1. essential_hard が全部通った
└─ evidence_hard も全部通った → hard-pass (success)
└─ evidence_hard が 1 つ以上落ちた → uncertain (Brain review)
2. essential_hard が 1 つ以上落ちた → clear-fail (retry 可)
3. uncertain の場合:
- worker を再実行しない
- uncertain_evidence を整形して Brain に渡す
- Brain が hard-pass 相当と判断すれば success 扱いにしてよい
- Brain が clear-fail 相当と判断すれば retry を Brain が指示する
dryrun_evidence_shape を essential に入れたか仕様書 §12 の「最低限返すべき項目」が満たされているかを確認する条件で、
これが通らない = Phase 1-a の完了条件を満たしていない、と明確に言えるため。
ファイルが存在しても中身が空、というケースを防ぐ役割も兼ねる。
wiki_worker_file_exists を evidence_hard に入れたかunit_tests_pass が通っていても、ファイルパスが
butler/maids/wiki_non_llm_maid.py だったりすると将来の import で壊れる。
ただし「ファイル名は違うが動いている」ケースは Brain が判断すべきなので
essential ではなく evidence に置いた。
no_scope_violation を scope_guard に独立させた理由soft に置くと「uncertain 扱いで Brain review」になってしまい、
「scope 違反でも evidence が良ければ通す」可能性が生まれる。
scope 違反は機能の良し悪しと無関係に Butler が拒否すべき安全境界なので、
hard / soft いずれとも切り離した独立セクションにした。
no_bak_files を soft に置いた理由機能には影響しない。cleanup 漏れを検出するが、それだけで retry するのはコスト過多。
uncertain_evidence に soft_condition_results が含まれるので、
Brain が cleanup を別 task として後送りするかどうかを判断できる。
uncertain_evidence.why_uncertain の役割Brain が差分全体を読まなくても状況を把握できるようにする 1 文。
Butler 側は hard_condition_results と soft_condition_results から機械的に生成できる。
例: