6. Adapter - Liplus-Project/liplus-language GitHub Wiki

アダプターレイヤー仕様書

本文書は Li+ プログラムのアダプターレイヤー(adapter/claude/CLAUDE.md / adapter/claude/hooks-settings.md / adapter/claude/hooks/*.sh / adapter/codex/AGENTS.md)の仕様を定義する。 要求(何を満たすか)と仕様(どう振る舞うか)を一体として記述する。


セッション初期化

ブートストラップ

セッション開始時に Li+config.md を読み込み、実行する。Character Instance は Claude の output-styles と Codex の利用者所有の .codex/output-styles/<name>.md に置く。従来の Codex inline 設定は移行前互換として保持する。Codex の生成 AGENTS.md にはキャラ定義・代替項目・参照リンクを置かない。

Codex のプロジェクト output style(#2173)

正本は project root の .codex/output-styles/<name>.md、選択名は project .codex/config.toml の [liplus] output_style である。本文を config・argv・環境変数へコピーしない。選択未設定でもフォルダがあれば character_instance を標準として読む。フォルダと選択がどちらもない移行前 project だけが legacy mode であり、フォルダがあるのに標準ファイルが無ければ明示失敗する。アカウント選択は character_codex_luna.md 等の選択本文だけへ差し替える。共通 developer_instructions と rules は保持し、二つの persona を同時注入しない。Luna を配布既定にしない。

通常 App/CLI は project 選択だけを読む。profile や CLI -c と同期するとは約束しない。Pullcept はプロセス専用 LI_PLUS_OUTPUT_STYLE の非空 stem で同じ helper の選択を上書きする。file mode の失敗は旧 inline に戻さない。明示的な false は上書き選択がない時の既定キャラ無効。空文字・true・その他の型は設定エラー。名前は大小区別する ASCII [A-Za-z0-9][A-Za-z0-9_-]{0,127}、separator・drive・dotdot・絶対パスと canonical 境界を抜けるリンクは拒否する。

UTF-8(BOM 可)、LF/CRLF を受け入れる。先頭の独立行 --- から次の独立行 --- までを frontmatter として除き、残り本文のバイトを保持する。任意 name が存在すれば stem と一致する単純 scalar だけを受け入れる。H1 は selector として再解釈しない。missing・invalid UTF-8・未閉鎖 frontmatter・空・oversize は明示失敗。artifact と handler の UTF-8 JSON 出力をそれぞれ 128 KiB 以下に制約する。

Python >=3.11 の scripts/codex_output_style.py を .codex/hooks/codex-output-style.py にバイト忠実に導入する。既存 rules handler と他製品 handler を保持して、startup/resume/clear/compact の独立 SessionStart handler に additionalContextLimit: 0 を指定する。実行された helper 自身の installed root と引数 root が一致し、cwd がその canonical root 内にある場合だけ配送する。信頼済み native hook が本文全文を一度だけ追加 developer context へ渡す。失敗は continue:false と secret-free error で不完全本文を渡さない。hook trust の迂回や hash 手編集は行わない。

model-free resolve API の CLI・protocol_version:1・成功 metadata・エラーの正本は adapter/codex/character-config.md。resolve 成功や helper 存在だけは native hook trust の証明ではない。Pullcept は native host の trusted handler 登録も照合する。

移行は Li+update.md 4x.0s の一回の明示適用だけで行い、利用者 style と共通指示・無関係な config・encoding を保護する。更新と rules installer は検証済み style/disabled を認め、native persona を再追加しない。file mode と有効 native persona の併置は事前に解決する。hook は有効 profile/CLI overrides を知らないため、未指定 global/profile を勝手に変更しない。Claude 共有は利用者環境で既存内容を保護し、.claude/output-styles に選択 artifact の共有リンクを作る。frontmatter は name、description、keep-coding-instructions: true を生成する。GUI trust・CLI startup/resume・Pullcept 実経路が未確認なら source CI 成功と分け、Issue の実機完了にはしない。

Codex の inline キャラ設定と移行前互換(#2167)

以下は style/disabled 選択がない legacy mode だけの互換手順であり、file mode の再更新には適用しない。

developer_instructions に届く Character_Instance literal が Codex の設定済み条件である。既存のネイティブ設定と明示的な opt-out を優先し、有効設定にキャラがなければ初回・再更新とも Lin/Lay の既定 literal を提案する。テンプレートの角括弧 marker がなくても NAME=... 行を持つ既存 native literal を保持する。Luna は配布既定にしない。既存の共通指示・profile 選択・その他の設定は保持し、利用者所有の設定を更新ごとに再生成しない。

AGENTS.md の存在だけでは導入済みと判定しない。Li+ 区画のない通常の既存 AGENTS.md への初回導入でも、ファイルがない初回と同じ Lin/Lay の opt-in を提案する。旧 literal の不在や空の指示値だけで opt-out と判定しない。既存 adapter の再更新でもキャラが不在なら提案し、明示的な利用者の拒否は disabled として保持する。sentinel の境界が曖昧なら判断を止める。境界検査は既存 native 設定/opt-out を保持する分岐より前に行う。

AGENTS.md の区画置換より先に旧 literal を読む。境界が一意なら全文を保持し、カスタム内容を配布既定で置き換えない。曖昧な境界・無効な TOML・未確認の実効設定では置換を止め、legacy 判断手順へ戻る。保存の対象は root の developer_instructions だけで、既存の共通指示へ追記する際は事前に差分を提示して承認を得る。旧 AGENTS.md と変更する既存 config はバイト忠実なバックアップを作り、書込後に TOML と literal を検証してから区画を置換する。既に設定済みならキャラ設定を書き換えず、旧 literal はバックアップに保持する。明示的な opt-out を選んだ環境に既定キャラを再導入しない。

scripts/migrate_codex_character.py はこの保存段階を実行する Python 3.11+ helper である。既定は read-only plan、--apply が保存、--approve-existing が既存 config の提案済み変更への承認を表す。--native-state は bootstrap が実効設定を確認した結果(absent / present / disabled)を必須で渡す。helper はホストの設定階層を解決せず、AGENTS.md の置換も実行しない。設定の read-back に失敗したら旧区画を保持し、config のバックアップから復旧する。

target に root の指示キーが無い場合は、実効設定から継承した共通指示も保持する。helper には private UTF-8 file の --inherited-instructions <file> で渡し、使用後に削除する。配置例・設定階層の根拠と profile の版差は adapter/codex/character-config.md が持つ。fixture テストは helper の保存・拒否・再実行と adapter の配置を検証するもので、ホストの session 注入・bootstrap を実機で実行した証明ではない。

Codex の導入済みルール(#2169)

.codex/rules/ はタグ解決した rules/**/*.md の相対構造とバイトを保持するコピーで、Codex の自動ロード機能ではない。SessionStart がここから注入し、cold-start anchor・literal の参照表・ルール検索も同じツリーを使う。skills/docs のタグ解決は継続する。全 matcher で注入前に所有 manifest の形式と記録された各ルールのファイル存在を確認する。anchor は manifest にも必要で、manifest の欠損・不正または記録済みルールの欠損時は installed-rules-missing と更新要求を出し、部分的な注入や共有 clone の作業ツリーへのフォールバックを行わない。この確認は内容の hash 一致を判定するものではない。

キャラ保存・読戻し確認または明示 opt-out の解決後、Li+update.md 4x.1r と scripts/install_codex_rules.py で導入する。model/character_Instance.md は除外する。検証済み style/disabled または legacy native を確認し、style と config バイトは変えない。.codex/state/liplus-rules.json は導入した相対パスと hash を記録し、記録済みで未改変のルールだけを更新・削除する。未記録の同一内容は採用でき、競合・改変・曖昧な所有境界は変更前に止める。native execution-policy と追加ファイルは保持する。旧 character コピーは配布原文と同一のもののみバックアップ後に除去し、カスタムの本文全体が実効 native 指示に保存済みならバックアップ後に除去できる。異なるカスタムは先に保存先と所有を解決する。変更前バックアップ、入力再確認、書込後読戻し、失敗時復旧を行う。再適用で重複しない。

生成物(Li+ セクション、hook スクリプト)にはソースタグを埋め込む。bootstrap 時にタグが更新されていた場合、既存の生成物を再生成する。ユーザーカスタマイズ部分(Li+ セクション外)は保護する。

配布先 workspace では、会話の基本言語と成果物のプロジェクト言語を分離できる。既定値は Li+config.md に保持し、未設定ならセッション開始時に AI が対話で確定して書き戻す。人間の明示指示で、現在の返答または成果物の言語を上書きできる。

bootstrap の ask と Li+config.md への書き戻しは、セッション開始時の config 未解決パスにのみ適用する。config が解決済みなら、セッション途中の再 ask と config 再書き込みは本 Phase の対象外とする。runtime precedence(人間の明示指示 > スレッド合意 > config > 再 ask)はアダプターの Workspace_Language_Contract が担い、セッション全体を通して本 Phase を再起動せずに働く。

この workspace 言語契約は liplus-language リポジトリ内部の運用言語と分離する。

セッション継続性

コンテキスト圧縮・再開・セッション継続時に rules/ 配下を再読込する。

rules/**/*.md(model/ evolution/ task/ operations/ subdir を含む)は .claude/rules/ に相対パスを保持したまま再帰ミラーされ、YAML frontmatter alwaysApply: true により常時コンテキストに存在し compaction を生存する。skills/<name>/SKILL.md(flat 命名)は .claude/skills/<name>/SKILL.md にミラーされ、skill auto-invocation で description が示すトリガー条件に一致した時点で読み込まれる。手動再読込は不要。

トリガーベースの再読込:PR 作成時のサブ issue 参照自動補完のみが PostToolUse hook で残存する(同じ hook が Claude Code 側でファイル書き込み後の異文字混入検出も担うが、これは再読込ではない。#2047)。それ以外の focus pointer 注入は、rules/ 常時コンテキスト化と skill auto-invocation へ移行済み(#1102 以降)。


アダプターの構成

アダプターレイヤーは2つの役割を持つ:

エントリーポイント(adapter/claude/CLAUDE.md / adapter/codex/AGENTS.md): ホスト指示ファイルへの Li+ 注入を担う。読込順序、skill auto-invocation トリガーのマッピング、workspace 言語契約の配線を所有する。rules/ の常時読込と skills/ の auto-invocation を主軸とし、hook は Cold-start Synthesis 素材収集・PR サブ issue 補完といった runtime 固有の補助に限定する。Claude adapter では Character Instance を Claude Code 標準の output-styles 機構(session 開始時 system prompt rendering)で常駐させ、毎ターン hook 再通知は撤廃。Codex のキャラ配置・移行は本書の「Codex のキャラ設定と更新前の移行」と Li+update.md 4x.0 が担い、AGENTS.md には定義もリンクも置かない。ファイル名はターゲット側の生成先(.claude/CLAUDE.md / AGENTS.md)に揃え、adapter が target world の命名に染まる方針を取る。

ランタイムバインディング(adapter/claude/hooks-settings.md + adapter/claude/hooks/*.sh): ホスト環境固有のトリガー実装を担う。エントリーポイントが定義するトリガー契約を、ランタイム固有のメカニズム(rules/、skills/、hook 等)へコンパイルする。script 本体は実ファイル、settings.json バインディングは markdown(hooks-settings.md)内に格納する。実ファイル = cp 対象、markdown = 抽出対象、という一貫ルールで bootstrap の挙動をファイル名から推測できるようにする。

エントリーポイント本文の範囲(#2145)

adapter/claude/CLAUDE.md と adapter/codex/AGENTS.md の sentinel 区間は毎セッション全体が文脈に入る。本文には、読んだ者の行い(何を・いつ・どの条件で・誰が・何を禁じるか)を決める行だけを置く(rules/model/liplus-coding-rule.md Body States Behavior)。位置づけ・理由・読込の仕組みの説明と、常時ロードの rule や skill が正本として持つ内容の写しは本文に置かず、本書と正本側が持つ。本文から外した記述の置き場は次のとおり。

  • 位置づけ:アダプターはホスト指示ファイルへ Li+ を注入する入口で、意味の出所は LI_PLUS_REPO の rules/*.md と skills/*/SKILL.md である。読込順序はランタイムへの取り付け順であって層の優先順位ではない(rules/model/axis-separation.md、rules/model/layer-definition.md)。Layer = L6 Adapter Layer の 1 行は frontmatter を持たないこのファイルの層宣言として本文に残す。
  • Sheepdog Engineering の三軸:表は docs/G.-Sheepdog-Engineering.md が持つ。本文に残すのは modifier 軸(AI が Li+ ソースを自ら編集する)の 1 行だけで、skills/evolution-l1-update-gating/SKILL.md Boundary clarification がこの行を「CLAUDE.md Sheepdog Engineering」として引く。position 軸(.claude/ 配下、Codex では AGENTS.md の最小コア・hook が注入する rules/・.agents/skills/・.codex/ の hook と設定を、外から被せられた制約ではなく AI の内部ツールとして読む)は態度の記述であり、initiator 軸は Evolution_Initiator_Autonomy の写しである。
  • LI_PLUS_UPDATE_STATUS=unnecessary の根拠:hook が adapter sentinel タグと対象タグの一致、Li+config schema の正規性、言語契約の解決を確かめた結果である(上の「起動時ステータスマーカー」と C. 更新同期手続き)。Codex で marker が無いのは、SessionStart hook が走らなかったとき——多くは一度きりの GUI trust が未付与のとき、または bootstrap 前——である。どちらも本文の「marker absent → step 2」で扱い、trust の案内は docs/D.-Installation.md が持つ。
  • 読込の仕組み:Claude Code では rules/**/*.md が .claude/rules/ から常時文脈に入り compaction を生存し、層は各ファイルの frontmatter が宣言する。skill は .claude/skills/ から description 一致で自動起動する(上の「セッション継続性」)。Codex には .claude/rules 相当の自動読込フォルダが無く、on-session-start hook が導入済み .codex/rules/**/*.md(character_Instance.md を除く)を additionalContext として session 開始時に出し、resume / clear / compact でも再注入する。AGENTS.md に inline で置くのは識別・起動契約の最小コアだけで、project_doc_max_bytes の 32 KiB 上限の内側に収める(Li+update.md の 32 KiB 注記)。Codex の skill は .agents/skills/<name>/SKILL.md(repo scope と user scope)から trust ゲート無しで自動起動する。Codex にはサブエージェント定義ファイルを置かない(下の「サブエージェント委任」)。Cold-start Synthesis の anchor(H1 preamble)を出す matcher は startup / resume / clear / compact / fork である。
  • Character_Instance の配置:Claude の output-styles への install、既定テンプレートの出所(rules/model/character_Instance.md)、初回のみ生成し既存ファイルを上書きしないことは、下の「Claude Code バインディング」の output-styles ファイルの項と Li+update.md が持つ。Claude 本文には配置・有効化の 1 行を残す。Codex は利用者所有の project output-styles(移行前はネイティブ設定)に置き、AGENTS.md に配置の項目・リンクを残さない(#2167)。
  • 常時ロード面の写し:Main never reads operations skills directly when subagent is available. が対の片割れであることの説明は rules/operations/main-agent-procedures.md The bar and its pair が、release 時の版種別とタグの確認は同ファイルの Human confirmation required が、brake 1 の宣言は rules/evolution/initiator-autonomy.md Merge brake が、それぞれ正本として常時ロード面に持つ。本文は写しを持たず、brake は 1 行の転送点で名指す。Main / Subagent axis separation の 2 行は、同じ本文の Subagent does not create, move, or remove worktrees ...、Worktree lifecycle の見出し行、skill 自動起動の行の写しとして外した(下の「Main / Subagent の軸分離」)。worktree 並列の理由と ListAgents の実測は下の「サブエージェント委任」節が持つ。同じ理由で、Serial delegation does not require worktrees. と commit serialize の軸の 1 行(どちらも Worktree requirement applies to same-branch parallel commit only と Same-parent sub-issue parallelism: が同じ行いを持つ)、Skills need no manual re-read.(再適用の義務は rules/ だけを名指す)、No adapter-side trigger table is maintained.(下の post-tool-use.sh 節が持つ)も外した(PR #2147 brake 1 round 1)。
  • 本文に残した写し:Main agent after subagent completion: の CHANGES_REQUESTED 行は skills/task-pr-review-judgment/SKILL.md の写しだが、L. Hop Count Instrument S2 のアンカーであり、skill への転送点に置き換えるとツール跳躍が増えるので残した。Decision_Structure_Write_Autonomy の Boundary clarification は rules/evolution/evolution.md がこの面に委ねた読み手 / 書き手対の説明を持つので残した。Human gate retained for: はこの自律ブロックの範囲の縁であり、ブロック固有の範囲の縁は各ブロックに置く(rules/evolution/autonomy-block-shape.md Block-specific carve-out)ので残した。

Claude Code バインディング

adapter/claude/hooks-settings.md に settings.json の hook バインディングを、adapter/claude/hooks/*.sh に hook スクリプト本体を、実ファイルとして格納する。rules/skills の生成手順は Li+update.md に集約されている(adapter 側のドキュメントには再掲しない)。bootstrap 時に3種類の生成物を作成する:

  1. rules/ ファイル: リポジトリの rules/**/*.md(model/ evolution/ task/ operations/ subdir を含む、ただし rules/model/character_Instance.md を除く)を相対パスを保持したまま .claude/rules/ に再帰ミラーする。YAML frontmatter(alwaysApply: true)を付与し、常時コンテキストに存在させる。compaction を生存する。なお L5 Notifications / L6 Adapter は意図的に rules/ subdir を持たない(L5 は realtime trigger 実装保留の予約席、L6 はテンプレート + hook 駆動で rules/ に載らない)。詳細は 判断構造 layer-reorg-rationale を参照する。
  2. skills/ ファイル: リポジトリの skills/<name>/SKILL.md(flat 命名、例:skills/operations-on-commit/、skills/model-agentic-search/、skills/evolution-loop/ 等)を .claude/skills/<name>/SKILL.md に再帰ミラーする。各 SKILL.md は既に skill frontmatter(name, description, layer)を含んでおり、auto-invocation タイミングを description で宣言する。
  3. output-styles/ ファイル: rules/model/character_Instance.md の body を読み出し、output-styles 形式の frontmatter(name: character_Instance / description / keep-coding-instructions: true)に書き換えて .claude/output-styles/character_Instance.md に install する。Claude Code 標準の output-styles 機構が session 開始時に system prompt 冒頭へ rendering し常駐させる。keep-coding-instructions: true は Claude Code 既定のコーディング作法 / TodoWrite / ツール使用ガイダンスを system prompt に保持するための必須フラグ(未指定時は custom output-style 有効化に伴い既定指示が除外される)。settings.json の "outputStyle": "character_Instance" で active 化。初回のみ生成し既存ファイルは上書きしない(ユーザーカスタマイズ可能)。旧 rules slot(.claude/rules/model/character_Instance.md)からの一回限り migration を bootstrap が担う(詳細は Li+update.md Phase 4c.2)。
  4. hook ファイル: 現行 hook3種(on-session-start.sh / on-user-prompt.sh / post-tool-use.sh)を .claude/hooks/ に生成する。

常時注入(Working with Issues、Research Strategy)と section extraction 方式の focus pointer 注入は、rules/ の alwaysApply 化と skills/ の auto-invocation に移行したため hook から削除された(#1102)。

パス安全性

settings.json の hook command はプロジェクトディレクトリにスペースを含む環境で壊れないよう bash "$CLAUDE_PROJECT_DIR/..." 形式でクォートする。hook スクリプトは冒頭で export PATH="$HOME/.local/bin:$PATH" を設定し、永続インストールされた外部コマンド(gh 等)を参照できるようにする。

on-session-start.sh

Codex Windows PowerShell 版の clone mode では、installed sentinel tag がローカルで解決できる場合、git archive --output=<絶対パス> で skills/docs のバイトを一時ファイルへ保存し、そのファイルを tar で展開する。両プロセスの exit 0、展開済み docs/Decision-Structure.md と skills/*/SKILL.md の実在を確認してから採用する。解決済みタグの展開失敗・空展開は LI_PLUS_UPDATE_STATUS=needed reason=liplus-source-unresolved と診断を出して停止し、clone working tree へ切り替えない。タグ未解決時の既存 fallback は維持する。一時ディレクトリ内の archive は採用前に削除し、展開先も終了時に削除する。ルール注入、anchor、source 検索、設定値と状態のテキスト読取りは UTF-8 を明示する。native profile/config、context 上限、hook trust、他 port の契約は変更しない。

トリガー:SessionStart (matcher: startup / resume / clear / compact / fork) — 各セッション開始イベントで発火。一次ソースの matcher 表にある 5 つすべてを settings.json に登録する。未登録の matcher は他の entry にフォールバックせず単に hook が発火しないため、その入口では LI_PLUS_UPDATE_STATUS マーカーも language contract banner も出ない。fork は --fork-session + --resume/--continue、/fork のバックグラウンドコピー、/branch が該当し、Claude Code v2.1.214 より前はこれらが resume を報告していた。

動作:本 hook はセッション開始時に3つの処理を順に実行する(これは hook 内部の実行順であり、下記「起動時ステータスマーカー」で説明する stdout への emission 順とは別軸なので混同しないこと)。(1) gh CLI 前提条件チェック、(2) 更新同期要否の verify、(3) Cold-start Synthesis の素材収集。(1)(2) は下記「起動時ステータスマーカー」で説明する直接 emit マーカーに対応し、(3) が本節以下で説明する素材収集本体である。hook 自身は synthesis せず、AI が Character_Instance 経由で合成するための素材を収集し stdout へ出す(Claude Code SessionStart 契約でセッション冒頭 context に注入される)。rules/ は compaction 生存のため再注入不要だが、以下の素材は session 開始時の観察 surface として収集対象になる:

  • rules/evolution/cold-start-synthesis.md の anchor(H1 preamble のみ。最初の H2 semantic tag で切る。drift recovery anchor として常時 emit、diff 比較対象外)。H2 節を落とすのは、ルールファイル自体が always-on でロードされるため全文 emit が同一セッションの context への二重載せになるから。H2 節を持たないファイルは全文が anchor になる(切り落としは経済性の措置であり、anchor 消失のほうが重い失敗)。契約は 2. Evolution の Cold-start Synthesis 節(anchor の切り出し)
  • docs/Decision-Structure.md の先頭(section key: decision_structure_head)
  • rules/ のパスツリー(cold-start でロードされる rules cache の fetch address table)(section key: rules_tree)
  • 最新リリースタグ(prerelease 含む)(section key: recent_releases)
  • open な in-progress issue 一覧(最大5件)(section key: open_in_progress_issues)
  • open な blocker を持つ open issue 一覧(section key: open_blocked_by_open_issues):LI_PLUS_REPO の open issue のうち、blockedBy に state が open の issue を 1 件以上持つものを、その open な blocker とともに列挙する(#<n> <title> <- blocked by #<m>, <owner>/<repo>#<k>。別リポジトリの blocker は owner/repo 付き)。closed の blocker は数えない。取得は gh api graphql による GraphQL(Issue.blockedBy)で、gh CLI の依存フラグ(gh 2.94.0 以降)には依存しない。走査は open issue の 1 ページ(100 件)までで、それを超える open issue がある場合はその旨を 1 行添える。bash 2 port は取得した JSON を node -e で絞り込み(node が無ければ空 = silent skip)、PowerShell port は ConvertFrom-Json で同じ絞り込みを行う。PowerShell port の GraphQL 文字列はダブルクォートを含まない形に保つ(owner / name は GraphQL 変数で渡す)── ネイティブ引数渡しでダブルクォートの扱いが PowerShell の版ごとに異なるため。エージェントの振る舞いの正本は rules/evolution/cold-start-synthesis.md の Dependency Ordering Surface 節、フックの出力契約は 2. Evolution の「フックの出力契約」(#1687)
  • memory/self-evaluation_log.md の先頭(section key: self_eval_head)
  • promotion candidates(memory → Li+ source):自己評価ログで同一の観察軸が繰り返し miss と tag されている件、最近(7日以内)に書かれた memory entry、memory entry title と Li+ source のキーワード重複(section key: promotion_candidates)
    • 検出対象の3軸は rules/evolution/evolution.md の Pattern Detection Surfacing At Cold-start が定め、しきい値と具体ロジックは adapter 側の裁量(同 rule が明示)。現行値は「同種の観察が THRESHOLD_N=2 件以上」と「一覧の表示上限 SURFACE_CAP=10 件」。上限で切っても総件数は必ず併記するため、省略が隠蔽にならない
    • 検出器1が読む書式:正本は skills/evolution-self-eval/SKILL.md の Axis tag line format 節。1行形(**Axis tags**: <軸>: <判定> / ...)と箇条書き形(**Axis tags (10-axis)**: ヘッダ + - <軸>: <判定>)の2種を読み、軸名は同節の normal form(* 除去 → 括弧修飾の切り落とし → -/_ を空白へ → 空白畳み込み → 小文字化 → 10軸のいずれか1つだけの語境界前方一致なら展開)へ正規化してから集計する。1行形の pair 列は括弧外の 。 または Root cause: / Domain: ラベルで終端する(同節 Inline list end)。ここで言う括弧は ASCII ( ) と全角 ( ) を1つの class として扱い、開きと閉じの両方が揃った対だけが「括弧の内側」を作る(対を持たない片方は地の文として読む)。#1651 以前は書式の規定が spec 側に存在せず、表記ゆれで同一軸が別キーに分裂し、行末の自由記述が最後の判定文字列に混入していた。#1653 以前は全角括弧が追跡対象外で、かつ対を持たない ( が行の残り全部を括弧内へ畳んでいた
    • 検出器2・3が読む単位:host auto-memory は 1 memory = 1 file なので、entry の単位はファイルそのもの。title は frontmatter の name:、無ければファイル名 stem。検出器3の token は title 由来で、entry 種別を表す接頭辞(feedback / project / reference / user)は topic を指さないため除外する。pair の報告は同一 source file に別々の token が THRESHOLD_N 個以上落ちた場合に限り、token 数の多い順に並べる(1語だけの一致はこの規模のコーパスでは偶然)
    • 並び順の決定性:3ポートの並び替えはすべてロケール/カルチャ非依存に固定する。bash 版は LC_ALL=C sort、PowerShell 版は [System.StringComparer]::Ordinal(Sort-Object は現在カルチャ依存のため使わない)。SURFACE_CAP で切り詰める一覧では並び順が「どの項目が残るか」を決めるため、順序の一致は表示上の問題ではない。加えて検出器出力は diff-only emission の sha256 対象なので、順序がぶれると差分が毎回発生する
    • 大小文字変換の決定性:検出器1の軸名 normal form の小文字化、miss 判定、検出器3の token 化で行う大小文字変換は、3ポートとも ASCII の A-Z だけを a-z へ写す自前の変換に固定し、それ以外の文字(多バイト文字を含む)はそのまま通す。bash 版は awk の tolower() ではなく ascii_lower(awk 関数)、PowerShell 版は ToLowerInvariant ではなく ConvertTo-AsciiLower を使い、変換後の分割は -csplit で行う。tolower() はホストのロケールに従う(トルコ語系ロケールでは I が点なしの ı になる、UTF-8 ロケールの gawk は Ä を畳むが C ロケールの awk は畳まない)ため、同一入力でポート間の結果が割れていた。LC_ALL=C を awk の起動全体に掛けないのは、多バイト文字に対する length() / substr() の文字単位の挙動へ波及させないため。ASCII 以外の大文字は3ポートとも変換されずに残る。issue #1654
    • MEMORY_DIR の解決規則:候補ディレクトリは優先順に走査するが、採用条件は「ディレクトリが存在すること」ではなく「消費側が読むファイルを1つ以上持つこと」。marker 集合は MEMORY_DIR 経由で読まれるファイル(self-evolution-observation.md、索引 MEMORY.md(consolidate due surface が読む。#2165)、promotion_tally.md、および検出器が走査する per-topic entry の接頭辞 feedback*.md / project*.md / reference*.md / user*.md)に、2つの解決経路が「何を memory ディレクトリとみなすか」で一致するよう self-evaluation_log.md を加えたもの。ただし最後の1つが実際に判定を左右することはない — self-evaluation_log.md は同じ候補パスを走査する独自の探索で解決されるため、そのファイルが存在する場合は先に primary 経路が採用され、この populated 判定に到達しない。任意の *.md ではなく接頭辞で照合するのは意図的で、無関係なファイル1つでディレクトリが枠を取ってしまうのを防ぐ。存在するだけの空ディレクトリが上位候補にあると、下位候補に実体があっても全消費者がまとめて黙るため。どの候補も marker を持たない場合は未解決のままとし、各消費者が個別のファイル存在チェックで silent skip する(従来と同じ帰結)。promotion_tally.md は marker 集合に残るが、上の「MEMORY_DIR 経由で読まれるファイル」ではない ── #1894 で reader を得て以降 #2018 まではそうだったが、#2018 で tally の解決が独立したため、ここに在る tally を読む消費者はもう居ない。それでも集合から外さないのは、#2018 の constraint が memory 側の解決を据え置きにしているため ── 外して動くのは「他に何も持たないディレクトリがどこに解決するか」であり、その判定の消費者は全て tally 以外のファイルである。したがって旧パスの tally 1 個しか持たないディレクトリは、引き続き枠を取る。memory entry の走査からの除外も残す ── 旧パスに残った tally を entry として拾うと、その cluster descriptor が promotion 検出器に戻り、検出器が自分の出力を読む形になるため
    • MEMORY_DIR の探索範囲(#1796):2つの名前つき候補がいずれも解決しなかったときの glob fallback(~/.claude/projects/*/memory)は、CLAUDE_PROJECT_DIR 自身か、それを内包するディレクトリの slug に限る。判定は slug 文字列で行い、候補 slug が CCD_SLUG と一致するか、- 境界での接頭辞であれば採用する。slug 不一致時の救済という fallback 本来の役目は残しつつ、横方向(別ワークスペース)への到達だけを落とす措置である。これは self-eval log の glob fallback(section key self_eval_head)にも同じ guard が掛かる — 2箇所は同形であり、射程も同一
      • 理由:promotion candidates も observation surface も self-eval head も当該セッションの observe stage 入力であり、別ワークスペースの観測はこのワークスペースの観測ではない。#1796 は2つのワークスペースから同時にこの越境を実測した。いずれも自分の memory ディレクトリは存在するが空で、上記 populated 判定がそれを未 populated として飛ばし、glob が第三のワークスペースの entry を promotion candidates として掴んでいた。populated 判定は slug の内側で正しい規則であって slug 間の選択に使う道具ではない。空の memory ディレクトリは「観測材料が無い」と読まれ silent skip になるのが正しく、隣を探しに行くことではない
      • 逆方向(自分より深い slug)は入れていない。必要とした観測が無く、rules/model/subtractive-structural-beauty.md (B) が要求されていない到達を落とすため
      • 残余:slug 符号化は非可逆で復号手段が無いため、判定は符号化後の文字列で行う。自分と同じ親を持ち、名前が自分の名前 + パス様の接尾辞になっている兄弟ディレクトリは「内包している」と読まれる。この取りこぼしは自分の親配下の兄弟に限定され、実測された越境(C--Users-smile-Claude-Lin から C--Users-smile-Code)はこの境界で落ちる
      • codex port(adapter/codex/hooks/on-session-start.sh / adapter/codex/hooks/on-session-start.ps1)は射程外。ホスト側に ~/.claude/projects/<slug>/memory という per-slug レイアウトが存在せず、両 port とも workspace-local な memory/ しか見ないため、構造的にこの欠陥に到達しない。この命題の検証面は claude 側 hook の内側ではなく codex 側の2ファイルにあり、.claude/projects で両ファイルを走査すれば足りる — 該当はレイアウト不在を述べたコメント各1行のみで、cross-slug glob は存在しない。parity を理由に同形の変更を持ち込まないこと
  • self-evolution observation surface(memory/self-evolution-observation.md の check window が開いたエントリ):verdict_state: pending のうち next_check <= today を DUE、expires < today を OVERDUE (human judgment needed) として列挙する。ファイル解決は promotion candidates と同じ MEMORY_DIR 経路を再利用し、ファイル不在・該当エントリ無しは silent skip。section key を持たない = diff-only 比較対象外(理由は下記「Diff-only 出力」を参照)。エージェントの振る舞いの正本は rules/evolution/cold-start-synthesis.md の Self-Evolution Observation Surface 節、フックの出力契約は 2. Evolution の「フックの出力契約」
  • promotion tally expiry surface(tally の 3d 窓が閉じた cluster):expires < today を OVERDUE (threshold judgment not taken)、expires <= today を DUE として列挙し、行末に occurrence 件数を併記する(どの Threshold Rules 行が当たるかを選ぶのは件数のため)。tally 書式は verdict 欄を持たないので状態は読まない — 閾値判定のどの帰結も cluster を消すため、書かれたまま残っている cluster は判定が未了であることそのものである。ファイル解決は MEMORY_DIR から独立する(#2018)── $HOME/.liplus/tally/promotion_tally.md を直接見る。3 ポートとも同一ホストでは同じ 1 ファイルに解決すること。ps1 port は bash port と同じ変数を読むため $env:HOME を先に見て、無い場合に自動変数 $HOME(Windows では通常こちら)へ落とす。旧 MEMORY_DIR/promotion_tally.md へのフォールバックは持たない ── 解決するパスは書かれ続けるパスであり、そこへ書かれた tally は誰も数えない。surface 本文の先頭には解決したパスを出す ── 規則本文は置き場を名指さない(rules/evolution/promotion-judgment.md の Tally 節がアダプタに委ねる)ため、agent は occurrence の書き込み先をこの提示から得る。提示が無いとき(tally 不在)は、同 Tally 節のとおり session 自身の hook の解決を読む。ファイル不在・該当 cluster 無しは silent skip。section key を持たない = diff-only 比較対象外(理由は下記「Diff-only 出力」を参照)。エージェントの振る舞いの正本は rules/evolution/cold-start-synthesis.md の Promotion Tally Expiry Surface 節、フックの出力契約は 2. Evolution の「フックの出力契約」、判定側の正本は rules/evolution/promotion-judgment.md の Threshold Rules 節
  • disposition log の保持期限 trim(素材ではなく、何も emit しない):上記 tally ファイルの <!-- disposition log --> 行より後、次の ## 見出しまでにある箇条書きのうち、先頭の日付(削除日)が今日から 14 日より前のものを削除する(今日との差が 14 日ちょうどの行は残す)。marker より上の cluster の occurrence 行は日付を持っていても対象外。暦日として読めない日付の行は残す。削除する行が無ければファイルは書き換えない。書き換えるときは同じディレクトリの一時ファイルへ書いてから置き換え、残す行は読んだとおり(CR を含む)書き戻す。bash 2 port は awk で日数差を計算し(GNU 専用の date -d を使わない)、BINMODE=3 で Git Bash の gawk が CR を落とさないようにする(gawk manual の "PC Using" 節が MS-Windows 上の CRLF 変換と BINMODE を規定し、Git Bash の GNU Awk 5.3.2 で CR が落ちることを #2107 で実測)。PowerShell port は BOM 無し UTF-8 で読み書きする(Windows PowerShell 5.1 の Set-Content -Encoding UTF8 は BOM を付けるため使わない。5.1 版 about_Character_Encoding の UTF8 Uses UTF-8 (with BOM))。書き込み直後の hook ではなく session 開始時に置くのは、tally がホストにつき 1 ファイルで全アダプタが書くのに対し、書き込み後に走る hook が見るのは自分のホストがその hook に通すツール呼び出しだけだから —— Claude Code では Write / Edit / MultiEdit で Bash のリダイレクトは通らず、Codex の PostToolUse は Bash にしか matcher が無い。3 ポートとも本処理を持つので、どのアダプタ・どのツールで書かれた行にも上限が掛かる。実行位置は tally surface の収集と同じで、Claude 版は全 matcher、Codex 2 port は startup でのみ走る。書き手は追記だけを行い trim しない(#2107)。規則の正本は rules/evolution/promotion-judgment.md の Tally 節
  • memory consolidate due surface(consolidate の発火条件が成立した memory):MEMORY_DIR/MEMORY.md の先頭(最初の空でない行。Consolidate Trigger が記録を置く位置)だけを読み、そこが記録行(ASCII の 1 行 **Last consolidate run:** <YYYY-MM-DD> そのもの。ラベルの前には空白もタブも置かず、ラベルと日付の間は空白 1 つ、日付の後には何も続かない。ラベルは大小文字を区別する。年は 0001〜9999 で、暦日として読める日付に限る)であればその日付を使い、それ以外の先頭はすべて「行なし」と読む(#2182)── 受け付ける形を広げるたびに、Unicode 空白・単独の CR・UTF-16・0000 年で同じ入力に対するポート間の判定が割れたため(#2181)、3 ポートが日付として読む形を 1 つに固定している。空行の判定は ASCII の空白とタブだけで読む ── awk の [[:space:]] はロケールで、.NET の \s は Unicode 空白全般で一致範囲が変わるため。先頭より下にある記録の形の行は記録として扱わない。今日との差が 14 日以上なら DUE (last run <date>, <n> days ago)、先頭が記録の形でない・MEMORY.md が無い・日付が暦日として読めない場合は DUE (no readable **Last consolidate run:** line) を 1 行出す。記録日が未来なら出さない。本文の先頭には読んだ索引のパスを出す。ファイル解決は observation surface と同じ MEMORY_DIR 経路で、MEMORY_DIR が解決しなければ silent skip(memory が無ければ consolidate の対象も無い)。日数差は bash 2 port が awk の日数計算(disposition log trim と同じ関数。GNU 専用の date -d を使わない)、PowerShell port が TryParseExact で取る。0000 年は暦日として読まない ── .NET の DateTime は 1〜9999 年しか持てないため、awk 側の関数も 0000 年を拒み、同じ日付を両側で「行なし」と読む(#2181)。行の区切りは LF だけで、LF の直前の CR を 1 つ落とす(それ以外の CR は行の一部として読むので、CR が 2 つ続く行末や単独の CR を含む先頭は記録行にならない)。bash 2 port は awk を BINMODE=1 で呼ぶ ── Git Bash の gawk はテキストモードで読むと LF の直前の CR を awk に渡す前に落とし、CR が 2 つ続く行末を記録行として読んでいたため(GNU Awk 5.3.2、2026-10-12 に確認。#2182)。バイト列は UTF-8 としてだけ読み、先頭の UTF-8 BOM は落とす。それ以外の BOM は解釈しないので、UTF-16 の索引は「行なし」と読む。PowerShell port は Get-Content ではなく、ファイルをバイト列で読み、UTF-8 BOM を自前で落としてから UTF-8 で復号し、LF で分割する ── Get-Content は単独の CR でも行を切り、ReadAllText は BOM から符号化を判定し直して UTF-16 の索引を復号するため、どちらも awk と同じ入力で先頭行の判定が割れていた(#2181)。hook は読むだけで memory を書き換えず、pass も走らせない。section key を持たない = diff-only 比較対象外(理由は下記「Diff-only 出力」を参照)。エージェントの振る舞いの正本は rules/evolution/cold-start-synthesis.md の Consolidate Due Surface 節、発火条件の正本は rules/evolution/memory-entry-format.md の Consolidate Trigger 節、フックの出力契約は 2. Evolution の「フックの出力契約」(#2165)
  • clone branch fetch surface(LI_PLUS_REPO clone の remote.origin.fetch が branch を 1 本も引けない状態):source 側を refs/heads/ 以下に持つ refspec が 1 本も無い場合に、設定済み refspec と期待する形を併記して列挙する。述語は wildcard の literal 一致ではなく、また refspec 全体の部分文字列一致でもない —— [+]<src>:<dst> の src 側だけを見る(--single-branch の clone は branch を追えており該当しない。逆に +refs/tags/v1:refs/heads/mirror は dst 側にしか refs/heads/ が無く branch を 1 本も引かない)。^<pattern> は exclusion であり、どの namespace を指していても mapping を成立させないため述語を満たさない。.git を持たないディレクトリ(api mode)と git 不在は silent skip。section key を持たない = diff-only 比較対象外(理由は下記「Diff-only 出力」を参照)。日付駆動の面(observation / tally / consolidate)と違い trigger は日付駆動ではなく state 駆動であり、lifecycle を持たない —— 条件が成立しなくなった時点で出なくなるため、除去のための判定を要しない。LI_PLUS_UPDATE_STATUS には載せない(同 marker は adapter startup 手順 step 2 の起動条件であり reason で分岐しないため、検出のみの検査が更新手続きを起動する経路を持ってしまう)。エージェントの振る舞いの正本は rules/evolution/cold-start-synthesis.md の Clone Branch Fetch Surface 節、フックの出力契約は 2. Evolution の「フックの出力契約」

起動時ステータスマーカー(Li+ update status / Li+config: unrecognized value / Li+ language contract / gh install)

cold-start 素材収集より前に、hook は register_section を経由しない printf 直接出力で最大4つのマーカーを emit する(Li+config: unrecognized value は該当時のみ)。いずれも diff-only 比較(sha256 fingerprint 方式)の対象外である。ただし各マーカーが matcher(startup/resume/clear/compact/fork)に関わらず毎回再 emit されるのは、liplus-language リポジトリ解決済み(bootstrap 後)の通常セッションに限る(Codex 版の Li+ update status のみ startup 限定。各マーカーの項を参照)。bootstrap 前の経路は挙動が異なるため、下記「pre-bootstrap 早期終了」を参照。

  • ━━━ Li+ update status ━━━:LI_PLUS_UPDATE_STATUS=unnecessary tag=<tag> channel=<channel> または LI_PLUS_UPDATE_STATUS=needed reason=<axis1,axis2,...> を出力する。adapter sentinel tag / Li+config schema / workspace 言語契約の3軸 verify 結果であり、意味論(unnecessary/needed の分岐条件と reason 軸)の正本は C. 更新同期手続き を参照する。AI 側の消費契約(Li+update.md walkthrough を skip するか実行するか)は adapter/claude/CLAUDE.md の "Execute the following at startup" ブロックが担い、本書では再掲しない。needed のときは status=needed target=<対象タグ> adapter=<sentinel タグ> の 1 行を {workspace_root}/.claude/state/update-status.txt に書き、unnecessary のときはこのファイルを削除する(#1987。Codex 版は .codex/state/update-status.txt に startup でのみ書く)。このファイルはワークスペース単位であり、LI_PLUS_AGENT_KEY のパーティションには載せない。毎ターンの再掲は下記 on-user-prompt.sh が担う。
  • ━━━ Li+config: unrecognized value ━━━:LI_PLUS_CHANNEL の値が既知の 3 値(latest / release / tag)のいずれでもなく、かつ空でもない場合のみ出力する。キー名と実際の値を含む 1 行を出すだけであり、既定へ倒れる挙動そのものは変えない(対象タグは未解決のまま、update status は needed)。空値は未設定であり既定が正しいため対象外。update status と同じ startup 限定(Codex 版)/全 matcher(Claude 版)の分岐に従う。正規化(Latest → latest)を採らずに値を名指しする理由は、正規化では lattest のような打ち間違いが依然として黙って既定へ倒れるため、「黙って倒れる」クラス全体を塞げないことによる(issue #1804)。
  • ━━━ Li+ language contract ━━━:LI_PLUS_BASE_LANGUAGE=<値> / LI_PLUS_PROJECT_LANGUAGE=<値> を出力する。値は上記3軸 verify の軸3が Li+config.md から抽出したものをそのまま使う(毎セッション実行時に live な config から取得しており、生成ファイルへの焼き込みではない)。未解決の値は unset として出力し、ブロック自体は(下記 pre-bootstrap 早期終了に到達しない限り)無条件に emit する — bootstrap 済みセッションの内側では「ブロックが無い」状態と「値が無い」状態を AI 側が区別せずに済むため。この marker が在る理由は、契約本文(Workspace_Language_Contract)が常時 context に在るのに対し、その値の解決だけが「Li+config.md を読む」という手続きとして書かれており、同ファイルはどのエージェントの context にも自動ロードされないため(issue #1575)。契約の意味論(base / project の定義と precedence)の正本は adapter/*/CLAUDE.md・AGENTS.md の Workspace_Language_Contract であり、本 marker は値のみを運ぶ。Codex 版では update status が startup matcher 限定なのに対し、本 marker は Claude 版と同じく全 matcher で emit する(rules 再注入と同じ理由で、非 startup の matcher でも値が context に要る)。
  • ━━━ gh install ━━━:gh CLI が PATH 上に無い場合のみ出力する(command -v gh が真を返せばブロック自体が実行されず、マーカーも出ない)。ブートストラップ済み通常セッションに限定すると、ブロックに入った場合の結果は GH_INSTALL_STATUS=installed / failed: <末尾ログ抜粋> / missing: <ホスト別ガイダンス> のいずれかであり、この3値すべてでマーカーは出力される(installed も無言スキップではない)。pre-bootstrap 経路では出力条件がこれと異なる(下記「pre-bootstrap 早期終了」参照。zero-output ケースはそちらにもう1つ存在する)。Linux ホストはアーキテクチャ判定つきで ~/.local/bin/gh へ自動インストールを試み、成否を installed/failed として報告する。macOS と Windows(Git-Bash・MSYS2・Cygwin) はドキュメント化された前提条件として扱い、自動インストールせず具体的な導入コマンド(brew install gh / winget install --id GitHub.cli)を案内する。認識できないホストカーネル(default ブランチ)は、具体的なコマンドは示さず「プラットフォームのパッケージマネージャーで導入せよ」という一般的な案内のみを出す。

pre-bootstrap 早期終了:LI_PLUS_MODE が api 以外で、liplus-language リポジトリが未解決(bootstrap 前)の段階では、hook は gh install チェックの直後、matcher 解決や更新同期 verify に入る前に exit する。このガードは GH_INSTALL_STATUS が failed または missing で始まる場合のみマーカーを出力する。したがって、この経路では gh が既に PATH 上にある場合に加え、Linux 自動インストールが成功した場合(GH_INSTALL_STATUS=installed)も無出力のまま hook が終了する — 「ブートストラップ済み通常セッションでは installed も出力される」の対象外にあたる、もう1つの zero-output ケースである。Li+ update status マーカー、Li+config: unrecognized value マーカー、Li+ language contract マーカーはこの経路では一切出力されない。したがって bootstrap 前のセッションでは言語値が context に届かない — この状態の扱い(marker 不在も unset と同じく human へ確認する)は adapter/*/CLAUDE.md・AGENTS.md の Workspace_Language_Contract が持つ。

api モード(LI_PLUS_MODE=api)では clone を置かないため、clone の不在は bootstrap 前を意味せず、この早期終了には入らない(#2031)。Claude の rules / skills / docs と Codex の skills / docs から読む素材は、adapter 自身の sentinel タグの tree を GitHub の tarball から取得した {workspace_root}/.liplus-extract/<タグ>/ から読む(無ければ hook が取得して展開し、以降のセッションはそれを再利用する)。LI_PLUS_CHANNEL=tag の対象タグは clone の origin ではなくリポジトリ URL へ git ls-remote して解決する。これらを除けば、出力されるマーカーと素材は clone モードと同じである。

出力順序(bootstrap 済みの通常セッション):Li+ update status → Li+config: unrecognized value(該当時のみ)→ Li+ language contract → gh install(該当時のみ)→ cold-start 素材(matcher 依存)。

Diff-only 出力(build-2026-05-11 以降)

session 冒頭 context 消費を抑えるため、startup matcher では前セッションから変化のあった section のみを出力する。各 section の body から sha256 fingerprint を計算し、{workspace_root}/.claude/state/last-cold-start-emit.json に永続化する。永続化先は LI_PLUS_AGENT_KEY(環境変数、既定 "default")でパーティション分割される(#1811):

{
  "agents": {
    "default": {
      "sections": {
        "decision_structure_head": "<sha256>",
        "rules_tree": "<sha256>",
        "recent_releases": "<sha256>",
        "open_in_progress_issues": "<sha256>",
        "open_blocked_by_open_issues": "<sha256>",
        "self_eval_head": "<sha256>",
        "promotion_candidates": "<sha256>"
      },
      "last_emit_at": "2026-05-11T..Z"
    }
  }
}

state file はワークスペースローカル(.claude/state/.gitignore でバージョン管理から除外、bootstrap Phase 4c.5 で生成)。

パーティション分割(#1811): 一つの作業ディレクトリを複数セッションが共有する場合、単一の共有ポインタでは「先に startup した側が diff を消費し、後から起きた側は自分がまだ見ていない section まで『変化なし』と誤判定する」という分配バグが起きる(飢餓とは形が違い、No new orientation material の合図すら立たないため、単独セッションの context からは検出できない)。agents.<key> は LI_PLUS_AGENT_KEY の値ごとに独立した {sections, last_emit_at} を保持し、各セッションが自分自身の baseline とだけ比較する。未設定時は固定キー "default" に解決され、単一セッションのワークスペース(大多数のケース)では旧来の単一パーティション挙動と挙動・ファイル形状ともに完全に一致する(レガシー形状からの一度きりの移行を除く。下記 fail-safe 参照)。複数セッションで共有する場合は各人自身の起動プロファイル(シェル env)にこの値を個別に設定する運用を人間側に要求する。セッション識別子(Claude Code / Codex 自身の session id)は分割キーとして不採用——理由は下記 fail-safe リストと 2. Evolution の「フックの出力契約」を参照(startup matcher は毎回新しい session id を発行するため、これを鍵にすると diff-only が毎回 full emit に潰れる)。

matcher 別の挙動:

matcher 挙動
startup 各 section の fingerprint を、この回の LI_PLUS_AGENT_KEY に対応するパーティションの前回値とだけ比較(他パーティションの値は参照しない)。変化あった section のみ emit。全 section 不変なら "No new orientation material since last session" marker を 1 行出力(silent skip ではなく、session boundary 観察可能性を保つ)。ただし diff-only の外に置かれた 4 surface(observation surface / promotion tally expiry surface / consolidate due surface / clone branch fetch surface)のいずれかが emit された session では marker を出さない — overdue な項目や壊れた clone を提示しながら「新規素材なし」と述べるのは自己矛盾のため。加えて、state file の last_emit_at を 1 行読み戻して emit する(前回 baseline が消費された時刻。diff-only 状態のみ —— full emit は何も抑制しておらず、marker 状態は既に境界を 1 行で示している)。識別子は持たず、追加もしない —— 「いつ baseline が動いたか」だけを述べ、「誰が動かしたか」は述べない(共有 workspace の他セッションによる消費と自セッションの開き直しはここでは同じに読める)。stamp が欠落・不正形式の場合はこの行を省くだけで、fail-safe の理由にはしない
resume / clear / compact / fork 作業 context は連続のため diff-only 評価は行わず、cold-start rule anchor だけを再出力。state file は更新しない

fail-safe 動作(startup matcher 時):以下のいずれかが発生した場合は全 section を full emit し、instruction footer に理由を human observable な形で記載する:

  • state file が存在しない(初回起動)
  • state file の JSON が malformed
  • sha256sum コマンドが PATH にない
  • node コマンドが PATH にない(stdin の matcher 解析は sed regex fallback を持つが、state 読み書きは node 必須。#1519 で外部 jq バイナリ依存から node -e に置換済 — node は Claude Code 自身が依存するランタイムのため安全な前提)
  • state file がレガシー形状(agents map を持たない、#1811 以前のルート直下 {sections, last_emit_at})である —— データを引き継ぐ移行コードは書かず、この回の書き込みで新形状へ一本化する(1 回きりの冗長 full emit で足りるとする既存 fail-safe 哲学の延長)
  • この回の LI_PLUS_AGENT_KEY に対応する agents.<key> エントリが state file にまだ存在しない(このキーの初回利用。"default" キー自身が #1811 の新形状下で初めて読まれる場合も含む)

matcher 解決:stdin の JSON(Claude Code から渡される hook payload)から node -e で .matcher / .source / .session_source を順に見て最初に値のあるものを採る。node 不在時、または JSON parse 失敗時は sed regex で fallback。stdin が空なら startup を既定値とする。実際に host が送るのは .source であり、matcher は settings.json 側のフィルタキーで payload には現れない(.matcher だけを見て .hook_event_name に fallback していた旧実装は、本番形 payload に対して常に SessionStart を返し、既定値 startup に落ちていた。#1632 F1)。値の照合は 3 port とも大小文字を区別する(PowerShell 版は -cin / -ceq / -cne)。仕様外の casing(Resume など)はどの literal にも一致せず、3 port とも既定の startup へ倒れる。ホストが渡す casing は一次資料に明文の固定が無いため、port 間で結論が割れないことを優先する。issue #1884。

cold-start rule anchor の常時 emit は drift recovery anchor としての役割を担うため、diff-only 比較セットから明示的に外す。詳細は rules/evolution/cold-start-synthesis.md および 2. Evolution を参照する。

self-evolution observation surface、promotion tally expiry surface、memory consolidate due surface も同じく比較セット外だが、外す理由は別軸である(3 つは同形であり、以下は 3 つに等しく当たる)。こちらはトリガーが日付駆動なのに body が内容駆動という非対称に由来する。未解決のまま日をまたいだエントリは body が byte 一致のままなので、fingerprint 比較に載せると「最初の 1 セッションだけ表面化し、以後は注意を要する期間ずっと抑制される」という意図と正反対の挙動になる。該当エントリが無ければ body が空になり silent skip されるため、常時 emit にしても通常セッションの context コストはゼロ。expires を過ぎたエントリは OVERDUE としてのみ報告する(next_check も過去であるのが通常だが、同一エントリを両軸で二重に出すのはノイズであり、escalation を担うのは overdue 軸)。 tally 側も同じく、窓を過ぎた cluster は OVERDUE としてのみ報告する。consolidate 側は索引が書き換えられない限り body が byte 一致のまま期限を過ぎ続けるので、同じ理由で比較に載せない(due / overdue の 2 段は持たず DUE だけを出す)。clone branch fetch surface も比較セット外だが、非対称の向きが違う —— トリガーが state 駆動であり、条件が成立し続ける間は body が byte 一致のままなので、fingerprint 比較に載せると壊れている間ずっと黙る。日付を読まないため due / overdue の区別も持たず、条件が成立しなくなった時点で body が空になり silent skip される。

on-user-prompt.sh

トリガー:UserPromptSubmit — ユーザーがメッセージを送信するたび(Claude の処理開始前)。

動作:Li+ update status の毎ターン再掲、Trigger Check Gate の毎ターン re-arm、webhook 取り込みの毎ターン re-arm。Character Instance は output-styles 機構で system prompt に常駐するため、毎ターン hook 再通知は不要(v1.16.11 で撤廃)。

Li+ update status の再掲(#1987):on-session-start.sh が書いた .claude/state/update-status.txt と、.claude/CLAUDE.md の sentinel タグをローカルで読む(ネットワークは使わない)。記録された対象タグが空でなく、sentinel タグがそれと異なり、かつ sentinel タグが同じ行に記録された sentinel タグと一致している間、━━━ Li+ update status ━━━ の下に LI_PLUS_UPDATE_STATUS=needed reason=sentinel-tag(adapter=<現在の sentinel タグ>,target=<記録された対象タグ>) と walkthrough 実行の 1 行を出す。それ以外の場合と、ファイルが無い場合は何も出さない。理由文字列は既存の sentinel-tag 軸だけであり、新しい軸は足さない。出力位置は webhook ブロックの手前。Codex 版は .codex/state/update-status.txt と AGENTS.md を読み、同じ条件で同じ行を出す。

webhook 取り込み re-arm は分離可能な 2 つを運ぶ。Li+config.md の LI_PLUS_WEBHOOK_DELIVERY が切り替えるのは下の呼び出し半分だけであり、届いたものをどう扱うかの面は切り替えない。

  • 呼び出し半分(未設定 / poll のみ):mcp__github-webhook-mcp__get_pending_status を AI 自身に呼ばせる指示を stdout へ出力する。channel は MCP channel が、mcp_hook は UserPromptSubmit の type: "mcp_tool" hook(settings.json template の既定)が同じ呼び出しを代替するため、両モードではこの半分を出さない。出せば二重配送になる。
  • 取り扱い半分(全モード):前景報告フィルタと mark_processed の re-arm。これを代替するものはどのモードにも無い。発火時刻は each user turn start であり、always-on 常駐はロード保証であって発火保証ではないため、ターン境界を発火できる hook だけがこの面を担える。両半分を一括で落としていたのが #1798 であり、イベントは届いているのに扱いを述べる面が無い状態になっていた。

Li+config.md の LI_PLUS_WEBHOOK_DELIVERY が既知の 3 値(poll / channel / mcp_hook)のいずれでもなく、かつ空でもない場合、webhook ブロックの手前に ━━━ Li+config: unrecognized value ━━━ を出し、キー名と実際の値を 1 行で名指しする。既定(poll 相当)へ倒れる挙動そのものは変えない。値の比較は 3 port とも大小文字を区別する(PowerShell 版は -cne、キー名照合は Select-String -CaseSensitive)。issue #1804。

hook が運ぶのは terse な re-arm であって正本の写しではない。関連性判定と destructive consume の正本は 5. Notifications、前景取り込み手続きの正本は rules/operations/main-agent-procedures.md の ## Foreground webhook notification intake に従う。

post-tool-use.sh

トリガー:PostToolUse (matcher: Claude Code は Bash|Write|Edit|MultiEdit、Codex は Bash) — Bash ツール呼び出し後に実行する。Claude Code では Write / Edit / MultiEdit の完了後にも実行する(下記「異文字混入の検出」)。 payload の tool_name 照合は 3 port とも大小文字を区別する(PowerShell 版は -cne)。bash のような仕様外の casing は 3 port とも guard で弾かれ、gh 呼び出しへ進まない。issue #1884。

動作:adapter flatten(#1102)以降、section extraction 方式の focus pointer 注入は rules/ の alwaysApply 化と skills/ の auto-invocation へ移行済みのため撤去された。現行 hook が担うのは PR 作成時のサブ issue 参照自動補完 と、Claude Code 版だけが持つ 書き込まれたファイルの異文字混入検出(#2047、下記)の 2 つである。

コマンドパターン 動作
gh pr create PR 作成出力 URL から PR 番号を抽出し、PR body で GitHub の closing keyword(close / closes / closed / fix / fixes / fixed / resolve / resolves / resolved。大文字小文字を区別せず、直後のコロンも許す)の直後にある最初の #<番号> を親 issue とし(keyword を伴わない #<番号> は親としない、#2071)、親 issue の子 issue を取得し、PR body に記載のない子 issue の Closes #NNN を自動追記する(マージ時に GitHub が自動クローズさせるため、Refs ではなく Closes を使う)。対象リポジトリは liplus-language clone の origin から読み、clone が無い場合(api モード)は PR 作成出力の URL(<owner>/<repo>/pull/<番号>)から読む(#2031)

それ以外のトリガー(on_issue / on_branch / on_commit / on_pr / on_ci / on_review / on_merge / on_release / on_research / on_subagent_delegation / on_judgment_form / on_self_eval / on_l1_update_proposal / on_persistence_decision / on_evolution_loop_stage / on_structural_change / on_search_decision / on_review_output / on_webhook_intake)は、各 skills/<name>/SKILL.md の description を Claude が意味的に評価して対応 skill を auto-invocation する(adapter 側に trigger table は保持しない)。hook 側での section extraction は不要。

JSON 処理:stdin payload の解析と hookSpecificOutput の組み立ては node -e で行う(on-session-start.sh と同方針、#1519 / #1540)。#1540 以前は外部 jq バイナリに依存しており、jq 未導入ホスト(Windows / macOS / 多くの Linux ディストリビューションで既定では同梱されない)では tool 名の解析が空となり、hook がフォールバックも痕跡も無い無言の no-op に陥っていた。なお本 hook 内の gh api --jq は gh バイナリ内蔵の jq 式エンジンであり、外部依存ではないため置換対象外。

node 不在時の挙動:node は jq より確度の高い前提だが保証ではない(Claude Code は packaged 実行ファイルとして配布され、hook 子プロセスの PATH に node を置くことを契約していない)。そのため command -v node で明示的に分岐し、不在時は「サブ issue 参照を追記できなかったので手動で補完せよ」という静的 JSON を hookSpecificOutput として出力して終了する。無言終了にすると、jq から node へ鍵を差し替えただけで #1540 と同じ不可視の失敗を再現するため。加えて、raw payload に gh pr create が含まれない呼び出しは node 起動前に事前フィルタで抜ける(Bash ツール呼び出しのたびに node を起動しないための高速 path)。ただしこの事前フィルタは生バイトに対する部分文字列一致であり、解析後のガードが照合するのはデコード済みの値なので、両者は一般には等価ではない。コマンド文字列中の空白を unicode escape で書いた payload はデコードすれば一致するが生バイトでは一致せず、取りこぼす。Claude Code の payload serializer が printable ASCII をエスケープしない限り実トラフィックでは到達しない、という前提の上で成立するフィルタである(この前提は Li+ 側で検証できる範囲を超えるため、仮定として明示する)。

出力形式:Claude Code の PostToolUse hook は plain text 出力を context に注入しない。hookSpecificOutput JSON ラッパーで出力する必要がある:

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "追加メッセージ"
  }
}

注:UserPromptSubmit hook は plain text がそのまま system-reminder として注入されるため、この制約は PostToolUse のみに適用される。

発火痕跡(#1710):コマンド 1 行目が gh pr create に一致した後は、どの経路で終わっても additionalContext を 1 行だけ出す(接頭辞 post-tool-use: )。以前は追記した場合にだけ出力があり、「何も該当しない」と判断して終えた実行は空出力だったため、正常に動いた実行と一度も動かなかった実行が transcript 上で区別できなかった(#1540 の根本条件)。一致しない Bash 呼び出しと Bash 以外のツールでは従来どおり何も出さない。3 port とも同じ文言を出す。違うのは、出力を読むフィールドを名指す <field> だけである(下記)。

終わり方 痕跡(post-tool-use: の後)
追記成功(PATCH の終了コード 0) PR #<n>: sub-issue refs auto-appended (parent #<m>): Closes #a, Closes #b.
PATCH 失敗 PR #<n>: PATCH of the body failed; sub-issue refs NOT appended (parent #<m>): Closes #a.
出力フィールドが無い・空・文字列でない gh pr create matched, but <field> is absent, empty or not a string; no sub-issue refs appended.
出力に /pull/<番号> が無い gh pr create matched, but <field> carries no /pull/<number> URL; no sub-issue refs appended.
リポジトリを解決できない PR #<n>: repository could not be resolved; no sub-issue refs appended.
PR body が読めない・空 PR #<n>: body could not be read or is empty; no sub-issue refs appended.
body に closing keyword 付きの #<issue> 参照が無い PR #<n>: body carries no closing #<issue> reference; no sub-issue refs appended.
親にサブ issue が無い(または読めない) PR #<n>: parent #<m> has no sub-issues, or they could not be read; nothing to append.
サブ issue がすべて参照済み PR #<n>: every sub-issue of parent #<m> is already referenced; nothing to append.

追記成功の行は PATCH が成功した場合にだけ出す。以前は PATCH の終了コードを見ずに「追記した」と報告していた。

PR URL を読むフィールド(#2060):<field> は各 port が読むフィールドで、ホストごとの Bash の tool_response の形に合わせている。Claude Code の Bash の tool_response は stdout / stderr / interrupted / isImage を持つオブジェクトで、output を持たない(hooks reference の PostToolUse updatedToolOutput の例が Bash の出力の形としてこれを示し、実 transcript も同じオブジェクトを toolUseResult として記録している)。そのため claude port は tool_response.stdout を読む。Codex の Bash の tool_response はコマンド出力そのものの JSON 文字列である(Codex hooks のページはこのフィールドを「JSON value」としか型付けしていないため、openai/codex の codex-rs/core/src/tools/context.rs の ExecCommandToolOutput::post_tool_use_response が JsonValue::String を返すことと、unified_exec_tests.rs の tool_response: serde_json::json!("three") で確認した)。そのため codex の 2 port は tool_response そのものを読む。どの port も読む値が文字列でなければ無いものとして扱い、別の形をテキストに変換して読むことはしない。#2060 以前は 3 port とも tool_response.output を読んでいたが、どちらのホストの形にもこのフィールドは無いため、実際の実行はすべて URL の段で終わっていた。

出力チャネルの根拠:Claude Code hooks reference(https://code.claude.com/docs/en/hooks)は、PostToolUse を含む大半のイベントで exit 0 の stdout を debug log に書き transcript には出さない、exit 0 の stderr は debug log のみで Claude は見ない、と明記している。exit 2 は PostToolUse では stderr を Claude に見せるが、同 reference が警告を届ける手段として挙げる経路であり、正常終了の痕跡には合わない。hookSpecificOutput.additionalContext はツール結果の隣に system reminder として Claude の context に入り、同 reference は注入テキストがセッション transcript に保存されると述べる(実 transcript では、UserPromptSubmit / SessionStart の additionalContext が attachment.type = "hook_additional_context" の記録として残る)。Codex hooks(https://developers.openai.com/codex/hooks)の PostToolUse は plain text stdout を無視し、additionalContext を developer context として加え、exit 2 の stderr はツール結果をフィードバックで置き換える。両ホストで、ツール結果を変えずにモデルと transcript へ届くのは exit 0 の additionalContext だけであるため、これを使う。systemMessage は両ホストとも利用者向けの warning として表示されるので、成功の痕跡には使わない。

異文字混入の検出(#2047、Claude Code 版のみ):Write / Edit / MultiEdit の完了後、tool_input.file_path のファイルを読み、キリル・ハングル・アラビア・ヘブライ・タイ・デーヴァナーガリー・アルメニア・ジョージアの文字の連なりのうち、直前か直後の 1 文字がラテン文字・ひらがな・カタカナ・漢字・長音符(U+30FC / U+FF70)であるものを報告する。空白・句読点・括弧・行の端で両側を区切られた連なり(引用された語など)は報告しない。日本語の文中に生成の段階で紛れ込む 1 文字単位の混入が、ファイル面に残ったものを拾うための検出である。ギリシャ文字は μm や Δt のようにラテン文字と隣り合う正当な用法があるため対象に含めない。

  • 出力:検出した場合だけ、additionalContext に post-tool-use: stray-script check: で始まる 1 行(件数とファイルパス)と、1 件ごとに line <行番号> (<文字体系>): <前後 8 文字>[<連なり>]<前後 8 文字> の行を出す。報告は最大 20 件で、超えた分は ... and <n> more とまとめる。検出が無ければ何も出さない。
  • ファイルは書き換えない。ツールの結果も止めない(exit 0)。直すかどうかは報告を受けた agent が判断する。
  • 飛ばすもの:通常ファイルでないパス、1 MiB を超えるファイル、NUL バイトを含むファイル(バイナリとみなす)。tool_name の照合は Bash と同じく大小文字を区別する。
  • 走査はファイル全体に対して行う。以前から残っている混入も、そのファイルを書き込むたびに報告される。
  • 覆わない面:部屋の発話・PR コメント・サブプロセス出力など、ファイルを経由しない面は対象外であり、受け入れる残余とする。空白で区切られて単独で現れる混入(キリル文字 1 文字が空白に挟まれている形)も、引用された語と区別できないため報告しない。
  • node が無い場合は、ファイルを走査できなかったことを述べる静的な additionalContext を出して終える(PR 作成時の静的メッセージと同じ扱い)。
  • Codex 版の 2 port は書き込みツールの形が異なるため、この検出を持たない。

生成先ファイル構成

{workspace_root}/
└── .claude/
    ├── CLAUDE.md         # ホスト指示ファイル(adapter/claude/CLAUDE.md から生成)
    ├── settings.json     # hook 登録(SessionStart + UserPromptSubmit + PostToolUse)+ outputStyle: character_Instance
    ├── output-styles/
    │   └── character_Instance.md  # session 開始時に system prompt 冒頭へ rendering(初回生成のみ、ユーザーカスタマイズ可)
    ├── rules/
    │   ├── model/*.md             # 常時コンテキスト(alwaysApply: true)— L1 Model layer
    │   ├── evolution/*.md         # 常時コンテキスト(alwaysApply: true)— L2 Evolution layer
    │   ├── task/*.md              # 常時コンテキスト(alwaysApply: true)— L3 Task layer
    │   └── operations/*.md        # 常時コンテキスト(alwaysApply: true)— L4 Operations layer
    ├── skills/
    │   └── <skill-name>/SKILL.md  # skill auto-invocation(flat 命名、例:operations-on-commit, model-agentic-search, evolution-loop, model-pair-review 等)
    ├── hooks/
    │   ├── on-session-start.sh  # Cold-start Synthesis 素材収集(diff-only 出力)
    │   ├── on-user-prompt.sh    # Li+ update status 再掲 + Gate re-arm + 通知取り込み re-arm(Character は output-styles で常駐)
    │   └── post-tool-use.sh     # PR 作成時のサブ issue 参照自動補完 + 書き込まれたファイルの異文字混入検出
    └── state/
        ├── .gitignore               # `*\n!.gitignore`(hook runtime state をバージョン管理から除外)
        ├── last-cold-start-emit.json  # on-session-start.sh の diff-only 比較用 sha256 fingerprint(hook が初回実行時に生成、LI_PLUS_AGENT_KEY でパーティション分割)
        └── update-status.txt          # update status が needed のとき on-session-start.sh が書く 1 行、on-user-prompt.sh が毎ターン読む(パーティション分割なし)

bootstrap は次回セッションから有効。現セッションは Li+config.md の実行で継続する。


前景 Webhook 通知取り込み

前景スレッドで軽量な GitHub webhook 通知を確認する。広く GitHub を探しに行くのではなく、届いている差分だけを扱う。

ホストが各ターン先頭でローカル確認を実行できる場合のみ使用する。確認処理は内部 housekeeping として無言で行い、確認中であることや empty/no-op 結果を会話へ出さない。

アダプターが所有するのは transport の選択と summary の受け渡しである。関連性判定、claim、ack/read、consume/done、mention、cleanup の正本は 5. Notifications に置く。

通知源の優先順位:

  1. mcp__github-webhook-mcp
  2. ローカル webhook ストア(LI_PLUS_MODE=clone かつ bundled helper が使える場合)
  3. 利用不可 → 黙ってスキップ

アダプターは inspect を既定とし、前景一致しない通知を勝手に排水しない。詳細が必要になるまでは full payload を開かない。このフローから別 AI プロセスを起動しない。

自己アクション通知の処理

自分(またはサブエージェント)の操作で発生した webhook 通知は、外部イベントではなく到着確認である。

自己アクション通知も他の通知と同じ前景チェックフローで処理する。自動排水の免除はない。

判定基準:sender が自分のアカウント、直前の操作と時系列・内容が対応する通知、governance CI の状態遷移(操作の副産物)。すべてを満たすこと。

処理ルール:

  • 前景チェックで通知を個別に inspect する
  • 自己アクションと確認できたもの = 人間への報告不要で processed にマーク
  • 自己アクションでない、または判断がつかない = 従来の前景ルールに従う

サブエージェント委任

委任の意味ルール(何を伝えるか、何を保持するか、hook チェーン、issue 管理、失敗報告)は skills/task-subagent-delegation/SKILL.md を参照。本セクションではアダプターレイヤー固有の実行詳細のみを定義する。

エージェント定義ファイルの再編(#1972)

adapter/claude/agents/ は、役ごと(implementer.md / brake-evaluator.md / dialogue-evaluator.md)からエフォートごと(low.md / medium.md / high.md)へ組み替えた。#2121 で同じ形の xhigh.md を一度加えたが、Master 判断(2026-09-29)で外し、3 本に戻した(#2124)。3 本のどれも役を持たない——本文は「役と手順は spawn プロンプトが渡す」旨のみで、固定するのはエフォート 1 個だけであり、model も tools も名乗らない。理由は 3 本共通で、役ごとに置くと役の数だけ 3 本ずつ増え、同じ役の説明が複数本へ写る(rules/model/subtractive-structural-beauty.md Core principle (A) の「2 つ目の写しがずれる」形)のに対し、エフォートごとに置けば 3 本のまま固定され、役が増えても本数は変わらない。

役は、剥がした定義ファイルの本文が担っていた分だけ、spawn プロンプト側へ移した。実装委譲の役 literal は skills/task-subagent-prompt/SKILL.md Role literal: implementation delegate、judge 型 brake 1 評価役の役 literal は skills/evolution-parallel-agent-eval/SKILL.md Constraint: Effort floor が、それぞれ 1 箇所だけ保持する。役の断片をエフォート定義ファイル側へ書き戻すことは禁じる。

Codex 側 (adapter/codex/agents/*.toml) は当初この再編の対象外とした(Master 合意、2026-09-14)。Codex は spawn 呼び出しで reasoning_effort を渡せるため Claude 側と前提が異なり、役ごとの定義を残したうえで、#1973 で effort の固定だけを各 TOML から外していた。#2176 で 3 本(implementer.toml / brake-evaluator.toml / dialogue-evaluator.toml)とも削除した(Master 提起、2026-10-10)。Codex の子は定義ファイルを持たない組み込みエージェントとして spawn し、役は Claude と同じ spawn プロンプトの役 literal が、model と reasoning_effort は spawn call が運ぶ。Claude 側でエフォートごとの定義ファイルが要るのは effort を spawn call で渡せないためであり、渡せる Codex にはその理由が無い。dialogue-evaluator の役は Claude 側(#1972)と同じく引き継ぎ先を持たない。

Li+update.md Phase 4c は agents ディレクトリに対して stale removal を行わない。削除した 3 本(implementer.md / brake-evaluator.md / dialogue-evaluator.md)は、この変更より前にインストール済みのワークスペースの .claude/agents/ には残り続ける。残骸を消す手順は本リポジトリの仕組みには無く、そのワークスペースを運用する人間の手に残る。Codex の .codex/agents/ も同じで、Li+update.md Phase 4 codex は何も書かず何も消さないため、#2176 より前に入れた 3 本の TOML は不活性のまま残り、片付けは人の手に残る。

実装委譲のエージェント定義

skills/task-subagent-delegation/SKILL.md に基づく委譲の spawn 先は、Claude Code では、ホスト組み込みの汎用エージェントではなく Li+ が配る subagent_type: low / medium / high のいずれか(adapter/claude/agents/ の 3 本)であり、Codex では定義ファイルを持たない組み込みエージェントである。役 literal(skills/task-subagent-prompt/SKILL.md Role literal: implementation delegate)はどちらのホストでも spawn プロンプトが同じ文面で運ぶ。

どれを選ぶかは役割が決めない(#1967)。モデルもエフォートも、その spawn が担う作業に応じて親が毎回選ぶ。判断材料(作業の中身 / 読む必要のある面の広さ / 誤りを後段のどのゲートが捕まえるか)の正本は skills/task-subagent-spawn/SKILL.md Selection criteria にあり、ここへは写さない。

同節は判断材料に加えて、その判断材料が動かす起点となる着地の目安(Landing anchors)と、条件付きの下向き経路を持つ(#2162、#2175)。条件と起点の優先関係は同節が正本で、ここへは写さない。目安の根拠となった比較(出典・日付付き)は #2162、下向き経路を検討した比較と限界は #2175 および tests/fixtures/model-selection-2175/RESULTS.md、tests/fixtures/model-selection-2175-separated/RESULTS.md に置く。

Claude 面で定義ファイルを置く理由は思考 effort の固定にある。Claude Code では effort が spawn call の引数に存在せず、各定義ファイル frontmatter の effort が固定する。固定するのは「その名前のエージェントを選んだときの effort」であって、委譲がどれを選ぶかではない。Codex 面は spawn call で reasoning_effort を渡すため、定義ファイルを置かない。定義ファイルに model_reasoning_effort を置けば per-launch の解決値を上書きする。

model はどちらの面にも書かない。モデル軸も effort 軸と同じく spawn call で親が選び、省略すれば親モデルを継承する(skills/task-subagent-spawn/SKILL.md)。定義ファイルはその軸を引き取らない。親のティアが変わったときに追従する性質をそのまま残すためである。

Claude 面の本文(low.md / medium.md / high.md)は役を持たないため単体で成立する形の懸念自体が消える。役 literal(skills/task-subagent-prompt/SKILL.md 側)は単体で成立する形で書く——ホストが基底プロンプトの上に重ねるのか置き換えるのかは判別が付いていない(2026-09-11 の実測で判別不成立)。未測定の基底に対する差分として読ませる本文は、その未決着がそのまま壊す形になる。

brake 1 の評価者のうち probe 型はこの実装委譲定義の対象外である。probe 型の素の挙動そのものが eval の読み取り対象であり、定義本文はその読み取り対象を置き換えてしまう。judge 型は下の節が扱う。

定義ファイルの更新は、走行中のセッションに対して次の spawn から効く。再起動は不要だが通知も出ないため、委譲が走っている最中に変更を落とす場合は人手で周知する。

brake 1 評価役のエージェント定義

Claude Code の judge 型 brake 1 評価役は subagent_type: low(adapter/claude/agents/low.md)以上として spawn し(low は床であり、その上でどれを選ぶかは他の spawn と同じく親の選択である)、役 literal は skills/evolution-parallel-agent-eval/SKILL.md Constraint: Effort floor がプロンプトで運ぶ。Claude の probe 型は定義ファイルを持たず、ホスト組み込みの汎用エージェントとして spawn する。Codex では judge / probe とも定義ファイルを選ばず、組み込みエージェントとして spawn し、reasoning_effort="low" を per-call で渡す。judge 型の役 literal は同じ self-contained prompt が運ぶ。

2026-09-13 の Claude 実測で、定義ファイルを持たない評価役は親セッションの effort をそのまま継承しており、本 workspace の 2 セッション全件でほぼ low で走っていた。モデル床(sonnet 級)はモデル軸しか守っておらず、effort 軸は素通りしていた。Claude の judge 型は low.md frontmatter の effort: low で床を固定し、probe 型は親セッションを上げる。Codex は両型とも spawn call の reasoning_effort="low" で床を固定し、定義ファイルを置かない。

値は low である(Master 判断、2026-09-29)。この判断は 2026-09-13 の暫定値 medium(Master 判断)を置き換える。09-13 の medium は、low で観測された外し方——正解の literal に到達したうえで「保護すべき rationale」と判断して finding から外す形であり、探索の不足ではなく境界判断が保護側へ寄る形——に最小の変更で当たる位置として 1 段だけ上げた値だった。引き下げの根拠は、sonnet 級モデルが 5 から 5.5 へ更新されたことである。5.5 の low の評価役がこの外し方をしないことは未測定であり、仮説として扱う。観測に回す点は、5.5 の low で到達した literal が「保護すべき rationale」として finding から外されるかである。モデル床(sonnet 級、per-call 明示、haiku 禁止)はこの判断で変わらない。

model は effort の軸と独立して spawn call で明示し、sonnet 級の床を担う(skills/evolution-parallel-agent-eval/SKILL.md Constraint: Model floor)。床の上でどの id を選ぶかは、その spawn ごとの親の選択である。Claude の effort 定義にも Codex の brake spawn にも tools 制限を加えない。評価者のツール権限を絞らない判断と、read-only を tools: で表現しない判断は同 skill の Non-scope が持つ。

Claude 面(low.md)は役割を持たず、本文に残るのは「役と手順はプロンプトが渡す」旨だけである。Codex には定義ファイルが無く、judge 型の役・判断基準・軸・手順を self-contained prompt が運ぶ。

judge 型は draft を読む側であって読まれる側ではないため、Claude では effort 床の agent を選べる。probe 型は素の挙動を観測するため Claude の定義ファイルを選べず、親セッション自身の effort を床まで上げる。Codex は effort を spawn call が運べるので、両型とも定義ファイル無しのまま per-launch 値で床へ届く。judge / probe の分岐と役 literal の正本は skills/evolution-parallel-agent-eval/SKILL.md に置く。

Codex spawn の per-call 引数

Codex には agent 定義ファイルが無く、every subagent spawn は組み込みエージェントとして、役を spawn プロンプトで、model と reasoning_effort を spawn call で受け取る。every subagent spawn の per-call 引数に reasoning_effort と fork_turns を必ず明示し、どちらも省略して既定値に依存することを禁止する。

  • 通常の non-brake spawn は、その spawn が担う作業に合わせて model を選ぶか、省略して親モデルを継承し、fork_turns="none" を指定する。
  • brake evaluator spawn は既存 evaluator policy に従って model を明示し、その床と独立して reasoning_effort="low"、fork_turns="none" を指定する。評価材料は self-contained prompt で渡す。
  • implementation delegate の spawn は、skills/task-subagent-prompt/SKILL.md Role literal: implementation delegate をそのままプロンプトに入れる。
  • implementation delegate は、その spawn が担う作業に合わせて reasoning_effort を選ぶ。役割が値を固定することはない(skills/task-subagent-spawn/SKILL.md Selection criteria)。
  • bounded read-only investigation は目的に合わせて reasoning_effort="low" / "medium" / "high" を選び、親 effort への暗黙継承は使わない。
  • reasoning_effort はその spawn で選択された model が公開する列挙値だけを渡す。未対応値に対する fallback を推測しない。
  • dialogue の限定区間そのものが評価材料として必要な場合に限り、fork_turns="3" のような正の10進数字文字列を使用できる。
  • fork_turns="all" による full-history inheritance は通常禁止する。
  • これらの拘束は spawn call ごとに行う。model_reasoning_effort を持つ agent 定義ファイルを足さない——定義ファイル側の値は解決済み per-launch 値を上書きする。

これは L3 の context-isolation semantic を Codex の host-specific 引数へ結び付ける規定であり、L3 semantic 自体、独立した model policy、evaluator の model floor / N / M / P / self-contained-prompt 契約は変更しない。

再開機構(brake 裁定フェーズ)

auto / semi_auto では、brake が報告した後に親が実装サブエージェントを再開し、著者に指摘を裁かせる。裁定主体とフェーズ分割そのものの正本は rules/evolution/initiator-autonomy.md Merge brake と skills/task-subagent-delegation/SKILL.md にあり、アダプターが持つのはホスト側の結線だけである。再開メッセージに何を入れるかは skills/task-subagent-prompt/SKILL.md Resume-phase authority boundary が持つ。

Claude Code:Agent ツールの SendMessage で、spawn 時に返るエージェント id または名前を宛先にして再開する。新しい Agent 呼び出しは cold start であり再開ではない。フェーズ1の spawn で得た id を保持すること。失えば、再開が保とうとしている実装文脈そのものを失う。ListAgents は失った id の回収経路にならない。2026-09-26/27 の実測では、同じマシン上の他のローカルセッションは列挙されたが、別セッションが spawn したサブエージェントも、そのセッション自身が spawn して完了済みのサブエージェントも列挙されなかった。保持している id は完了済みのサブエージェントにも届く。id を宛先にした SendMessage で再開できた。したがって id を失った親には、同一セッション内でも後のセッションでも再開先がない。これは以下の「id は spawn したセッションの文脈にある」が述べる状態である。子に問い合わせる経路も存在しない。サブエージェントは自分の agent id を保持しないため、尋ねても宛先は得られず、宛先を持つのは spawn した側だけである。id は spawn したセッションの文脈にあるため、それを持たない親には再開先が存在しない。実装より後のセッションで裁定が走る場合は常にこの状態であり、そのとき再構成フォールバックが適用される。条件と形の正本は skills/task-subagent-prompt/SKILL.md Resume-phase authority boundary にあり、アダプターが持つのは「Claude 側は id 喪失の形でこれを生む」という結線だけである。

Codex:resume_agent ツールで、保存済み rollout からエージェントを復元する。同じくフェーズ1の id を保持する。fork_turns は spawn call の引数であってここには関係しない——再開は構造上エージェント自身の保存済み context を引き継ぎ、それがこのフェーズの依拠する性質である。resume_agent が使えない場合、またはフェーズ1の id を親が持たない場合(実装より後のセッションで裁定が走るときは常にこうなる)は、再構成フォールバックが適用される。条件と形の正本は同じく skills/task-subagent-prompt/SKILL.md Resume-phase authority boundary にあり、アダプターが持つのは「Codex 側は両方の形でこれを生む」という結線だけである。

メインエージェントの責務

サブエージェント完了後、メインエージェントは報告を受けて次のアクションを判断する。CHANGES_REQUESTED の場合はレビューコメントを読み、issue 要求と照合したうえで修正をサブエージェントに委任する。リリースの場合はバージョン種別とタグを人間に確認する。サブエージェントが利用可能な場合、メインエージェントは operations 系 skill(skills/operations-*/SKILL.md)を直接読まない。この禁止は対の片割れであり、実行主体がメインになりうる手続きのすべてがメインの読める面に正本を持っているあいだだけ成立する。対の明文とそれらの手続きは rules/operations/main-agent-procedures.md が保持する(オペレーションレイヤー仕様書「メインエージェントの手続き」節)。

Main / Subagent の軸分離

operations 系 skill 直接読取はサブエージェント不在環境にのみ適用する。worktree とセッション用 clone の操作はサブエージェント有無に依存せず常に main のみが担う。この2つは独立した軸であり、1つのスイッチではない。サブエージェントが利用可能なときも、main は operations 読取を委任するが worktree / セッション用 clone のライフサイクルは保持する。

worktree 並列実行

直列委任では worktree は不要。

worktree 要否と commit serialize の軸分離

worktree 必須は same-branch parallel commit にのみ適用する。commit serialize は same-parent sub-issue の並列実装(親ブランチ共有、worktree 不要)にのみ適用する。この2つは独立した軸であり、1つのスイッチではない。worktree は staging area を隔離して並列 commit を可能にし、commit serialize は staging area を共有したまま commit 順序を整える。

同一ブランチ並列の制約: 同一ブランチを共有する複数サブエージェントは .git/index(staging area)を共有する。同一ブランチ上の並列 commit は staging area の衝突を引き起こす。worktree で隔離すること。

worktree が隔離しないもの: worktree が分離するのは working tree と index であって、refs/stash は分離しない。stash スタックは共有 .git にある単一の ref である。2つの worktree からの git stash push は同一スタックに積まれ、どちらの pop も先頭が誰のものかを問わずに取り出す。エラーも警告も出ずに成功するため、失敗が報告されない。上の段落が名指しているのは staging area だけであり、「worktree を使えば並列は安全」という読みはそこからは出ない。退避手順は skills/task-subagent-prompt/SKILL.md Worktree-safe shelving of uncommitted work にあり、委譲プロンプトへ毎回注入される(ここには再掲しない)。

異なる親 issue 間の並列(推奨): 異なる親 issue は異なるブランチを持つ(#919)。親ブランチごとに1つの worktree を作成する。各サブエージェントは独立した worktree で完全なコミット独立性を得る。

同一親 issue のサブ issue 並列: サブ issue は親ブランチを共有する。ファイルが重複しなければ実装を並列実行できるが、コミットは直列化する必要がある(worktree 不要、ただしコミット順序の制御が必要)。

ライフサイクルはメインエージェントが管理する:

作業場所は worktree のほか、セッション用 clone(1セッションのために作るリポジトリの別 clone)でもよい(#2034)。以下の各手順は、どちらか一方を名指ししない限り両方に適用する。clone モードでワークスペースに置かれる LI_PLUS_REPO の共有 clone ではブランチを切り替えない。そのリポジトリのブランチ作業は worktree かセッション用 clone で行う。

  1. gh issue develop でブランチを作成(issue リンク確立)。本ライフサイクルに限定したスコープであり、main がブランチを作るのは worktree を使う場合のみ。直列委任は worktree を使わないため、そこでのブランチ作成はサブエージェント側に残る(skills/task-subagent-delegation/SKILL.md)。セッション用 clone は main の状態で始まるため、そこでのブランチ作成もサブエージェントが clone 内で行う
  2. git worktree add workspace/.worktrees/{repo}-{issue_number}/ {branch_name} で作業ディレクトリを分離。セッション用 clone の場合は git clone {repo_url} {workspace_root}/{repo}-{session}/ で専用ディレクトリに clone する(共有 clone は使わない)
  3. サブエージェントへの委任時に worktree またはセッション用 clone の絶対パスを追加で伝達
  4. サブエージェントは渡されたパス内でのみ作業する。worktree・セッション用 clone の作成・移動・削除は行わない
  5. PR マージ後に git worktree remove で片付け。セッション跨ぎでは既存 worktree を再利用できる。セッション用 clone は PR マージ後、または作業を取りやめたとき(PR を未マージのまま閉じた場合を含む)に例外なく削除する

EnterWorktree(ホスト機能)はセッション全体の CWD を切り替えるため、並列サブエージェントには不適。素の git worktree add と絶対パス指定を使用する。


memory 書き込みの自律性

memory ファイル(feedback_*.md / project_*.md / user_*.md / reference_*.md 等、1 memory = 1 file)への追加・更新・削除は AI 判断で即時実行する。auto-memory の system-prompt 保存判断基準が成立した時点で人間への許可確認を挟まず書き込む。

書き込み前の永続化ティアリング判定(hard gate)は adapter/claude/CLAUDE.md の Memory_Write_Autonomy が正本であり、同ブロックが inline で持つ。既存のメンテナンスルール(重複は更新で捌く / 陳腐化は削除 / 矛盾する併存を作らない)は rules/evolution/memory-entry-format.md の Entry Format 節が、明示的な「書くな」指示の scope 限定と literal 確認の原則は rules/evolution/autonomy-block-shape.md の Explicit exclusion scope / Literal verification 節が正本である(後ろ二つは Memory_Write_Autonomy 末尾が委譲する先であり、本文はそちらに在る)。書き込む前に該当する面を読むこと。ここには再掲しない——過去に後ろ二つだけを写した結果、後から Memory_Write_Autonomy にだけ追加された hard gate が写しに届かず、rules/evolution/memory-entry-format.md Trigger point が断とうとしている「memory に永続情報が滞留する」経路がこの面からは素通りに見えていた。二つ目のコピーが drift する。


進化

再構築・削除・最適化はすべて許容する。構造の一貫性のみ維持する。

⚠️ **GitHub.com Fallback** ⚠️