[design] エージェント活動 capture 機構 ── ACP / OTel / transcript を AgentRun に正規化

汎用評価層(n_01KT3C0Q…)の α(grounding 基盤)を「**どのレイヤーで活動を capture するか**」調査した結論(2026-06-02、user alignment 済 / 実装前。末尾に ① 反映追記)。

## 核心: capture / usage / store は別レイヤー(混同しない)

| 層 | 役目 | 手 |
|---|---|---|
| 行動 capture | 何をしたか(tool 呼び出し・plan・stop) | ACP(均一・full)/ Claude は transcript |
| usage | token | OTel / Claude は transcript の `usage` |
| cost | コスト | **token から自前計算**(ACP も OTel も標準化してない) |
| store / 解析 | 永続 + post と join | quacker-native AgentRun |

前回 framing の「OTel vs 自前」は capture 層と store 層の混同だった。OTel は「行動 capture」でなく「usage」の答え。行動 capture の本命は ACP。

## ACP(行動)findings

- client↔agent の JSON-RPC/stdio。`session/update` に tool_call(`rawInput`/`rawOutput`/`kind`/`status`/触ったファイル位置)/ plan / thought / stopReason。
- **4 ランタイムを 1 client で均一駆動できる**: goose(native)/ Gemini(native)/ Claude(`@agentclientprotocol/claude-agent-acp`)/ Codex(`zed-industries/codex-acp`)。
- **取れない: token / cost / model**(RFD 止まり・主要ランタイム未実装)。
- driver-coupled。pre-1.0(v0.13.5)/ Zed 単独ガバナンス。

## OTel(usage)findings

- token / duration は取れるが ── cost 非標準 / tool I/O は opt-in + 截断 / 横断は方言バラバラ(Claude=独自 `claude_code.*`、Codex `exec` は metrics ゼロ)で正規化層要 / collector 要 / experimental。

## 結論 = per-runtime best-available を 1 AgentRun に正規化する normalizer

| ランタイム | 行動 | token | 組み合わせ |
|---|---|---|---|
| Claude(bg/main) | transcript | transcript `usage` | transcript 1 本で両方 |
| goose | ACP tool_call | goose OTel | ACP + OTel |
| Codex headless | ACP tool_call | ✗(codex exec metrics 無) | ACP のみ、token 穴 |
| cost | — | token から計算 | 共通 |

= Langfuse 型「多方言 ingest → 1 内部モデル」を独立に再導出。

## 構成

- quacker-channel を**汎用 ACP client** 化(`goose_acp.rs` を一般化、既に goose ACP client、spawn コマンドを registry 化)。
- dispatcher(`acp_dispatch.rs`)が turn 前後で AgentRun event を emit。
- **`posts_read` = bridge --proxy chokepoint で捕る**(下記 ① 反映で更新 ── 旧「ACP tool_call rawOutput / transcript」より確実)。
- store = strand-3 ブループリント(`AgentRun` aggregate / event / projection)。

## サポート範囲の境界

- ACP がきれいに統一するのは goose / Codex(ACP-native worker)。
- **Claude は ACP でフル機能で駆動できる(parity 確認済、`claude-agent-acp` が settingSources + claude_code preset で skills/hooks/MCP/CLAUDE.md ロード)。adapter スコープ外は auto-worktree + bg lifecycle のみ(quacker-channel が既に持つ orchestration)。**
- **それでも Claude は transcript レーンが優(選択)**: transcript が行動 + token を 1 本でくれる。ACP の旨味は transcript 等価物が無い goose/Codex 側。
- = capture は 2 レーン(ACP worker / transcript Claude)、store は 1 つ(AgentRun)。

## ① 反映:posts_read = proxy chokepoint / E2E は moot(2026-06-02 実コード検証)

未解決 (a)(posts_read が ACP tool_call として surface するか)を実コード検証 → **ACP tool_call に賭けなくてよい**と判明:

- dispatched ACP agent も main session も **quacker を読むのは全部 `bridge --proxy` 経由**(`goose_acp.rs` が `<self> bridge --proxy` を MCP として渡す)。proxy は run_sql 結果の**全 row を iterate して `id` にアクセス**(`decrypt_row_if_encrypted`)。
- → **`posts_read` = proxy chokepoint で捕る**(run_id を env で渡す、`QUACKER_CHANNEL_DATA` と同じ inject)。ランタイム横断 uniform、ACP raw I/O 有無に非依存。
- ACP tool_call stream は**非 quacker 活動(file 編集 / bash / plan / stop)**の source として残す(「ACP raw I/O を運ぶか」は full-trace tier で実機検証、posts_read には非ブロッカー)。
- 未解決 (b)(E2E)も **moot**: `id` は暗号化 post でも平文(`body` のみ暗号化)→ 復号できなくても posts_read は surface。E2E 復号は dispatched agent が**本文**を読む必要がある場合の別 fix(`XDG_RUNTIME_DIR` を ACP env に inject、capture と分離)。

## 実装上の注意

ACP stream と OTel trace を後で突合するには共通 `run_id`(駆動側 quacker-channel が振る)。

## 由来

汎用評価層(n_01KT3C0Q…)の α grounding を「どこに保存するか」が log 方針一般化(n_01KT47S86…)に発展。本 note = capture(どう取るか)。