Compare commits

..

No commits in common. "9e0f8e9aad6f2a1d3b08311be54f64e2595888cc" and "f5b200d36b8b07fa6fc4431aa4edcad16a8c62c8" have entirely different histories.

188 changed files with 3836 additions and 10741 deletions

View File

@ -2,15 +2,15 @@
title: "ネイティブGUIアプリケーション"
state: "active"
created_at: "2026-06-10T07:41:18Z"
updated_at: "2026-07-15T21:18:00Z"
updated_at: "2026-06-10T07:41:18Z"
linked_tickets: []
---
## Goal
Yoi の Pod / Ticket / Orchestrator / Skill・prompt resource 操作を、TUI だけでなくネイティブ GUI から扱えるようにする。
Yoi の Pod / Ticket / Orchestrator / workflow 操作を、TUI だけでなくネイティブ GUI から扱えるようにする。
最初の到達点は、既存の runtime / Ticket backend / Pod protocol / Profile / Skill/prompt resource authority を再実装せずに、workspace の状態を視覚的に把握し、選択した Pod・Ticket・role action に対して安全に操作できる desktop GUI client を持つこと。GUI は core authority ではなく client surface とし、既存 CLI/TUI と同じ durable state・同じ protocol・同じ permission/prompt/resource 境界を使う。
最初の到達点は、既存の runtime / Ticket backend / Pod protocol / Profile / workflow authority を再実装せずに、workspace の状態を視覚的に把握し、選択した Pod・Ticket・role action に対して安全に操作できる desktop GUI client を持つこと。GUI は core authority ではなく client surface とし、既存 CLI/TUI と同じ durable state・同じ protocol・同じ permission/prompt/workflow 境界を使う。
## Motivation / background
@ -24,12 +24,12 @@ Yoi の Pod / Ticket / Orchestrator / Skill・prompt resource 操作を、TUI
- long-running orchestration の通知、状態変化、失敗診断の視覚化。
- 将来的な review / merge-ready dossier / plan board / settings editor の専用 UI。
一方で、GUI を理由に runtime authority を分散させたり、Ticket/Pod state を独自 DB として二重管理したり、prompt/resource 文字列を GUI code に直書きしたりしてはいけない。
一方で、GUI を理由に runtime authority を分散させたり、Ticket/Pod state を独自 DB として二重管理したり、prompt/workflow 文字列を GUI code に直書きしたりしてはいけない。
## Strategy / design direction
- GUI は Yoi core の上に乗る client として作る。
- Pod lifecycle、session/history、Ticket storage、Profile resolution、resource/prompt authority は既存 core を正とする。
- Pod lifecycle、session/history、Ticket storage、Profile resolution、workflow/prompt authority は既存 core を正とする。
- GUI 固有 state は selection、layout、local UI preference などに限定する。
- 最初に toolkit / architecture の小さな spike を置く。
- 評価軸は Rust code reuse、async/runtime 統合、native packaging、Linux dogfooding しやすさ、testability、accessibility、long-running log/output 表示、将来の cross-platform 余地。
@ -41,10 +41,10 @@ Yoi の Pod / Ticket / Orchestrator / Skill・prompt resource 操作を、TUI
4. Ticket body/thread/artifacts、Pod output、validation evidence、review report を閲覧しやすくする。
5. 必要に応じて settings/profile/config editor や merge-ready dossier UI を追加する。
- TUI は廃止前提にしない。
- GUI 導入後も CLI/TUI は fallback / automation / terminal-first operation flow として維持する。
- GUI 導入後も CLI/TUI は fallback / automation / terminal-first workflow として維持する。
- GUI で見つかった state model の改善は、TUI と共有できる pure data model / client API に寄せる。
- Prompt / resource / role guidance は GUI code に直書きしない。
- LLM-facing prompt は `resources/prompts` または `.yoi/skills` / configured resources を正とする。
- Prompt / workflow / role guidance は GUI code に直書きしない。
- LLM-facing prompt は `resources/prompts` または `.yoi/workflow` / configured resources を正とする。
- GUI は prompt 文言を所有せず、選択・起動・runtime context の入力面を担当する。
- Security / privacy / authority boundary を保つ。
- secret-like data は UI diagnostics / logs / model context に漏らさない。
@ -57,17 +57,17 @@ Yoi の Pod / Ticket / Orchestrator / Skill・prompt resource 操作を、TUI
- GUI は既存 workspace config、Profile、Ticket backend、Pod registry/protocol を使い、独自の authority store を持たない。
- 最小 dashboard で live/stored Pod、Ticket lane/state、Orchestrator/role session の概況を確認できる。
- GUI から少なくとも attach/restore/open 相当の安全な Pod 操作ができる。
- GUI から Ticket Intake または既存 role launcher を使った role action を実行でき、既存の prompt/resource 境界を壊さない。
- GUI から Ticket Intake または既存 role launcher を使った role action を実行でき、既存の prompt/workflow/resource 境界を壊さない。
- Pod output / Ticket body/thread/artifacts を、TUI より見通しよく閲覧できる最小 UI がある。
- GUI 固有 state と core durable state の境界が文書化されている。
- toolkit / architecture 選定理由、採用しなかった選択肢、packaging 方針が Ticket artifact または design note として残っている。
- GUI で使う state transformation / action eligibility は pure model として test 可能で、主要な selection/action state の unit test がある。
- GUI 実装は CLI/TUI の既存 operation flow を破壊せず、必要な targeted validation が定義されている。
- GUI 実装は CLI/TUI の既存 workflow を破壊せず、必要な targeted validation が定義されている。
## Decision context
- この Objective は中長期の方向性・判断軸を保持する。具体的な toolkit 選定、crate 構成、初期 dashboard 実装、role action 実装、packaging は個別 Ticket に分ける。
- GUI は TUI の単純な置換ではなく、複数 Pod / Ticket / Orchestrator を扱う workspace cockpit として設計する。
- authority は既存 core に残す。GUI は client/view/controller surface であり、Pod/Ticket/resource/prompt の正本を所有しない。
- Prompt 直書き禁止方針を守る。GUI 実装中に LLM-facing 文言が必要になった場合は、`resources/prompts` または Skill/resource 側に置く。
- authority は既存 core に残す。GUI は client/view/controller surface であり、Pod/Ticket/workflow/prompt の正本を所有しない。
- Prompt 直書き禁止方針を守る。GUI 実装中に LLM-facing 文言が必要になった場合は、`resources/prompts` または workflow/resource 側に置く。
- 初期 target は dogfooding しやすい desktop GUI とし、public release / cross-platform polish / installer は後続段階で扱う。

View File

@ -2,7 +2,7 @@
title: "Team workspace control plane and runtime architecture"
state: "active"
created_at: "2026-06-20T14:26:29Z"
updated_at: "2026-07-15T21:18:00Z"
updated_at: "2026-07-07T12:40:00Z"
linked_tickets: ["00001KVMFFYVX", "00001KWMBAA6V"]
---
@ -10,15 +10,15 @@ linked_tickets: ["00001KVMFFYVX", "00001KWMBAA6V"]
Yoi を、単一のローカル開発ディレクトリで動くエージェント実行ツールから、チームで作業・判断・実行結果を管理できるワークスペース基盤へ発展させる。
この Objective の中心は、Web から扱える管理システムを作り、その管理システムにローカル Runtime・リモート Runtime・将来のクラウド Runtime を接続できるようにすることである。管理システムは Ticket、Objective、Memory、Skill catalog、Artifact、Policy、Actor、Repository、Runtime state の正本を持つ。Runtime はその管理システムから Worker launch request / config bundle / repository target / authority を受け取り、作業環境を用意して Worker を実行し、結果・イベント・証跡を返す。
この Objective の中心は、Web から扱える管理システムを作り、その管理システムにローカル Runtime・リモート Runtime・将来のクラウド Runtime を接続できるようにすることである。管理システムは Ticket、Objective、Memory、Knowledge、Artifact、Policy、Actor、Repository、Runtime state の正本を持つ。Runtime はその管理システムから Worker launch request / config bundle / repository target / authority を受け取り、作業環境を用意して Worker を実行し、結果・イベント・証跡を返す。
この Objective は Git ホスティングサービスを作るものではない。Git は重要な Repository provider として扱うが、Yoi の Workspace は Git Repository root と同じものにしない。Yoi が作るべきものは、コード・ドキュメント・データ・成果物などの Repository と Runtime を接続しながら、人間とエージェントの作業、Ticket lifecycle、Memory、Skill catalog、検証証跡、実行環境配置を管理するチームワークスペースである。
この Objective は Git ホスティングサービスを作るものではない。Git は重要な Repository provider として扱うが、Yoi の Workspace は Git Repository root と同じものにしない。Yoi が作るべきものは、コード・ドキュメント・データ・成果物などの Repository と Runtime を接続しながら、人間とエージェントの作業、Ticket lifecycle、Memory/Knowledge、検証証跡、実行環境配置を管理するチームワークスペースである。
## Glossary
この Objective では、以下の語をこの意味で使う。
- Workspace: チームまたはプロジェクトの管理単位。Ticket、Objective、Memory、Skill catalog、Artifact、Policy、Actor、Repository、Runtime state を持つ。Git Repository root ではない。
- Workspace: チームまたはプロジェクトの管理単位。Ticket、Objective、Memory、Knowledge、Artifact、Policy、Actor、Repository、Runtime state を持つ。Git Repository root ではない。
- Control plane: Workspace の正本を持ち、Web UI / API / CLI から操作される管理システム。
- Runtime: Worker 群を束ねる実行基盤。Worker lifecycle、sandbox、mount、cache、checkout/worktree/container filesystem などの working directory materialization、event/control plane を管理する。将来的には 1 つの Runtime が複数 Workspace / Repository の Worker を抱えられる。
- Worker: Runtime が管理する 1 つの agent/session/process。Runtime が用意した working directory と authority の中で動く。
@ -32,7 +32,7 @@ Yoi を、単一のローカル開発ディレクトリで動くエージェン
- Objective: 複数の Ticket を束ねる長期目標や設計方針。
- Artifact: Ticket や Worker 実行に紐づく成果物や証跡。diff、log、validation result、review result、report など。
- Memory: エージェントやユーザーが再利用するための要約された文脈。Ticket や Artifact の正本ではない。
- Skill catalog: `.yoi/skills` / builtin skills から Workspace backend が解決する procedural guidance catalog。外部状態 authority は持たず、Ticket / Worker / workdir などの操作は typed feature/tool surface が担う
- Knowledge: 保守された知識や設計判断。Memory より人間が維持する資料に近い
- Actor: 人間、エージェント、システム、外部サービスなど、Workspace 上で操作や発言を行う主体。
## Motivation / background
@ -44,7 +44,7 @@ Yoi を、単一のローカル開発ディレクトリで動くエージェン
- Ticket をローカル作業メモではなく、チームの作業調整 record にする。
- 実行証跡は Ticket thread、Artifact、WorkerRef snapshot、Runtime event として扱い、独立した実行単位概念を先に増やさない。
- 管理システムと Runtime を分ける。
- まず Web から Ticket、Objective、Memory、Skill catalog、Artifact、Runtime / Worker state を見られるようにする。
- まず Web から Ticket、Objective、Memory、Knowledge、Artifact、Runtime / Worker state を見られるようにする。
- 最初はローカル Runtime を使い、後でリモート Runtime、クラウド Runtime、runtime pool、resource allocation、quota、billing、sandboxing に拡張する。
- Git ホスティング機能を取り込むのではなく、Git Repository / worktree / clone は Repository provider と working directory materialization の手段として扱う。
@ -56,7 +56,7 @@ OSS として Control plane、Runtime、Web frontend、protocol を公開しつ
Team Workspace の正本は server-side control plane に置く。`.yoi` は local backend、single-user/self-hosted compatibility、offline/export/import、local projection、migration bridge として残せるが、multi-user SaaS の正本とはみなさない。
Control plane は Ticket、Objective、Memory、Skill catalog、Artifact、Actor、Permission、Audit、Repository、Runtime / Worker state を管理する。Web UI、CLI、TUI、将来の desktop client は、この Control plane を操作する client であり、別の正本 store を持たない。
Control plane は Ticket、Objective、Memory、Knowledge、Artifact、Actor、Permission、Audit、Repository、Runtime / Worker state を管理する。Web UI、CLI、TUI、将来の desktop client は、この Control plane を操作する client であり、別の正本 store を持たない。
### 2. Workspace と Repository を同一視しない
@ -126,21 +126,20 @@ Ticket には次の概念が必要になる。
- Board / queue / planning / review / done / archived views.
- Conflict handling and concurrent editing policy.
### 4. Memory / Skill catalog の本格再設計は後回しにする
### 4. Memory / Knowledge の本格再設計は後回しにする
Memory は Ticket / Artifact のコピーではない。再利用可能な文脈、方針、学習された制約を扱うが、Ticket や Artifact の authority を置き換えない。Skill catalog は procedural guidance の catalog であり、外部状態 authority を持たない。Knowledge record kind は削除方針なので、この Objective では separate Knowledge storage を新しい control plane entity として増やさない。
Memory / Knowledge は Ticket / Artifact のコピーではない。再利用可能な文脈、方針、学習された制約、保守された知識として扱う。ただし、Memory の意味論・抽出・承認・検索・staleness 処理を今この Objective で先に作り込まない。
理由は、Memory と Skill catalog の正しい設計が Workspace control plane の record model、Actor / visibility / permission、Ticket、Artifact / evidence、RepositoryPoint、Runtime に渡す context の監査方法に依存するためである。これらが固まる前に Memory schema や Skill API だけを作ると、local `.yoi` 前提や現行 agent runtime 前提に引っ張られ、後で再設計が必要になる。
理由は、Memory の正しい設計が Workspace control plane の record model、Actor / visibility / permission、Ticket、Artifact / evidence、RepositoryPoint、Runtime に渡す context の監査方法に依存するためである。これらが固まる前に Memory schema だけを作ると、local `.yoi` 前提や現行 agent runtime 前提に引っ張られ、後で再設計が必要になる。
この Objective では、Memory / Skill catalog について以下の platform contract だけを維持する。
この Objective では、Memory / Knowledge について以下の platform contract だけを維持する。
- Memory は Control plane が扱う record だが、Ticket / Artifact の authority を置き換えない。
- Skill catalog は Workspace backend が扱う prompt/resource catalog だが、Ticket / Worker / workdir / queue の authority を持たない。
- 将来、Memory と Skill catalog の canonical storage / API は Workspace control plane 側に置く。
- local `.yoi` memory と `.yoi/skills` は compatibility、offline/export/import、local projection、migration bridge として扱う。
- Personal Memory、Workspace Memory、Worker Summary、Skill catalog は分離が必要である。
- Memory / Knowledge は Control plane が扱う record だが、Ticket / Artifact の authority を置き換えない。
- 将来、Memory / Knowledge の canonical storage は Workspace control plane 側に置く。
- local `.yoi` memory は compatibility、offline/export/import、local projection、migration bridge として扱う。
- Personal Memory、Workspace Memory、Worker Summary、Maintained Knowledge は分離が必要である。
- Generated Memory には provenance、visibility、approval、audit が必要である。
- Runtime / Worker に渡した Memory / Skill context は、将来 ContextPack などとして Artifact/evidence に記録できる必要がある。
- Runtime / Worker に渡した Memory/Knowledge context は、将来 ContextPack などとして Artifact/evidence に記録できる必要がある。
本格的な Memory 再設計は、Memory の保存先を Workspace backend / control plane record に移すタイミングで回収する。それまでは低リスクな観察、問題例の収集、既存 local memory の互換維持に留める。
@ -201,7 +200,7 @@ Desktop app は対応コストが高いので、まず Web frontend を primary
- TUI/local panel: fallback、dogfooding surface。
- Future desktop: Web/control-plane model が安定した後に検討する optional client。
Web UI は Ticket、Objective、Memory、Skill catalog、Runtime、Worker、Artifact を扱う。UI の都合で正本を二重化しない。
Web UI は Ticket、Objective、Memory、Knowledge、Runtime、Worker、Artifact を扱う。UI の都合で正本を二重化しない。
### 7. 多重起動コストと runtime placement を見直す
@ -227,17 +226,17 @@ Worker の一元管理、データ永続化、アーカイブは将来的には
## Initial phases / candidate tickets
1. **Vocabulary / architecture record**
- Workspace / RepositoryId / RepositorySelector / RepositoryPoint / working directory / Runtime / Worker / Control Plane / Ticket / Memory の用語と境界を固める。
- Workspace / RepositoryId / RepositorySelector / RepositoryPoint / working directory / Runtime / Worker / Control Plane / Ticket / Memory / Knowledge の用語と境界を固める。
2. **Team-space canonical data model**
- Ticket / Objective / Target / Artifact / Actor / Permission / Audit / Memory の entity/event model を設計する。
- Ticket / Objective / Target / Artifact / Actor / Permission / Audit / Memory / Knowledge の entity/event model を設計する。
3. **Ticket evidence model**
- Ticket lifecycle、WorkerRef、Artifact、validation evidence、review evidence、Ticket thread の責務を明確化する。
4. **Memory storage migration boundary**
- Memory の本格再設計は後回しにし、まずは Workspace backend に移す時の platform contract、compatibility/cache/export 方針、将来の provenance / visibility / approval 要件だけを固定する。
- Memory / Knowledge の本格再設計は後回しにし、まずは Workspace backend に移す時の platform contract、compatibility/cache/export 方針、将来の provenance / visibility / approval 要件だけを固定する。
5. **Control plane backend architecture**
- local `.yoi` backend と server-side canonical backend の境界、migration/export/import、compatibility mode を設計する。
6. **Web control plane MVP design**
- read-only Ticket / Objective / Memory / Runtime / Worker state UI/API の範囲を決める。
- read-only Ticket / Objective / Memory / Knowledge / Runtime / Worker state UI/API の範囲を決める。
7. **Local Runtime protocol design**
- Web/control plane から local Runtime に安全な操作を送り、Runtime が Worker lifecycle と working directory materialization を担う protocol と authority boundary を設計する。
8. **Repository and working directory materialization model**
@ -259,16 +258,16 @@ Worker の一元管理、データ永続化、アーカイブは将来的には
## Success criteria / exit conditions
- Workspace / RepositoryId / RepositorySelector / RepositoryPoint / working directory / Runtime / Worker / Control Plane / Ticket / Memory の境界が文書化されている。
- Workspace / RepositoryId / RepositorySelector / RepositoryPoint / working directory / Runtime / Worker / Control Plane / Ticket / Memory / Knowledge の境界が文書化されている。
- Ticket が team coordination record として、target selector / Artifact / Actor / Permission / Audit と分離された model を持つ。
- `.yoi` local backend は compatibility/local backend として整理され、server-side canonical backend の設計を阻害しない。
- Web UI/API が Ticket / Objective / Runtime / Worker state を中心とした read-only view を提供できる設計または MVP を持つ。Memory は既存 record の表示または将来 placeholder に留め、本格再設計をこの段階の必須条件にしない。
- Web UI/API が Ticket / Objective / Runtime / Worker state を中心とした read-only view を提供できる設計または MVP を持つ。Memory / Knowledge は既存 record の表示または将来 placeholder に留め、本格再設計をこの段階の必須条件にしない。
- Control plane から local Runtime に対して、現在のローカル管理画面相当の安全な操作を実行できる design/protocol がある。
- Runtime は single Workspace / Git repository root 専用 process ではなく、sandbox/authority が成立すれば複数 Workspace / Repository の Worker を抱えられる execution substrate として設計されている。
- Git Repository root に依存しない Workspace model があり、Git Repository は Repository provider の一種として扱われている。
- Ticket と Objective は Workspace 配下に平たく存在し、Repository への所属ではなく RepositoryId / RepositorySelector / path scope / intent で対象を表現する。
- Git worktree 相当は working directory materialization strategy として扱われ、Artifact/evidence が concrete RepositoryPoint を記録する。
- Memory は Ticket / Artifact の authority を置き換えない record として platform contract だけを持つ。本格的な意味論・抽出・承認・検索・staleness 処理は、Memory の保存先を Workspace backend / control plane record に移すタイミングで回収する。
- Memory / Knowledge は Ticket / Artifact の authority を置き換えない record として platform contract だけを持つ。本格的な意味論・抽出・承認・検索・staleness 処理は、Memory の保存先を Workspace backend / control plane record に移すタイミングで回収する。
- Hosted Runtime / resource allocation / SaaS offering に進むための後続 Ticket が切れる状態になっている。
- 既存 local dogfooding runtime を壊さず、local use と remote-capable architecture が両立している。

View File

@ -2,13 +2,13 @@
title: "効果的な Memory システム設計・検証"
state: "active"
created_at: "2026-06-20T15:16:00Z"
updated_at: "2026-07-15T21:18:00Z"
updated_at: "2026-06-20T15:16:00Z"
linked_tickets: ["00001KSKBPHRG", "00001KT02TCCG", "00001KTGCAFXG", "00001KSKBPTHR"]
---
## Goal
Yoi の Memory / generated memory / resident context / retrieval / usage metrics を、実際の開発・設計・レビュー・オーケストレーションに効く sensemaking substrate として再設計・検証する。Knowledge は separate record kind として削除する方針であり、この Objective では Memory と authority record / Skill / docs の境界を再整理する。
Yoi の Memory / Knowledge / generated memory / resident context / retrieval / usage metrics を、実際の開発・設計・レビュー・オーケストレーションに効く sensemaking substrate として再設計・検証する。
この Objective でいう「効果的な Memory システム」は、単に多く保存する仕組みではなく、作業中の問いに対して relevant material を集め、根拠を検証可能にし、再表現・仮説形成・反証探索・意思決定・成果物への反映を低コストにする仕組みである。
@ -41,9 +41,9 @@ Yoi の現行 Memory は、この流れのうち「保存」と「一部の検
- Ticket / task / question ごとの shoebox がない。
- shoebox から evidence snippets を切り出し、source / provenance / applicability / confidence と共に扱う evidence file がない。
- `summary`, `decision`, `request` は durable memory storage taxonomy であり、sensemaking 用 schema としては粗い。Knowledge record kind は削除方針なので、再利用可能な手順は Skill、保守された設計資料は docs / Ticket decisions に寄せる。
- `summary`, `decision`, `request`, `knowledge` storage taxonomy であり、sensemaking 用 schema としては粗い。
- decision は残るが、hypothesis space、alternative、rejected reason、disconfirming evidence が残りにくい。
- reviewer / orchestrator が confirmation bias を避けるための反証探索導線が弱い。関連する手順誘導は旧 Workflow ではなく Skill と role prompt / typed tools へ寄せる。
- reviewer / orchestrator が confirmation bias を避けるための反証探索導線が弱い。
- resident exposure と explicit retrieval は観測できても、Memory が product に効いたかは測りにくい。
この Objective は、Memory 関連の設計・検証・検討・考察を一元化し、個別 Ticket がばらばらに storage、prompt、retrieval、metrics を改善して再び墓場を増やすことを防ぐための判断背景である。
@ -115,15 +115,15 @@ Memory が prompt に入った、または query されたことは成功では
- Ticket routing 用 Memory shoebox artifact を試作する。
- evidence snippet schema / source resolver を設計する。
- hypothesis / rejected alternative / disconfirming evidence の表現を追加する。
- Reviewer Skill / review process に反証探索を入れる。
- Reviewer workflow に反証探索を入れる。
- Memory usage metrics を product impact oriented に拡張する。
- stale / contradiction / renewal の検出・表示を設計する。
## Success criteria / exit conditions
- Memory システムの目的が「保存」ではなく「sensemaking loop 支援」として project records / docs / prompts / Skills で一貫して説明されている。
- Memory システムの目的が「保存」ではなく「sensemaking loop 支援」として project records / docs / prompts / workflows で一貫して説明されている。
- Pirolli & Card の `shoebox -> evidence file -> schema -> hypotheses -> product` に対応する Yoi 内の責務と非責務が整理されている。
- Ticket / Objective / docs / session logs / Memory / Skills の authority boundary が明確で、Memory が authority を僭称せず、Skill は手順資源とて外部状態 authority を持たない。
- Ticket / Objective / docs / session logs / Memory / Knowledge の authority boundary が明確で、Memory が authority を僭称しない。
- 少なくとも一つの実作業 routing / review / design analysis で、task-bound shoebox または evidence file が生成・利用され、作業品質にどう効いたかが確認されている。
- Memory records または関連 artifacts が source / provenance / applicability / staleness / supports-or-refutes のいずれかを扱えるようになっている。
- Reviewer / Orchestrator が supporting evidence だけでなく、contradicting evidence / stale assumptions / rejected alternatives を探す導線を持っている。
@ -131,7 +131,7 @@ Memory が prompt に入った、または query されたことは成功では
- 古い Memory が放置されるのではなく、stale / superseded / contradicted / needs-review として扱える方針がある。
- 後続の実装 Ticket が concrete slice として分割され、Objective が Ticket dependency や進捗 container として使われていない。
この Objective は、Memory が少なくとも一つの中規模設計・実装・レビュー作業で「関連情報を見つける」「根拠を確認する」「代替案/反証を検討する」「成果物へ反映する」流れを実証し、その設計方針が docs / Skills / metrics に反映された時点で `done` を検討できる。
この Objective は、Memory が少なくとも一つの中規模設計・実装・レビュー作業で「関連情報を見つける」「根拠を確認する」「代替案/反証を検討する」「成果物へ反映する」流れを実証し、その設計方針が docs / workflows / metrics に反映された時点で `done` を検討できる。
## Decision context
@ -141,8 +141,8 @@ Memory が prompt に入った、または query されたことは成功では
- Memory は durable project authority ではない。Ticket、docs、git history、session logs、明示 user instruction の代替として使わない。
- Objective context は判断背景であり、個別実装の authority は各 Ticket body/thread/artifacts と明示的な Ticket relations / OrchestrationPlan records にある。
- `history` に残らない context-only injection を改善案にしない。新しい context input は history に commit する原則を守る。
- Knowledge record kind は削除方針。再利用可能な手順・作法は Agent Skills (`.yoi/skills/<skill>/SKILL.md`) へ、durable policy/rationale は Memory decisions / maintained docs / Ticket decisions へ、外部状態 authority は typed feature/tool surface へ分ける。
- Generated memory / Ticket / docs / report / Skill の境界を再定義する場合は、authority boundary と migration/staleness を明示する。
- Knowledge は単なる長期保存ではなく、再利用可能な schema / model / procedure / invariant として再検討する余地がある。
- Generated memory / curated Knowledge / Ticket / docs / report の境界を再定義する場合は、authority boundary と migration/staleness を明示する。
- 関連する既存 Ticket:
- `00001KSKBPHRG` — Prompt / Workflow 評価メトリクスと改善 Offer
- `00001KT02TCCG` — Memory prompt: conditional guidance and proactive lookup
@ -183,9 +183,9 @@ Yoi 初期設計では、これを参考に以下を意図していた。
- activity token 閾値で extract を発火する。
- compact より前に session log range を抽出する。
- extract は `decisions`, `discussions`, `attempts`, `requests` などの候補を staging に保存する。
- 抽出時点では durable policy / Skill / docs へ早期分類せず、純粋な「起きたこと」に寄せる。
- consolidation が summary / decisions / requests と、必要に応じた docs / Skill / Ticket decision 更新候補を整理する。
- consolidation 入力に linter warnings / usage metrics / stale cleanup 候補を含める。
- 抽出時点では Knowledge 化せず、純粋な「起きたこと」に寄せる。
- consolidation が summary / decisions / requests / knowledge candidates を整理する。
- consolidation 入力に linter warnings / usage metrics / Knowledge 化候補を含める。
- stale / superseded / unused / noisy な情報を整理する。
この Objective での再解釈:
@ -224,7 +224,7 @@ HermesAgent で特に重要だった点:
この Objective での再解釈:
- HermesAgent の `MEMORY.md` / `USER.md` / `skills` の分離は、Yoi の Memory / Skills / prompt resources / docs / Ticket decision / generated memory の責務再整理に使える。
- HermesAgent の `MEMORY.md` / `USER.md` / `skills` の分離は、Yoi の Knowledge / Workflow / prompt resource / docs / Ticket decision / generated memory の責務再整理に使える。
- reusable procedure, reviewer focus, orchestration tactic, project preference, user preference, design invariant を同じ Memory bucket に入れると墓場化しやすい。
- `Nothing to save.` / empty extraction allowed は重要だが、保存抑制だけでは効果的な Memory にはならない。保存されたものが task-bound shoebox / evidence / schema / hypothesis / product に接続される必要がある。
- frozen snapshot / prompt cache 配慮は Yoi の history/context 加工原則と整合するが、それだけでは retrieval / resurfacing / disconfirmation は解決しない。

View File

@ -1,335 +0,0 @@
---
created_at: "2026-07-15T21:33:00Z"
objective: "00001KVJSMQXZ"
status: "architecture-draft"
notes: "Draft architecture for redesigning Yoi Memory as a sensemaking substrate. This is an Objective resource, not implementation authority."
---
# Memory architecture overview
## Position
Yoi Memory should be redesigned as a **sensemaking substrate** rather than a larger persistent note store. The core architecture should help an agent gather task-relevant material, preserve evidence and provenance, build intermediate representations, test hypotheses, and feed outcomes back into Tickets, reviews, docs, Skills, and implementation decisions.
This document intentionally assumes the current Memory/Knowledge implementation can be redesigned. Existing `summary` / `decision` / `request` records, extraction, consolidation, resident context, and memory tools are useful historical inputs, but they should not constrain the target model when they conflict with the sensemaking architecture.
## Design constraints
- Memory is not project authority.
- Tickets, docs, git history, session logs, explicit user instructions, Workspace records, and typed feature/tool results remain authority.
- Memory provides evidence indexes, working sets, synthesis, reminders, and candidate updates.
- Knowledge is not a separate target record kind.
- Reusable procedures belong in Skills.
- Maintained policies and design rationale belong in docs, Ticket decisions, Objective resources, or explicit Memory decisions depending on authority.
- Workflow tracking is not part of Memory.
- Procedure guidance belongs in Skills / role prompts.
- External state transitions belong in typed feature/tool surfaces.
- Context-only injection is forbidden.
- Any new model-visible memory/shoebox/evidence context must be appended to Worker history or represented through explicit tool results/artifacts.
- Workspace backend should become the shared authority for Memory-related control-plane APIs.
- Local `.yoi/memory` can remain compatibility/offline storage while the architecture is proven.
## Reference model
The target architecture follows Pirolli & Card's sensemaking process:
```text
External data sources
-> task-bound shoebox
-> evidence file
-> schemas / representations
-> hypotheses
-> product
-> product feedback / memory maintenance
```
Yoi should optimize the loop, not just the final durable storage.
## Core concepts
### 1. External sources
External sources are authoritative or semi-authoritative material that Memory can point to but should not replace.
Examples:
- Ticket item/thread/resolution/artifacts.
- Objective item/resources.
- Git commits, diffs, branches, worktrees.
- Session logs and Worker transcripts.
- Docs and reports.
- Code references.
- Runtime/Workspace API records.
- Explicit user messages.
- Existing Memory records.
- Skill metadata and `SKILL.md` content when procedural context matters.
External sources should be addressed through source refs, not copied wholesale into Memory.
### 2. Task-bound shoebox
A shoebox is a bounded working set of potentially relevant material for a concrete question, Ticket, Objective, review, or design task.
Properties:
- Scoped to a task/question.
- Contains source refs plus short rationale for inclusion.
- Can include both likely supporting and likely contradicting material.
- Does not assert conclusions.
- Is disposable or artifact-like; it is not necessarily durable memory.
Example uses:
- Orchestrator asks for context before routing a queued Ticket.
- Reviewer asks for prior decisions and contradictory evidence for a change.
- Designer asks for relevant reports, tickets, commits, and old memory before writing architecture.
A shoebox is the first concrete slice to prototype because it directly addresses Memory graveyard behavior: information must gather around the current question.
### 3. Evidence file
An evidence file extracts snippets or observations from a shoebox.
Each evidence item should carry:
- source ref;
- quoted or summarized snippet;
- source location/anchor if available;
- why it matters;
- supports / refutes / contextualizes relation;
- applicability scope;
- confidence;
- staleness or supersession markers;
- extractor identity/time.
Evidence files are not final decisions. They are structured working material for reasoning.
### 4. Representation / schema layer
The representation layer reorganizes evidence into forms that reduce reasoning cost.
Possible representations:
- timeline;
- subsystem map;
- authority boundary map;
- invariant list;
- risk list;
- decision table;
- hypothesis table;
- alternative/rejected alternative table;
- contradiction/staleness report;
- review checklist;
- dependency graph.
Representations should be attached to a task or Objective as artifacts/resources before becoming durable Memory records.
### 5. Hypotheses and alternatives
Memory should help track not only final conclusions but also competing interpretations.
A hypothesis record or artifact should be able to express:
- claim;
- supporting evidence;
- contradicting evidence;
- open questions;
- rejected alternatives;
- decision threshold;
- current status: proposed / accepted / rejected / stale / superseded.
This is the main anti-confirmation-bias layer. Reviewer and Orchestrator Skills should explicitly ask agents to seek disconfirming evidence, but the data structure should make that evidence easy to preserve.
### 6. Product feedback
The loop must end in a product. A memory-driven activity should normally produce or update one of:
- Ticket decision/comment/implementation report/review;
- Objective resource;
- design doc/report;
- Skill update;
- code implementation or validation evidence;
- explicit Memory decision/request update;
- stale/superseded marker on old memory.
Memory that is collected but never changes a product should be considered low-value unless it is intentionally kept as a temporary shoebox/evidence artifact.
## Storage layers
### Compatibility layer: current `.yoi/memory`
Keep only the minimum durable Memory record kinds needed during transition:
- summary;
- decision;
- request;
- audit/extraction logs as implementation artifacts.
Do not reintroduce Knowledge as a separate kind.
Existing generated Memory can continue to serve as background context, but new architecture should not optimize around resident memory injection first.
### Workspace control-plane layer
Target authority should move toward Workspace backend APIs for:
- memory catalog/search;
- task shoebox creation/list/show;
- evidence file creation/list/show;
- artifact/source refs;
- staleness/supersession markers;
- product-impact metrics;
- memory maintenance diagnostics.
This mirrors the direction already chosen for Tickets and Skills: Workers should not maintain divergent local interpretations when `WorkspaceClient::Http` is available.
### Artifact/resource layer
Before inventing new durable schemas, prefer Objective/Ticket resources or artifacts for prototypes:
```text
.yoi/objectives/<objective-id>/resources/<name>.md
.yoi/tickets/<ticket-id>/artifacts/<name>.json|md
```
This keeps early experiments inspectable and avoids overfitting storage before the architecture is proven.
## Runtime/API architecture
### Memory service responsibilities
A future Workspace Memory service should provide typed operations for:
- `MemorySourceSearch`: find candidate source refs across Tickets, Objectives, docs, sessions, code, reports, and existing Memory.
- `ShoeboxCreate`: create a task-bound working set from query/task/source refs.
- `ShoeboxShow`: render a bounded shoebox for an agent.
- `EvidenceExtract`: extract evidence items from a shoebox or selected source refs.
- `EvidenceShow`: render evidence with provenance and support/refute relations.
- `RepresentationCreate`: create tables/maps/timelines/hypothesis matrices from evidence.
- `MemoryCandidatePropose`: propose durable Memory/doc/Ticket/Skill updates based on artifacts.
- `MemoryImpactRecord`: record when memory/evidence influenced a product.
- `MemoryStalenessMark`: mark records as stale/superseded/contradicted/needs-review.
These should be typed feature/tool surfaces, not hidden prompt injection.
### Worker interaction
Workers should interact with Memory through explicit tools/API results:
- A Worker asks for a shoebox or evidence file.
- The result is committed as a tool result or artifact reference.
- If the Worker needs additional source material, it explicitly reads/fetches it.
- If a result should influence future work, the Worker writes a Ticket comment, Objective resource, doc update, Skill update, or Memory candidate.
Resident context may still include high-signal summary, but it should not be the primary product of the Memory system.
### Prompt/context behavior
- Shoebox and evidence outputs should be bounded.
- Large source material should remain referenced, not inlined.
- If a generated summary or evidence bundle is used for reasoning, it must be visible in history/tool output/artifact.
- Context should distinguish source quote, extractor summary, and model inference.
## Metrics
Memory metrics should distinguish exposure from impact.
Low-level events:
- resident exposure;
- explicit query;
- shoebox created;
- evidence extracted;
- representation created;
- source opened/read;
- stale marker created.
Product-impact events:
- cited in Ticket comment/review/resolution;
- changed implementation plan;
- changed review outcome;
- changed acceptance criteria or requirement;
- caused docs/Skill update;
- invalidated stale assumption;
- avoided duplicate work;
- reduced time-to-evidence.
The architecture should not treat retrieval count alone as success.
## Relationship to Skills
Skills are procedural guidance. They can instruct agents to use Memory tools, ask for disconfirming evidence, or produce a dossier, but they do not own external state.
Examples:
- Reviewer Skill says: request contradicting evidence before approving.
- Orchestrator Skill says: create a Ticket shoebox before routing a risky Ticket.
- Coder Skill says: cite relevant prior decisions in the final dossier.
The Memory service provides the data and artifacts those Skills ask for.
## Relationship to Tickets/Objectives
Tickets and Objectives remain the primary durable work-management records.
- Ticket body/thread/artifacts define work authority and evidence for implementation.
- Objective item/resources define long-running design context.
- Memory can point to them, synthesize across them, and propose updates, but does not replace them.
A practical rule: if a statement changes what should be built or reviewed, it should appear in Ticket/Objective/docs, not only in Memory.
## Initial implementation slices
Do not start by redesigning every Memory record. Split into small Tickets after this architecture is accepted.
Recommended order:
1. **Task-bound shoebox artifact prototype**
- Given a Ticket or Objective and a question, collect source refs and short rationales.
- Store as Ticket artifact or Objective resource.
2. **Evidence file schema prototype**
- Extract snippets from shoebox refs with source/provenance/support-refute metadata.
- Keep as artifact/resource first.
3. **Contradiction/staleness markers**
- Mark existing Memory decisions or source refs as stale/superseded/contradicted.
- Avoid deleting first; make staleness visible.
4. **Reviewer/Orchestrator Skill integration**
- Update Skills to ask for shoebox/evidence before risky review/routing.
- This tests whether the architecture changes product quality.
5. **Workspace Memory API sketch**
- Add read-only catalog/search and shoebox/evidence endpoints once artifact prototypes stabilize.
6. **Product-impact metrics**
- Record when evidence is cited in Ticket/review/docs and when it changes outcomes.
## Non-goals for the first redesign
- Recreating Knowledge under another name.
- Making Memory the canonical source for Ticket requirements or design decisions.
- Automatically rewriting docs/Skills/Tickets from extracted memory without explicit review.
- Building a general vector database before task-bound evidence flows are proven.
- Optimizing resident prompt stuffing before explicit shoebox/evidence usefulness is validated.
- Creating a hidden context channel that bypasses Worker history.
## Open decisions
- Whether shoebox/evidence should start as Ticket artifacts, Objective resources, or a new Workspace Memory record type.
- How much session-log search belongs in the first slice.
- Whether source refs need a shared URI scheme across Ticket/Objectives/docs/git/session/code.
- Whether Memory tools live in `worker` feature space first or are exposed only via Workspace backend APIs.
- How to represent confidence/staleness without encouraging false precision.
- What exact product-impact events are worth recording in the first implementation.
## Exit criteria for this architecture phase
This architecture is ready to split into Tickets when:
- the core flow `source -> shoebox -> evidence -> representation/hypothesis -> product` is accepted;
- storage boundaries between Memory, Ticket, Objective, docs, and Skills are accepted;
- the first prototype slice is chosen;
- non-goals are accepted so implementation does not re-create Knowledge or Workflow tracking.

View File

@ -1,137 +0,0 @@
---
source_url: "https://andymatuschak.org/files/papers/Pirolli%2C%20Card%20-%202005%20-%20The%20sensemaking%20process%20and%20leverage%20points%20for%20analyst%20technology%20as.pdf"
fetched_at: "2026-07-15T21:24:00Z"
content_type: "application/pdf"
notes: "Untrusted external reference captured as local Objective resource for Memory sensemaking design. This is a summary/extraction for design discussion, not project authority."
---
# Pirolli & Card (2005): The sensemaking process and leverage points for analyst technology
## Citation / source
Peter Pirolli and Stuart Card, PARC. "The Sensemaking Process and Leverage Points for Analyst Technology as Identified Through Cognitive Task Analysis" (2005).
Source PDF: <https://andymatuschak.org/files/papers/Pirolli%2C%20Card%20-%202005%20-%20The%20sensemaking%20process%20and%20leverage%20points%20for%20analyst%20technology%20as.pdf>
## Core model
The paper frames intelligence analysis as a sensemaking task:
```text
Information -> Schema -> Insight -> Product
```
The analyst transforms raw data into progressively more structured representations so expertise can apply and so results can be communicated.
The paper's notional data flow is especially relevant to Yoi Memory design:
```text
External data sources
-> shoebox
-> evidence file
-> schemas
-> hypotheses
-> presentation / work product
```
- **External data sources**: raw material, mostly text in the studied setting.
- **Shoebox**: the smaller subset collected as relevant for the task.
- **Evidence file**: extracted snippets / nuggets from the shoebox, plus low-level inferences.
- **Schemas**: re-representations that organize information for analysis.
- **Hypotheses**: tentative conclusions with supporting or disconfirming evidence.
- **Product**: report / presentation / action suited for communication.
## Two major loops
The process has two interacting loops rather than a simple linear pipeline.
### Foraging loop
Activities aimed at finding and selecting information:
- search and filter external data sources;
- collect potentially relevant material into a shoebox;
- read and extract evidence snippets;
- follow up on questions generated by extracted evidence.
The paper highlights the exploration / enrichment / exploitation tradeoff:
- **Exploration**: monitor or search more of the information space; increases recall.
- **Enrichment**: narrow the collected set into smaller, higher-precision subsets.
- **Exploitation**: read/extract/analyze the chosen material more thoroughly.
Analyst tooling can help by changing the cost structure of search, scanning, assessment, selection, attention shifting, and follow-up searches.
### Sensemaking loop
Activities aimed at structuring and reasoning:
- schematize evidence;
- build a case;
- generate / manage hypotheses;
- marshal evidence for and against hypotheses;
- tell a story / produce a report;
- re-evaluate based on feedback or new evidence.
The paper emphasizes opportunistic mixing of bottom-up and top-down processing:
- **Bottom-up**: data triggers schemas, relations, hypotheses, and products.
- **Top-down**: hypotheses / client feedback trigger new searches, re-reading, and re-organization.
## Leverage points
### Foraging loop leverage
- Cost structure of exploration / enrichment / exploitation.
- Cost structure of scanning, recognizing, and selecting items for attention.
- Cost of shifting attentional control to a new domain or task.
- Cost of follow-up searches generated by extracted information.
- Broad-band low-fidelity assessment plus narrow-band high-fidelity processing is a useful design pattern.
### Sensemaking loop leverage
- Span of attention for evidence, hypotheses, and evidentiary relations.
- Generation of alternative hypotheses.
- Confirmation bias and failure to seek disconfirming evidence.
- External representations can expand working memory for evidence/hypothesis structures.
- Tools should help distribute attention toward diagnostic evidence and disconfirming relations.
## Implications for Yoi Memory Objective
This paper directly supports the Objective's direction that effective Memory is not just durable storage or retrieval count.
Useful design implications:
1. **Task-bound shoebox**
- For each Ticket / Objective / question, provide a bounded collection of potentially relevant materials.
- Include provenance and why each item was collected.
2. **Evidence file**
- Extract snippets from source material with source, applicability, confidence, and context.
- Evidence should be usable for support and disconfirmation, not only recall.
3. **Schema / representation layer**
- The system should help create intermediate structures: timelines, entity/relation maps, alternatives, checklists, hypothesis spaces, decision tables.
- These are separate from raw Memory records.
4. **Hypotheses and alternatives**
- Record competing explanations, rejected alternatives, open questions, and evidence gaps.
- Avoid only storing final decisions.
5. **Disconfirming evidence**
- Reviewer / Orchestrator support should explicitly search for contradiction and diagnostic evidence.
- This belongs in Skill guidance and typed review tooling, not in a separate Knowledge store.
6. **Metrics**
- Measure whether Memory changes product quality, review quality, decision quality, or time-to-evidence.
- Avoid treating exposure/retrieval counts alone as success.
7. **Product connection**
- The loop should end in a product: Ticket decision, implementation report, review, design doc, Skill update, or validated artifact.
- Memory that never reaches a product is likely a graveyard.
## Boundary with current Yoi direction
- Knowledge as a separate record kind is being removed; this paper's reusable representations should map to Memory artifacts, Ticket artifacts, maintained docs, or Skills depending on authority.
- Workflow tracking is being removed; procedural guidance such as "generate alternatives" or "seek disconfirming evidence" should live in Skills / role prompts and be enforced by typed tools where authority is needed.
- Workspace backend should eventually be the authority for task-bound shoebox / evidence artifacts when Memory moves from local compatibility storage to control-plane records.

View File

@ -1,8 +1,8 @@
---
title: 'Type Workdir cleanup plan status'
state: 'closed'
state: 'done'
created_at: '2026-07-13T12:45:21Z'
updated_at: '2026-07-15T16:19:30Z'
updated_at: '2026-07-13T12:56:55Z'
assignee: null
queued_by: 'yoi ticket'
queued_at: '2026-07-13T12:46:05Z'

View File

@ -1,3 +0,0 @@
Ticket `00001KXDR5EK4` (`Type Workdir cleanup plan status`) はすでに `state: done` に到達していたため、workspace Dashboard から close しました。
この Close action によって、実装作業、state 変更、Orchestrator/Companion launch、worker invocation は開始されていません。

View File

@ -73,24 +73,4 @@ Validation:
State changed to `done`.
---
<!-- event: state_changed author: hare at: 2026-07-15T16:19:30Z from: done to: closed reason: closed field: state -->
## State changed
Ticket を closed にしました。
---
<!-- event: close author: hare at: 2026-07-15T16:19:30Z status: closed -->
## 完了
Ticket `00001KXDR5EK4` (`Type Workdir cleanup plan status`) はすでに `state: done` に到達していたため、workspace Dashboard から close しました。
この Close action によって、実装作業、state 変更、Orchestrator/Companion launch、worker invocation は開始されていません。
---

View File

@ -1,8 +1,8 @@
---
title: 'Stabilize cleanup plan ordering'
state: 'closed'
state: 'done'
created_at: '2026-07-13T13:29:51Z'
updated_at: '2026-07-15T16:19:29Z'
updated_at: '2026-07-13T13:38:25Z'
assignee: null
queued_by: 'yoi ticket'
queued_at: '2026-07-13T13:30:39Z'

View File

@ -1,3 +0,0 @@
Ticket `00001KXDTPXFD` (`Stabilize cleanup plan ordering`) はすでに `state: done` に到達していたため、workspace Dashboard から close しました。
この Close action によって、実装作業、state 変更、Orchestrator/Companion launch、worker invocation は開始されていません。

View File

@ -79,24 +79,4 @@ Validation:
State changed to `done`.
---
<!-- event: state_changed author: hare at: 2026-07-15T16:19:29Z from: done to: closed reason: closed field: state -->
## State changed
Ticket を closed にしました。
---
<!-- event: close author: hare at: 2026-07-15T16:19:29Z status: closed -->
## 完了
Ticket `00001KXDTPXFD` (`Stabilize cleanup plan ordering`) はすでに `state: done` に到達していたため、workspace Dashboard から close しました。
この Close action によって、実装作業、state 変更、Orchestrator/Companion launch、worker invocation は開始されていません。
---

View File

@ -1,8 +1,8 @@
---
title: 'Stop Worker before cleanup deletion'
state: 'closed'
state: 'inprogress'
created_at: '2026-07-13T13:58:15Z'
updated_at: '2026-07-15T16:19:27Z'
updated_at: '2026-07-13T13:58:54Z'
assignee: null
queued_by: 'yoi ticket'
queued_at: '2026-07-13T13:58:54Z'

View File

@ -1,3 +0,0 @@
Ticket `00001KXDWAXTS` (`Stop Worker before cleanup deletion`) はすでに `state: done` に到達していたため、workspace Dashboard から close しました。
この Close action によって、実装作業、state 変更、Orchestrator/Companion launch、worker invocation は開始されていません。

View File

@ -39,65 +39,4 @@ Ticket を `yoi ticket` が queued にしました。
State changed to `inprogress`.
---
<!-- event: implementation_report author: hare at: 2026-07-13T14:05:35Z -->
## Implementation report
Changed Worker cleanup execution to stop the selected Worker before Runtime deletion.
Root cause:
- Runtime `delete_worker` rejects active Workers with `worker <id> is running and must be stopped before deletion`.
- The cleanup plan can present a Worker as cleanup-eligible when the browser-facing projected state is not `running` (for example execution rejected/unconnected) while Runtime status is still active.
- Cleanup execution previously called Runtime delete directly, causing `workspace_cleanup_worker_runtime_delete_rejected`.
Changes:
- `cleanup_runtime_worker_for_execution` now calls `stop_worker` first with a cleanup reason.
- If stop is rejected, the cleanup fails with `workspace_cleanup_worker_runtime_stop_rejected` and preserves Runtime diagnostics.
- Only after accepted stop does cleanup call Runtime delete and then delete the Backend registry row.
- Unknown-worker during stop/delete remains idempotent for stale Backend registry cleanup.
Manual observation:
- Direct Runtime DELETE for worker 1 returned HTTP 400: `worker 1 is running and must be stopped before deletion`.
- Direct Runtime stop followed by delete succeeded, confirming the required operation sequence.
Validation:
- `cargo fmt --check`
- `git diff --check`
- `cargo check -q`
- `cargo test -q -p yoi-workspace-server`
- targeted cleanup execution test
- `nix build .#yoi --no-link`
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-13T14:05:35Z from: inprogress to: done reason: cli_state field: state -->
## State changed
State changed to `done`.
---
<!-- event: state_changed author: hare at: 2026-07-15T16:19:27Z from: done to: closed reason: closed field: state -->
## State changed
Ticket を closed にしました。
---
<!-- event: close author: hare at: 2026-07-15T16:19:27Z status: closed -->
## 完了
Ticket `00001KXDWAXTS` (`Stop Worker before cleanup deletion`) はすでに `state: done` に到達していたため、workspace Dashboard から close しました。
この Close action によって、実装作業、state 変更、Orchestrator/Companion launch、worker invocation は開始されていません。
---

View File

@ -1,29 +0,0 @@
---
title: 'Render console snapshot entries'
state: 'closed'
created_at: '2026-07-13T14:17:43Z'
updated_at: '2026-07-15T16:19:27Z'
assignee: null
queued_by: 'yoi ticket'
queued_at: '2026-07-13T14:18:30Z'
---
## 背景
Browser Console は Worker observation WebSocket から connect-time `snapshot` event を受け取っているが、`snapshot.data.entries` を conversation surface に投影していない。そのため reconnect / restore 後、snapshot 自体は届いていても console が空に見える。現在は snapshot の status と in-flight blocks だけを使っている。
Protocol の `Event::Snapshot.entries` は session-store `LogEntry` JSON であり、bulk reconstruction lane として UI が派生 view を seed するためのデータである。Browser Console でも TUI と同様に snapshot entries から committed conversation rows を復元する。
## 要件
- Browser Console projection が `snapshot.data.entries` を render する。
- `SegmentStart.history` も replay し、fork/restore/compaction 後の seed history を表示する。
- UserInput / assistant message / reasoning / tool call / tool result を既存 ConsoleLine に変換する。
- Snapshot の in-flight block 表示は維持する。
- 不明な snapshot entry は error にせず無視する。
## 受け入れ条件
- snapshot entries だけで user / assistant / tool rows が console に表示される。
- snapshot in-flight blocks は引き続き streaming line として表示される。
- frontend tests が通る。

View File

@ -1,3 +0,0 @@
Ticket `00001KXDXEJQS` (`Render console snapshot entries`) はすでに `state: done` に到達していたため、workspace Dashboard から close しました。
この Close action によって、実装作業、state 変更、Orchestrator/Companion launch、worker invocation は開始されていません。

View File

@ -1,97 +0,0 @@
<!-- event: create author: "yoi ticket" at: 2026-07-13T14:17:43Z -->
## 作成
LocalTicketBackend によって作成されました。
---
<!-- event: intake_summary author: hare at: 2026-07-13T14:18:30Z -->
## Intake summary
Marked ready by `yoi ticket state`.
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-13T14:18:30Z from: planning to: ready reason: cli_state field: state -->
## State changed
Marked ready by `yoi ticket state`.
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-13T14:18:30Z from: ready to: queued reason: queued field: state -->
## State changed
Ticket を `yoi ticket` が queued にしました。
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-13T14:18:30Z from: queued to: inprogress reason: cli_state field: state -->
## State changed
State changed to `inprogress`.
---
<!-- event: implementation_report author: hare at: 2026-07-13T14:30:37Z -->
## Implementation report
Implemented console rendering for WebSocket snapshot entries.
Root cause:
- Browser Console consumed `snapshot.status` and `snapshot.in_flight`, but ignored `snapshot.entries`.
- After reconnect/restore the WebSocket snapshot arrived, but committed conversation history was not projected into console rows, so the console appeared empty without an error.
Changes:
- `projectConsole` now rebuilds committed console rows from `snapshot.data.entries`.
- `SegmentStart.history` is replayed so seed history from restored/compacted sessions is visible.
- Snapshot log entries for user input, assistant messages, reasoning, tool calls, and tool results are projected into existing ConsoleLine shapes.
- Existing in-flight snapshot rendering is preserved.
- Unknown snapshot entries are ignored rather than surfaced as errors.
Validation:
- `cd web/workspace && deno task check`
- `cd web/workspace && deno task test`
- `git diff --check`
- `nix build .#yoi --no-link`
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-13T14:30:38Z from: inprogress to: done reason: cli_state field: state -->
## State changed
State changed to `done`.
---
<!-- event: state_changed author: hare at: 2026-07-15T16:19:27Z from: done to: closed reason: closed field: state -->
## State changed
Ticket を closed にしました。
---
<!-- event: close author: hare at: 2026-07-15T16:19:27Z status: closed -->
## 完了
Ticket `00001KXDXEJQS` (`Render console snapshot entries`) はすでに `state: done` に到達していたため、workspace Dashboard から close しました。
この Close action によって、実装作業、state 変更、Orchestrator/Companion launch、worker invocation は開始されていません。
---

View File

@ -1,28 +0,0 @@
---
title: 'Restore Worker without backend profile fetch'
state: 'closed'
created_at: '2026-07-13T14:44:12Z'
updated_at: '2026-07-15T16:19:26Z'
assignee: null
queued_by: 'yoi ticket'
queued_at: '2026-07-13T14:45:04Z'
---
## 背景
Worker restore は Worker metadata の `resolved_manifest_snapshot` と session log を authority として復元できる。現状の worker-runtime restore path は fresh spawn と同じ profile source archive resolution を先に実行しており、Runtime restart 時に workspace-server / backend resource endpoint が未起動だと profile archive fetch に失敗して Worker が stale のまま残る。
Active Worker restore では backend profile source fetch を不要にし、metadata snapshot を使って復元する。
## 要件
- `ProfileRuntimeWorkerFactory::restore_controller` は active metadata restore の前に `request.profile_source` を解決しない。
- Restore は metadata の resolved manifest snapshot と builtins loader を authority にする。
- Pending/no-history fallback の fresh recreation が必要な場合だけ、従来通り profile source resolution を行う。
- backend が未起動でも、metadata snapshot を持つ active Worker restore は profile fetch failure で stale にならない。
## 受け入れ条件
- restore path の active case で `resolve_profile_source_archive` が呼ばれないことをテストする。
- worker-runtime tests が通る。
- Nix build が通る。

View File

@ -1,3 +0,0 @@
Ticket `00001KXDYZ254` (`Restore Worker without backend profile fetch`) はすでに `state: done` に到達していたため、workspace Dashboard から close しました。
この Close action によって、実装作業、state 変更、Orchestrator/Companion launch、worker invocation は開始されていません。

View File

@ -1,100 +0,0 @@
<!-- event: create author: "yoi ticket" at: 2026-07-13T14:44:12Z -->
## 作成
LocalTicketBackend によって作成されました。
---
<!-- event: intake_summary author: hare at: 2026-07-13T14:45:03Z -->
## Intake summary
Marked ready by `yoi ticket state`.
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-13T14:45:03Z from: planning to: ready reason: cli_state field: state -->
## State changed
Marked ready by `yoi ticket state`.
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-13T14:45:04Z from: ready to: queued reason: queued field: state -->
## State changed
Ticket を `yoi ticket` が queued にしました。
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-13T14:45:04Z from: queued to: inprogress reason: cli_state field: state -->
## State changed
State changed to `inprogress`.
---
<!-- event: implementation_report author: hare at: 2026-07-13T14:55:17Z -->
## Implementation report
Changed active Worker restore so it does not fetch backend profile source archives.
Root cause:
- Worker metadata already contains the resolved manifest snapshot used by `Worker::restore_from_worker_metadata_with_context`.
- `ProfileRuntimeWorkerFactory::restore_controller` still resolved `request.profile_source` before calling restore, so Runtime restart could fail if workspace-server/backend resource endpoint was not available yet.
Changes:
- Active restore now builds only a minimal builtin fallback manifest/loader and calls `Worker::restore_from_worker_metadata_with_context` directly.
- The metadata `resolved_manifest_snapshot` remains the restore authority.
- Backend profile source archive resolution is deferred to the pending/no-history fallback path only, where fresh Worker recreation still needs a manifest.
Validation:
- `cargo fmt`
- `cargo check -q`
- `cargo test -q -p worker-runtime --features fs-store,ws-server`
- `cargo test -q -p yoi-workspace-server`
- `cd web/workspace && deno task check && deno task test`
- `git diff --check`
- `nix build .#yoi --no-link`
Note: README.md had pre-existing unrelated local modifications and was not included.
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-13T14:55:17Z from: inprogress to: done reason: cli_state field: state -->
## State changed
State changed to `done`.
---
<!-- event: state_changed author: hare at: 2026-07-15T16:19:26Z from: done to: closed reason: closed field: state -->
## State changed
Ticket を closed にしました。
---
<!-- event: close author: hare at: 2026-07-15T16:19:26Z status: closed -->
## 完了
Ticket `00001KXDYZ254` (`Restore Worker without backend profile fetch`) はすでに `state: done` に到達していたため、workspace Dashboard から close しました。
この Close action によって、実装作業、state 変更、Orchestrator/Companion launch、worker invocation は開始されていません。
---

View File

@ -1,29 +0,0 @@
---
title: 'Use live Worker snapshot for runtime observation'
state: 'closed'
created_at: '2026-07-13T17:48:09Z'
updated_at: '2026-07-13T17:59:59Z'
assignee: null
queued_by: 'yoi ticket'
queued_at: '2026-07-13T17:48:47Z'
---
## 背景
Browser Console は worker-runtime REST/WebSocket observation endpoint から connect-time snapshot を受け取る。現状の `Runtime::worker_observation_snapshot` は live WorkerController の session-log sink を見ず、stub snapshot (`entries: []`) を返している。そのため Worker restore 後に文脈は engine/session log 上で保持されていても、console snapshot entries は空になる。
TUI/socket attach と同じく、Runtime observation snapshot も live WorkerHandle の `SegmentLogSink` / greeting / status / in-flight snapshot から構築する必要がある。
## 要件
- `worker-runtime` observation snapshot は live execution backend の Worker snapshot を使う。
- Real worker backend は `WorkerHandle` から `Event::Snapshot` を構築する。
- Snapshot entries は `SegmentLogSink::subscribe_with_snapshot` の prefix を JSON 化したものにする。
- Live Worker がない場合だけ existing fallback/stub を使う。
- Browser Console reconnect/restore 時に snapshot entries が空にならない。
## 受け入れ条件
- `Runtime::worker_observation_snapshot` が backend-provided snapshot を優先する。
- worker-runtime tests が通る。
- workspace frontend console tests が通る。

View File

@ -1,10 +0,0 @@
Runtime observation snapshots now prefer the live execution backend snapshot. The real Worker backend returns a WorkerHandle-built Snapshot containing session-log entries, greeting, status, and in-flight state; the Runtime falls back to the old stub only when no live backend handle exists.
Validation:
- cargo fmt --check
- cargo check -q
- cargo test -q -p worker-runtime --features fs-store,ws-server
- cargo test -q -p yoi-workspace-server
- cd web/workspace && deno task check && deno task test
- git diff --check
- nix build .#yoi --no-link

View File

@ -1,69 +0,0 @@
<!-- event: create author: "yoi ticket" at: 2026-07-13T17:48:09Z -->
## 作成
LocalTicketBackend によって作成されました。
---
<!-- event: intake_summary author: hare at: 2026-07-13T17:48:47Z -->
## Intake summary
Marked ready by `yoi ticket state`.
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-13T17:48:47Z from: planning to: ready reason: cli_state field: state -->
## State changed
Marked ready by `yoi ticket state`.
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-13T17:48:47Z from: ready to: queued reason: queued field: state -->
## State changed
Ticket を `yoi ticket` が queued にしました。
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-13T17:48:47Z from: queued to: inprogress reason: cli_state field: state -->
## State changed
State changed to `inprogress`.
---
<!-- event: state_changed author: hare at: 2026-07-13T17:59:59Z from: inprogress to: closed reason: closed field: state -->
## State changed
Ticket を closed にしました。
---
<!-- event: close author: hare at: 2026-07-13T17:59:59Z status: closed -->
## 完了
Runtime observation snapshots now prefer the live execution backend snapshot. The real Worker backend returns a WorkerHandle-built Snapshot containing session-log entries, greeting, status, and in-flight state; the Runtime falls back to the old stub only when no live backend handle exists.
Validation:
- cargo fmt --check
- cargo check -q
- cargo test -q -p worker-runtime --features fs-store,ws-server
- cargo test -q -p yoi-workspace-server
- cd web/workspace && deno task check && deno task test
- git diff --check
- nix build .#yoi --no-link
---

View File

@ -1,2 +0,0 @@
{"id":"orch-plan-20260715-162205-1","ticket_id":"00001KXK9507N","kind":"accepted_plan","accepted_plan":{"summary":"Consolidate durable Ticket configuration from `.yoi/ticket.config.toml` into tracked Workspace project settings. Move Ticket backend provider/root, record language, orchestration defaults, and fixed role launch config under Workspace settings while keeping local backend/runtime operational settings separate. Preserve default `.yoi/tickets` behavior, allow narrow read-only legacy fallback/migration note, and ensure CLI, Worker Ticket tools, workspace-server Ticket endpoint, Panel/role launch paths, and tests resolve the new Workspace settings authority.","branch":"work/00001KXK9507N-ticket-workspace-settings","worktree":"/home/hare/Projects/yoi/.worktree/00001KXK9507N-ticket-workspace-settings","role_plan":"Routing-only pass under explicit workflow instruction: do not spawn role Pods until explicit follow-up. On follow-up, re-check Ticket/relation/plan/worktree state, record `queued -> inprogress`, create a dedicated worktree under `/home/hare/Projects/yoi/.worktree`, then use sibling Coder/Reviewer Pods. Orchestrator retains merge/final-validation/close/worktree-cleanup authority; per current user instruction, do not call StopPod."},"author":"orchestrator","at":"2026-07-15T16:22:05Z"}
{"id":"orch-plan-20260715-162216-2","ticket_id":"00001KXK9507N","kind":"waiting_capacity_note","note":"Routing-only workflow invocation explicitly instructed: “wait for explicit follow-up before spawning role Pods.” Ticket is implementation-ready and no blockers were found, but this pass intentionally does not transition `queued -> inprogress`, create a worktree, or spawn Coder/Reviewer Pods. Next explicit follow-up should re-check state, then accept `queued -> inprogress` before implementation side effects.","author":"orchestrator","at":"2026-07-15T16:22:16Z"}

View File

@ -1,32 +0,0 @@
---
title: 'Consolidate Ticket configuration into Workspace settings'
state: 'closed'
created_at: '2026-07-15T16:18:25Z'
updated_at: '2026-07-15T17:51:30Z'
assignee: null
queued_by: 'workspace-panel'
queued_at: '2026-07-15T16:19:31Z'
---
## 背景
`.yoi/ticket.config.toml` は現在、Ticket backend root/provider、Ticket record language、orchestration defaults、固定 role launch config を持っている。Worker Ticket tools は Workspace backend 経由の Ticket authority に寄せ始めており、Ticket 専用 config file が Workspace settings から独立している状態は設定境界として不自然になっている。
local backend の listen address、DB path、runtime store、remote runtime endpoint などの local-only operational settings は project/workspace の Ticket policy とは分けたまま、Ticket に関する durable policy を Workspace settings に統合する。
## 要件
- tracked な Workspace project settings を Ticket configuration の durable authority として導入または再利用する。
- Ticket backend provider/root、Ticket record language、orchestration defaults、固定 role launch config を Workspace settings namespace 配下へ移す。
- local-only backend/runtime settings と tracked project/workspace Ticket policy を混ぜない。
- Ticket CLI、Worker Ticket feature setup、workspace-server Ticket backend endpoint、Panel / role launch code、関連 tests が Workspace settings authority から Ticket settings を解決するように更新する。
- `.yoi/ticket.config.toml` は必要なら移行用に narrow な read-only fallback として読むが、長期的な active authority として残さない。
- workspace-server の Ticket backend endpoint が `.yoi/tickets` を hard-code せず、設定された Ticket backend root を尊重する。
## 受け入れ条件
- Ticket backend root、language、orchestration defaults、role slots の Workspace settings loading を tests で確認している。
- 既存の Ticket CLI と Worker Ticket tools が default では同じ `.yoi/tickets` data に対して動作し続ける。
- Workspace settings で non-default Ticket backend root を設定した場合、direct CLI/local path と workspace-server-backed Worker Ticket tools の両方で尊重される。
- `.yoi/ticket.config.toml` が obsolete/replaced であることを docs または migration note / in-code note に残している。
- affected crates の `cargo test``nix build .#yoi` が通る。

View File

@ -1,37 +0,0 @@
Ticket configuration の Workspace settings 統合を実装・レビュー・merge・検証した。
実装内容:
- durable Ticket policy authority を `.yoi/workspace.toml [ticket]` に移動。
- tracked `.yoi/ticket.config.toml` を削除。
- Workspace `[ticket]` がない場合のみ legacy `.yoi/ticket.config.toml` を narrow read-only migration fallback として読むようにした。
- Workspace `[ticket]` が存在する場合は legacy config を無視し、二重 active authority を避ける precedence にした。
- default behavior は維持し、設定なしでは `provider = builtin:yoi_local`, `root = .yoi/tickets` を使う。
- Ticket backend provider/root、record language、orchestration defaults、fixed role launch config を Workspace settings authority に移した。
- Direct Ticket CLI / Objective config consumers / Worker Ticket feature setup / workspace-server Ticket backend endpoint / project record reader / Panel role launch availability / role launch tests を新 authority 解決へ更新。
- Panel orchestration overlay も orchestration worktree の `TicketConfig::load_workspace(worktree_root)` を使い、configured backend root と record language を尊重するよう修正。
- docs で `.yoi/ticket.config.toml` obsolete と新 `[ticket]` workspace settings shape を記録。
Review:
- 初回 review は Panel orchestration overlay が `.yoi/tickets` を hard-code している blocker で `request_changes`
- `40f21145 tui: use configured ticket root for overlay` で overlay read path を configured Ticket root に移行し、non-default root regression test を追加。
- focused re-review は `approve`
Merge / validation:
- Merge commit: `45783b74 merge: workspace ticket settings`
- Final validation passed:
- `cargo fmt --check`
- `git diff --check`
- `cargo test -p ticket`
- `cargo test -p yoi --tests`
- `cargo test -p client --lib --tests`
- `cargo test -p worker --lib --tests`
- `cargo test -p yoi-workspace-server --lib`
- `cargo test -p tui --lib --tests`
- `cargo check -p yoi`
- `yoi ticket doctor`
- `nix build .#yoi --no-link`
- Validation log: `/run/user/1000/yoi/yoi-orchestrator/bash-output/workspace-ticket-settings-final-validation-1784137566.txt`
Cleanup:
- Implementation worktree/branch cleanup will be performed after close commit。
- Per user instruction, `StopPod` is not used。

View File

@ -1,417 +0,0 @@
<!-- event: create author: "yoi ticket" at: 2026-07-15T16:18:25Z -->
## 作成
LocalTicketBackend によって作成されました。
---
<!-- event: plan author: hare at: 2026-07-15T16:18:40Z -->
## Plan
Background:
- `.yoi/ticket.config.toml` currently owns Ticket backend root/provider, Ticket record language, orchestration defaults, and fixed role launch config.
- Workspace backend / Runtime / Worker Ticket tools are moving toward Workspace-owned Ticket authority, so a separate Ticket-specific config file is no longer the right durable settings boundary.
- Local backend operational settings such as listen address, DB path, runtime store, and remote runtime endpoint should remain local/backend settings, not project Ticket policy.
Requirements:
- Introduce or reuse a tracked Workspace project settings file as the durable authority for Ticket configuration.
- Move Ticket backend provider/root, Ticket record language, orchestration defaults, and fixed role launch config under a Workspace settings namespace.
- Keep local-only backend/runtime settings separate from tracked project/workspace Ticket policy.
- Update Ticket CLI, Worker Ticket feature setup, workspace-server Ticket backend endpoint, Panel/role launch code, and tests to resolve Ticket settings from the Workspace settings authority.
- Preserve a narrow read-only migration path from `.yoi/ticket.config.toml` only if needed for transition, but do not keep it as the long-term active authority.
- Ensure workspace-server Ticket backend endpoint honors the configured Ticket backend root instead of hard-coding `.yoi/tickets`.
Acceptance criteria:
- New/updated tests cover Workspace settings loading for Ticket backend root, language, orchestration defaults, and role slots.
- Existing Ticket CLI and Worker Ticket tools continue to work against the same `.yoi/tickets` data by default.
- A non-default Ticket backend root configured in Workspace settings is respected by both direct CLI/local paths and workspace-server-backed Worker Ticket tools.
- Documentation or in-code migration notes state that `.yoi/ticket.config.toml` is obsolete/replaced.
- `cargo test` for affected crates and `nix build .#yoi` pass.
---
<!-- event: intake_summary author: hare at: 2026-07-15T16:18:40Z -->
## Intake summary
Marked ready by `yoi ticket state`.
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-15T16:18:40Z from: planning to: ready reason: cli_state field: state -->
## State changed
Marked ready by `yoi ticket state`.
---
<!-- event: state_changed author: workspace-panel at: 2026-07-15T16:19:31Z from: ready to: queued reason: queued field: state -->
## State changed
Ticket を `workspace-panel` が queued にしました。
---
<!-- event: decision author: orchestrator at: 2026-07-15T16:22:50Z -->
## Decision
Routing decision: implementation_ready_waiting_explicit_followup
Reason:
- Ticket は Workspace project settings を Ticket configuration の durable authority にする具体的な移行作業であり、要件・受け入れ条件・migration/fallback 方針が実装可能な粒度で記録されている。
- `TicketRelationQuery(00001KXK9507N)` は 0 件で、blocking relation はない。
- `TicketOrchestrationPlanQuery(00001KXK9507N)` は事前 record なし。今回 accepted plan と waiting-capacity note を記録した。
- `TicketList(inprogress)` は 0 件、`TicketList(queued)` はこの Ticket 1 件のみ。
- Orchestrator worktree `/home/hare/Projects/yoi/.worktree/orchestration` は clean。
- ただし今回の workflow invocation は「explicit follow-up before spawning role Pods」と明示しているため、この routing pass では `queued -> inprogress`、worktree 作成、Coder/Reviewer Pod spawn は行わない。
Evidence checked:
- Ticket body / thread / artifacts。
- `.yoi/workspace.toml` and `.yoi/ticket.config.toml` current files。
- `docs/development/work-items.md` and `docs/design/workflows-public-dogfood-split.md` Ticket config references。
- Code map via grep:
- `crates/yoi/src/ticket_cli.rs`, `crates/yoi/src/objective_cli.rs`
- `crates/tui/src/workspace_panel.rs`
- `crates/worker/src/feature/builtin/ticket.rs`, `crates/worker/src/controller.rs`
- workspace identity/settings references in `crates/worker`, `crates/worker-runtime`, workspace-server related code。
- Current worktrees/branches: no existing `work/00001KXK9507N-*` branch/worktree。
- Visible Pods: no active role Pod needed for this routing-only pass。
IntentPacket:
Intent:
- `.yoi/ticket.config.toml` を長期 active authority から外し、tracked Workspace project settings の namespace に Ticket configuration を統合する。
- Ticket backend provider/root、Ticket record language、orchestration defaults、固定 role launch config を Workspace settings authority から解決する。
Binding decisions / invariants:
- local-only backend/runtime operational settingsDB path、listen address、runtime endpoint/store 等)と tracked project/workspace Ticket policy を混ぜない。
- Workspace settings が Ticket configuration の durable authority になる。
- `.yoi/ticket.config.toml` は必要な場合だけ narrow read-only migration fallback として扱い、長期 active authority として残さない。
- default behavior は保つ: 設定なし/移行後 default では既存 `.yoi/tickets` data に対して CLI と Worker Ticket tools が動く。
- workspace-server Ticket backend endpoint は `.yoi/tickets` hard-code ではなく configured Ticket backend root を尊重する。
- role launch config は fixed role slots を維持し、Profile/workflow selector の authority を Workspace settings 側に移す。
- Ticket record language policy は durable Ticket records に適用し、通常 worker prose language と混同しない。
Requirements / acceptance criteria:
- Workspace settings loading が Ticket backend root/provider、language、orchestration defaults、role slots を読む test を追加/更新する。
- Ticket CLI / Objective CLI / Worker Ticket feature setup / workspace-server Ticket backend endpoint / Panel role launch path が Workspace settings authority から Ticket settings を解決する。
- Workspace settings で non-default Ticket backend root を設定した場合、direct CLI/local path と workspace-server-backed Worker Ticket tools の両方が尊重する。
- `.yoi/ticket.config.toml` obsolete/replaced の docs または migration/in-code note を残す。
- affected crates の cargo tests と `nix build .#yoi` が通る。
Implementation latitude:
- 既存 `.yoi/workspace.toml` を拡張するか、tracked Workspace project settings file を別に導入するかは、既存 Workspace settings architecture に合わせて coder が選んでよい。ただし local-only backend/runtime settings とは分離すること。
- Legacy `.yoi/ticket.config.toml` fallback の exact scoperead-only import / warning / migration helperは bounded local tactic としてよいが、active authority として二重管理しないこと。
- DTO/schema/test layout は existing workspace/ticket config patterns に合わせてよい。
Escalate if:
- Workspace project settings の tracked file 形式自体を新規 product decision として固定する必要があり、既存 `.yoi/workspace.toml` / settings architecture から自然に選べない場合。
- `.yoi/ticket.config.toml` と Workspace settings の二重 authority を長期互換として残す必要が出る場合。
- remote/backend Ticket authority の設計が、local path migration を越えて protocol/API redesign を要求する場合。
- existing Ticket storage migration で data loss risk が出る場合。
Validation:
- `git diff --check`
- `cargo test -p ticket` if ticket config crate/tests are touched。
- `cargo test -p yoi --lib --tests` or targeted CLI tests if CLI paths are touched。
- `cargo test -p worker --lib --tests` if Worker Ticket feature/controller paths are touched。
- `cargo test -p yoi-workspace-server --lib` if workspace-server Ticket endpoint/settings are touched。
- `cargo test -p tui --lib --tests` if Panel role launch/config availability is touched。
- `cargo check -p yoi`
- `cd web/workspace && deno task check && deno task test` if web/API types are touched。
- `yoi ticket doctor`
- `nix build .#yoi --no-link`
Current code map / likely touch points:
- `crates/ticket` config model/loading。
- `crates/yoi/src/ticket_cli.rs`, `crates/yoi/src/objective_cli.rs`
- `crates/tui/src/workspace_panel.rs` for Panel config availability/role launch。
- `crates/worker/src/feature/builtin/ticket.rs`, `crates/worker/src/controller.rs` for Worker Ticket tools/setup。
- workspace-server Ticket backend endpoint/settings code。
- `.yoi/workspace.toml` / tracked Workspace settings file and docs under `docs/development/work-items.md`
Critical risks / reviewer focus:
- accidentally keeping `.yoi/ticket.config.toml` as co-equal active authority。
- mixing local-only backend/runtime settings into tracked project settings。
- direct CLI and workspace-server-backed Worker Ticket tools resolving different Ticket roots/languages。
- role launch config losing fixed role semantics or using `inherit` where top-level role launch cannot support it。
- hard-coded `.yoi/tickets` remaining in workspace-server endpoint or Worker Ticket tools。
Next action:
- Wait for explicit follow-up. On follow-up, re-check Ticket/relation/plan/worktree state, then record `queued -> inprogress` before creating `/home/hare/Projects/yoi/.worktree/00001KXK9507N-ticket-workspace-settings` and spawning role Pods。
---
<!-- event: decision author: orchestrator at: 2026-07-15T16:30:32Z -->
## Decision
Implementation follow-up accepted。
Evidence rechecked before side effects:
- Ticket remains `queued` and has prior implementation-ready IntentPacket / accepted plan。
- `TicketRelationQuery(00001KXK9507N)`: 0 blockers。
- `TicketOrchestrationPlanQuery(00001KXK9507N)`: accepted plan exists for branch/worktree `work/00001KXK9507N-ticket-workspace-settings` / `/home/hare/Projects/yoi/.worktree/00001KXK9507N-ticket-workspace-settings`
- `TicketList(inprogress)`: 0 件。
- Orchestrator worktree is clean。
- No existing `00001KXK9507N` worktree/branch was reported in the recheck。
Next action:
- Record `queued -> inprogress` now, then create the dedicated implementation worktree and route implementation to sibling Coder Pod。
- Per current user instruction, do not call `StopPod` during later cleanup。
---
<!-- event: state_changed author: orchestrator at: 2026-07-15T16:30:37Z from: queued to: inprogress reason: accepted_for_implementation field: state -->
## State changed
Explicit follow-up authorized implementation start. Ticket/relation/plan/worktree state was rechecked and no blockers were found. Moving to inprogress before worktree creation or role Pod spawn.
---
<!-- event: implementation_report author: orchestrator at: 2026-07-15T17:20:06Z -->
## Implementation report
Implementation progress report:
- Coder Pod `yoi-coder-00001KXK9507N-ticket-settings` completed implementation in `/home/hare/Projects/yoi/.worktree/00001KXK9507N-ticket-workspace-settings` on branch `work/00001KXK9507N-ticket-workspace-settings`
- Implementation commit: `b1e3a2ad338a551f6e875087c93ecf709a03c1c8 ticket: move ticket settings into workspace config`
- Orchestrator inspected worktree status, branch log, commit stats, `git diff --check c68ed1fd..HEAD`, and changed files. Worktree was clean and diff check passed。
Implementation summary:
- Durable Ticket policy authority を `.yoi/workspace.toml``[ticket]` に移動。
- tracked `.yoi/ticket.config.toml` を削除。
- Workspace settings に `[ticket]` がない場合のみ、legacy `.yoi/ticket.config.toml` を narrow read-only migration fallback として読む。
- Ticket settings がない場合の default behavior は `provider = builtin:yoi_local`, `root = .yoi/tickets` として維持。
- `[ticket]` が存在する場合は legacy `.yoi/ticket.config.toml` を無視する precedence にした。
- Direct CLI / Objective config consumers / Worker Ticket feature setup / workspace-server Ticket backend endpoint / project record reader / Panel role launch availability / role launch tests を新 authority 解決へ更新。
- local-only runtime/backend operational settings は tracked workspace Ticket policy と分離。
- docs で `.yoi/ticket.config.toml` obsolete と新 `[ticket]` workspace settings shape を記録。
Files touched:
- `.yoi/ticket.config.toml` removed。
- `.yoi/workspace.toml` updated。
- `crates/client/src/ticket_role.rs`
- `crates/ticket/src/config.rs`
- `crates/tui/src/dashboard/tests.rs`
- `crates/tui/src/workspace_panel.rs`
- `crates/worker/src/controller.rs`
- `crates/worker/src/feature/builtin/ticket.rs`
- `crates/workspace-server/src/identity.rs`
- `crates/workspace-server/src/records.rs`
- `crates/workspace-server/src/server.rs`
- `crates/yoi/src/ticket_cli.rs`
- `docs/design/workflows-public-dogfood-split.md`
- `docs/development/work-items.md`
Coder-reported validation passed:
- `cargo fmt --check`
- `git diff --check`
- `cargo test -p ticket`
- `cargo test -p yoi --tests`
- `cargo test -p client --lib --tests`
- `cargo test -p worker --lib --tests`
- `cargo test -p yoi-workspace-server --lib`
- `cargo test -p tui --lib --tests`
- `cargo check -p yoi`
- `yoi ticket doctor`
- `nix build .#yoi --no-link`
Not run:
- `cargo test -p yoi --lib --tests`: `yoi` has no library target。
- web checks: web/API types were not touched。
Next action:
- Route to external Reviewer Pod before merge/close decisions。
---
<!-- event: review author: reviewer at: 2026-07-15T17:26:26Z status: request_changes -->
## Review: request changes
External review result: request_changes
Acceptance blocker:
- Workspace settings が active Panel Ticket reads の全経路で durable authority になっていない。
- `crates/tui/src/workspace_panel.rs:673-688` は orchestration worktree overlay を `worktree_root.join(DEFAULT_TICKET_BACKEND_RELATIVE_PATH)` (`.yoi/tickets`) から直接読んでいる。
- そのため orchestration worktree 側の `TicketConfig` / workspace `[ticket.backend].root` を解決しておらず、non-default Ticket root では Panel overlay が “no .yoi/tickets directory” と扱う、または orchestration state を見落とす。
- これは configured backend-root authority と Panel resolution-path acceptance focus に違反する。
Positive evidence:
- `TicketConfig``.yoi/workspace.toml [ticket]` を legacy fallback / precedence / default tests 付きで読むようになっている。
- Direct Ticket CLI、Objective link validation、Worker feature setup、workspace-server record/backend endpoint、queue handoff、role launch は概ね `TicketConfig::load_workspace` 経由になっている。
- `.yoi/workspace.toml` に backend/language/role config が入り、docs は legacy config obsolete を記録している。
Validation performed by reviewer:
- read-only diff/source inspection。
- implementation worktree で `git status --short`, `git rev-parse`, `git diff --check c68ed1fd..HEAD`
- test rerun はしていない。
Non-blocking note:
- non-default-root Panel orchestration-overlay test を追加すると、この経路の regression を防げる。
---
<!-- event: implementation_report author: orchestrator at: 2026-07-15T17:43:06Z -->
## Implementation report
Follow-up implementation progress report after request_changes:
- Coder Pod `yoi-coder-00001KXK9507N-ticket-settings` completed the Panel overlay configured-root fix in `/home/hare/Projects/yoi/.worktree/00001KXK9507N-ticket-workspace-settings`
- Follow-up commit: `40f21145415106dabf0905d301dd04b73fabd424 tui: use configured ticket root for overlay`
- Orchestrator inspected worktree status, branch log, commit stats, and `git diff --check c68ed1fd..HEAD`; worktree was clean and diff check passed。
Fix summary:
- Panel orchestration overlay loading now uses `TicketConfig::load_workspace(worktree_root)`
- Overlay reads use the orchestration worktrees configured `[ticket.backend].root` and Ticket record language。
- Hard-coded `.yoi/tickets` overlay read path was removed except through config defaults。
- Missing/unusable configured overlay backend root diagnostics are preserved。
- Regression test added: `workspace_panel_orchestration_overlay_uses_configured_backend_root`
- Test verifies non-default orchestration overlay Ticket root is used and `.yoi/tickets` is not required。
Coder-reported validation passed:
- `cargo fmt --check`
- `git diff --check`
- `cargo test -p tui --lib --tests` (`383 passed`)
- `cargo check -p yoi`
- `yoi ticket doctor`
- `nix build .#yoi --no-link`
Next action:
- Request focused re-review of the previous Panel overlay blocker and overall acceptance before merge/close decisions。
---
<!-- event: review author: reviewer at: 2026-07-15T17:45:34Z status: approve -->
## Review: approve
Focused re-review result: approve
Evidence:
- Previous blocker is fixed: `crates/tui/src/workspace_panel.rs:674-694` now loads `TicketConfig::load_workspace(worktree_root)`, uses `overlay_config.backend_root()` and `overlay_config.ticket_record_language()`, and reports a clear diagnostic when the configured root is not a directory。
- Regression `workspace_panel_orchestration_overlay_uses_configured_backend_root` covers non-default overlay roots and reviewer reran it successfully (`1 passed`)。
- No new co-equal legacy authority was found: legacy `.yoi/ticket.config.toml` remains only the documented fallback through `TicketConfig::load_workspace` when workspace `[ticket]` is absent。
- Overall acceptance remains satisfied by inspected paths: direct CLI/Objectives, Worker feature setup, workspace-server records/backend endpoint, Panel/role launch, and queue/overlay paths resolve through workspace Ticket settings/defaults。
- `.yoi/workspace.toml` carries backend/language/fixed role policy, docs/help note legacy obsolete, and operational/runtime settings remain outside tracked Ticket policy。
Validation performed by reviewer:
- clean `git status`
- HEAD `40f21145`
- `git diff --check c68ed1fd..HEAD`
- focused source review。
- focused TUI regression test。
Blockers: none。
---
<!-- event: implementation_report author: orchestrator at: 2026-07-15T17:51:09Z -->
## Implementation report
Merge / final validation report:
- Reviewer approved the implementation after the Panel overlay configured-root fix。
- Merged branch `work/00001KXK9507N-ticket-workspace-settings` into orchestration branch with merge commit `45783b74 merge: workspace ticket settings`
- Final validation was rerun in `/home/hare/Projects/yoi/.worktree/orchestration` after merge。
Validation passed:
- `cargo fmt --check`
- `git diff --check`
- `cargo test -p ticket`
- `cargo test -p yoi --tests`
- `cargo test -p client --lib --tests`
- `cargo test -p worker --lib --tests`
- `cargo test -p yoi-workspace-server --lib`
- `cargo test -p tui --lib --tests`
- `cargo check -p yoi`
- `yoi ticket doctor`
- `nix build .#yoi --no-link`
Validation log:
- `/run/user/1000/yoi/yoi-orchestrator/bash-output/workspace-ticket-settings-final-validation-1784137566.txt`
Reviewed/merged implementation commits:
- `b1e3a2ad ticket: move ticket settings into workspace config`
- `40f21145 tui: use configured ticket root for overlay`
Notes:
- `.yoi/ticket.config.toml` was removed from tracked active project config。
- `.yoi/workspace.toml [ticket]` is now the durable Ticket settings authority; legacy config remains only as narrow fallback when workspace `[ticket]` is absent。
- Per user instruction, no `StopPod` cleanup was performed。
---
<!-- event: state_changed author: orchestrator at: 2026-07-15T17:51:15Z from: inprogress to: done reason: implementation_validated field: state -->
## State changed
Implementation was reviewed, merged into orchestration branch, and final validation passed. Moving to done before closure.
---
<!-- event: state_changed author: hare at: 2026-07-15T17:51:30Z from: done to: closed reason: closed field: state -->
## State changed
Ticket を closed にしました。
---
<!-- event: close author: hare at: 2026-07-15T17:51:30Z status: closed -->
## 完了
Ticket configuration の Workspace settings 統合を実装・レビュー・merge・検証した。
実装内容:
- durable Ticket policy authority を `.yoi/workspace.toml [ticket]` に移動。
- tracked `.yoi/ticket.config.toml` を削除。
- Workspace `[ticket]` がない場合のみ legacy `.yoi/ticket.config.toml` を narrow read-only migration fallback として読むようにした。
- Workspace `[ticket]` が存在する場合は legacy config を無視し、二重 active authority を避ける precedence にした。
- default behavior は維持し、設定なしでは `provider = builtin:yoi_local`, `root = .yoi/tickets` を使う。
- Ticket backend provider/root、record language、orchestration defaults、fixed role launch config を Workspace settings authority に移した。
- Direct Ticket CLI / Objective config consumers / Worker Ticket feature setup / workspace-server Ticket backend endpoint / project record reader / Panel role launch availability / role launch tests を新 authority 解決へ更新。
- Panel orchestration overlay も orchestration worktree の `TicketConfig::load_workspace(worktree_root)` を使い、configured backend root と record language を尊重するよう修正。
- docs で `.yoi/ticket.config.toml` obsolete と新 `[ticket]` workspace settings shape を記録。
Review:
- 初回 review は Panel orchestration overlay が `.yoi/tickets` を hard-code している blocker で `request_changes`
- `40f21145 tui: use configured ticket root for overlay` で overlay read path を configured Ticket root に移行し、non-default root regression test を追加。
- focused re-review は `approve`
Merge / validation:
- Merge commit: `45783b74 merge: workspace ticket settings`
- Final validation passed:
- `cargo fmt --check`
- `git diff --check`
- `cargo test -p ticket`
- `cargo test -p yoi --tests`
- `cargo test -p client --lib --tests`
- `cargo test -p worker --lib --tests`
- `cargo test -p yoi-workspace-server --lib`
- `cargo test -p tui --lib --tests`
- `cargo check -p yoi`
- `yoi ticket doctor`
- `nix build .#yoi --no-link`
- Validation log: `/run/user/1000/yoi/yoi-orchestrator/bash-output/workspace-ticket-settings-final-validation-1784137566.txt`
Cleanup:
- Implementation worktree/branch cleanup will be performed after close commit。
- Per user instruction, `StopPod` is not used。
---

View File

@ -1,38 +0,0 @@
---
title: 'Workspace Orchestrator-managed workdirs and Worker spawn tools'
state: 'planning'
created_at: '2026-07-15T16:28:09Z'
updated_at: '2026-07-15T16:30:00Z'
assignee: null
---
## 背景
現在の LLM-facing `SpawnWorker` は child Worker の process/tool `cwd`、profile、initial input、delegation scope を扱うが、Workspace Runtime の `WorkingDirectory` 作成・claim・Worker 紐付け・cleanup authority は扱わない。
一方で workspace-server / worker-runtime には `WorkingDirectoryRequest`、`WorkingDirectoryClaim`、browser Worker create API の workdir selection、workdir registry / worker link が既にある。embedded Orchestrator が coder / reviewer などの sibling Worker 群を律するには、raw path / cwd ではなく Workspace backend-managed workdir authority を通して Worker spawn できる tool surface が必要。
## 要件
- 既存 `SpawnWorker` を膨らませず、Workspace backend API 経由で workdir と Worker spawn を扱う Orchestrator 用 tool feature を追加する。
- Orchestrator tool は `WorkspaceClient::Http { workspace_id, base_url }` を必須 authority とし、無い場合は fail closed / not registered にする。
- 最小 tool surface として以下を提供する。
- Workspace-managed working directory の一覧または詳細取得。
- repository id / selector から working directory を作成する操作。
- existing `working_directory_id` / optional `relative_cwd` を指定して Worker を spawn する操作。
- Worker spawn 操作は Workspace backend の safe request schema を使い、raw filesystem path、runtime-internal store path、executable path、socket path、secret-like value を model input として受け取らない。
- Spawn する Worker には profile selector、display/requested name、initial input、acceptance requirement を指定できる。
- Workspace backend は spawn 結果として Worker id、runtime id、console href、working directory summary、diagnostics を返し、workdir registry / worker-workdir link を更新する。
- Orchestrator role/profile だけに mutation-capable tools を出し、Coder / Reviewer などには原則として sibling spawn / workdir create 権限を出さない。
- Ticket orchestration flow からは、Ticket を読んだ後に workdir create/select -> coder/reviewer spawn -> Worker status/output 追跡へ進める形にする。
- `SpawnWorker.cwd` と Workspace `WorkingDirectory` の違いを prompt/tool description で明確にし、cwd は authority ではないことを維持する。
## 受け入れ条件
- embedded Orchestrator Worker から Workspace API 経由で workdir を作成できる。
- embedded Orchestrator Worker から作成済み workdir を指定して sibling Worker を spawn できる。
- Spawn された Worker が Runtime / Workspace backend の worker list と workdir link に現れる。
- raw path を渡す spawn/workdir tool input は schema または validation で拒否される。
- `WorkspaceClient::Http` を持たない Worker では Orchestrator workspace mutation tools が登録されない、または実行時に fail closed する。
- 既存 `SpawnWorker` の local delegation behavior は壊れない。
- worker-runtime / workspace-server / worker の該当 tests と `nix build .#yoi` が通る。

View File

@ -1,7 +0,0 @@
<!-- event: create author: "yoi ticket" at: 2026-07-15T16:28:09Z -->
## 作成
LocalTicketBackend によって作成されました。
---

View File

@ -1 +0,0 @@
{"id":"orch-plan-20260715-195505-1","ticket_id":"00001KXKJGYGD","kind":"accepted_plan","accepted_plan":{"summary":"Remove legacy Workflow tracking/resource/invocation machinery only. Delete ActiveWorkflow model-visible tools and durable active workflow state, remove workflow resource discovery and `/workflow-slug` invocation, delete or retire workflow crate/resources/tests, and clean prompt/docs wording. Leave Skills support to dependent follow-up Ticket `00001KXKMX0QM` and keep external state control in typed feature/tool surfaces.","branch":"work/00001KXKJGYGD-remove-workflow-tracking","worktree":"/home/hare/Projects/yoi/.worktree/00001KXKJGYGD-remove-workflow-tracking","role_plan":"Orchestrator accepts queued Ticket after inspecting Ticket/relation/plan/workspace state. Use sibling Coder for implementation in a dedicated worktree, then sibling Reviewer for read-only review. Orchestrator retains merge/final-validation/close/worktree cleanup authority. Do not call StopPod unless explicitly instructed."},"author":"orchestrator","at":"2026-07-15T19:55:05Z"}

View File

@ -1,40 +0,0 @@
---
title: 'Remove workflow tracking and workflow resources'
state: 'inprogress'
created_at: '2026-07-15T19:02:13Z'
updated_at: '2026-07-15T21:25:42Z'
assignee: null
queued_by: 'workspace-panel'
queued_at: '2026-07-15T19:54:36Z'
---
## 背景
現在の Workflow は、Workflow resource を single-step な procedural prompt として提示し、ActiveWorkflowList / ActiveWorkflowComplete / ActiveWorkflowCancel で追跡する中途半端な obligation tracking になっている。今後の再利用可能な作業手順は別 Ticket で扱う Agent Skills 形式へ寄せるため、この Ticket では既存 Workflow tracking / resource / invocation path の削除に範囲を限定する。
外部状態や authority は Workflow ではなく typed feature/tool surface が持つ。Ticket queue、Worker spawn、workdir 管理、review wait などの制御を Workflow に移さない。
## 要件
- ActiveWorkflow tracking 系を削除する。
- `ActiveWorkflowList` / `ActiveWorkflowComplete` / `ActiveWorkflowCancel` tools を削除する。
- Worker durable state、snapshot、compaction、rehydration、system-item extension から active workflow snapshot / obligation を削除する。
- resident workflow / active workflow prompt wording を削除する。
- Workflow resource / invocation path を削除する。
- `resources/workflows` / `.yoi/workflow` を長期 authority として扱わない。
- `/workflow-slug` 形式の invocation を削除する。
- Workflow 関連 crate/resource/API/test を削除する。
- 既存 role prompts / internal prompts / docs から Workflow 固有の記述を削除する。
- Workflow 削除後も Ticket / Worker / workdir / queue などの外部状態制御は typed feature/tool surface に残す。
- 既存 session に ActiveWorkflow storage が残っている場合の扱いを明示する。
- 初期実装では無視 / drop / diagnostic のいずれかを決め、migration code を増やしすぎない。
## 受け入れ条件
- `ActiveWorkflow*` tools が model-visible tool schema から消えている。
- active workflow state が Worker snapshot / compaction / rehydration に残らない。
- Workflow resource discovery / invocation path が削除されている。
- Workflow 関連 prompt wording / resident workflow advertisement が消えている。
- Workflow 関連 crate/resource/API/test が削除されている、または不要な互換 layer なしに整理されている。
- Ticket queue、Worker spawn、workdir 管理などの外部状態制御は Workflow ではなく feature/tool surface に残る。
- affected crates の `cargo test``nix build .#yoi` が通る。

View File

@ -1,329 +0,0 @@
<!-- event: create author: "yoi ticket" at: 2026-07-15T19:02:13Z -->
## 作成
LocalTicketBackend によって作成されました。
---
<!-- event: intake_summary author: hare at: 2026-07-15T19:27:11Z -->
## Intake summary
Marked ready by `yoi ticket state`.
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-15T19:27:11Z from: planning to: ready reason: cli_state field: state -->
## State changed
Marked ready by `yoi ticket state`.
---
<!-- event: state_changed author: workspace-panel at: 2026-07-15T19:54:36Z from: ready to: queued reason: queued field: state -->
## State changed
Ticket を `workspace-panel` が queued にしました。
---
<!-- event: decision author: orchestrator at: 2026-07-15T19:55:37Z -->
## Decision
Routing decision: implementation_ready
Reason:
- Ticket は前回の broad Skills support scope から修正され、今回は Workflow tracking / Workflow resource / Workflow invocation path の削除に範囲が限定されている。
- Skills support は incoming dependent Ticket `00001KXKMX0QM``depends_on` として後続に分離されており、この Ticket 自身の blocker ではない。
- `TicketRelationQuery(00001KXKJGYGD)` は incoming dependency のみで、outgoing blocker はない。
- `TicketOrchestrationPlanQuery(00001KXKJGYGD)` は事前 record なし。今回 accepted plan を記録した。
- `TicketList(inprogress)` は 0 件。
- Orchestrator worktree `/home/hare/Projects/yoi/.worktree/orchestration` は clean で、既存 implementation worktree/branch は見当たらない。
Evidence checked:
- Ticket body / thread / relations / artifacts。
- `TicketRelationQuery`, `TicketOrchestrationPlanQuery`, `TicketList(inprogress)`
- Orchestrator worktree / branch / worktree state。
- Previous broad scope was corrected: current title is `Remove workflow tracking and workflow resources` and body says Agent Skills is separate follow-up。
IntentPacket:
Intent:
- Legacy Workflow tracking/resource/invocation machinery を削除する。
- ActiveWorkflow obligation state と model-visible ActiveWorkflow tools を取り除く。
- Workflow resource authority と `/workflow-slug` invocation を廃止し、prompt/docs から Workflow 固有 wording を削除する。
Binding decisions / invariants:
- この Ticket では first-class Skills support を実装しない。Skills は dependent follow-up Ticket `00001KXKMX0QM` の責務。
- Workflow を scheduler / state machine / script / external-state automation layer として残さない。
- `ActiveWorkflowList` / `ActiveWorkflowComplete` / `ActiveWorkflowCancel` は model-visible tool schema から削除する。
- Worker durable state / snapshot / compaction / rehydration / system-item extension から active workflow snapshot / obligation を削除する。
- `resources/workflows` / `.yoi/workflow` は長期 authority として扱わない。
- Ticket / Worker / workdir / queue などの外部状態制御は typed feature/tool surface に残す。
- 既存 session に ActiveWorkflow storage がある場合は、初期実装では ignore / drop / diagnostic の bounded 方針にし、過剰な migration layer を作らない。
Requirements / acceptance criteria:
- ActiveWorkflow tools が schema から消えている。
- active workflow state が Worker snapshot / compaction / rehydration に残らない。
- Workflow resource discovery / invocation path が削除されている。
- Workflow prompt wording / resident workflow advertisement が消えている。
- Workflow 関連 crate/resource/API/test が削除または不要な互換なしに整理されている。
- External state control は feature/tool surface に残る。
- affected tests と `nix build .#yoi` が通る。
Implementation latitude:
- 既存 Workflow crate/resource を削除するか、必要最小限の型削除/rename にするかは dependency graph に応じて選んでよい。
- Persisted old ActiveWorkflow state は bounded compatibility として無視/drop/diagnostic のどれかを選び、設計が大きくなる場合は escalation。
- Prompt/docs cleanup の具体的な文言は Workflow authority を広告しない範囲で local tactic とする。
Escalate if:
- ActiveWorkflow state removal が destructive session migration や broad compatibility layer を必要とする場合。
- Skills support を同時実装しないと build/API が成立しない場合。
- `/slug` など user-facing invocation の代替設計をこの Ticket 内で固定する必要が出た場合。
- Workflow に external-state authority を残さないと既存 feature が動かない場合。
Validation:
- `rg "ActiveWorkflow|active_workflow|Active workflow"` with remaining hits explained or removed。
- `rg "resources/workflows|\.yoi/workflow|workflow invocation|Resident workflows"` with remaining hits explained or removed。
- `git diff --check`
- affected crate tests such as `cargo test -p worker --lib --tests`, `cargo test -p session-store --lib --tests`, `cargo test -p yoi --tests` if CLI/input touched, and workflow crate tests if crate remains。
- `cargo check -p yoi`
- `yoi ticket doctor`
- `nix build .#yoi --no-link`
Current code map / likely touch points:
- `crates/worker/src/active_workflow.rs`, `crates/worker/src/workflow`, `crates/worker/src/controller.rs`, `crates/worker/src/worker.rs`, `crates/worker/src/prompt/*`
- `crates/workflow/*`, `resources/workflows/*`, `resources/prompts/*`
- `crates/protocol/src/lib.rs`, `crates/session-store/src/system_item.rs`
- CLI/TUI input path for `/slug` workflow invocation if present。
- docs mentioning Workflow / ActiveWorkflow / resident workflows。
Critical risks / reviewer focus:
- ActiveWorkflow tools/state still present under renamed or hidden paths。
- Workflow resource discovery/invocation surviving as hidden active path。
- Prompt/docs still advertising Workflow obligations or resident workflows。
- Accidentally implementing broad Skills support in this Ticket instead of leaving it to dependent follow-up。
- Ticket queue / Worker spawn / workdir management logic being moved into Workflow/Skill rather than remaining typed tools/features。
Next action:
- Record `queued -> inprogress`, create dedicated implementation worktree, and route to sibling Coder Pod。
---
<!-- event: state_changed author: orchestrator at: 2026-07-15T19:55:42Z from: queued to: inprogress reason: accepted_for_implementation field: state -->
## State changed
Dashboard queue authorization was inspected, no blockers were found, and an accepted plan was recorded. Moving queued Ticket to inprogress before creating worktree or spawning role Pods.
---
<!-- event: implementation_report author: orchestrator at: 2026-07-15T20:46:17Z -->
## Implementation report
Implementation progress report:
- Coder Pod `yoi-coder-00001KXKJGYGD-workflow-removal` completed implementation in `/home/hare/Projects/yoi/.worktree/00001KXKJGYGD-remove-workflow-tracking` on branch `work/00001KXKJGYGD-remove-workflow-tracking`
- Implementation commit: `d801b2698bb1b3d07e0d49af71b2e4777efd3f6e` (`refactor: remove workflow machinery`)。
- Orchestrator inspected worktree status, branch log, commit stats, `git diff --check 83ad7506..HEAD`, and grep checks for ActiveWorkflow / Workflow resource references. Worktree was clean and diff check passed。
Implementation summary:
- Removed model-visible `ActiveWorkflowList`, `ActiveWorkflowComplete`, and `ActiveWorkflowCancel` tools。
- Removed Worker active-workflow durable state, extension snapshotting, compaction/rehydration handling, and prompt re-injection paths。
- Removed Workflow registry/resource loading and `/workflow-slug` invocation behavior。
- Deleted `crates/workflow` crate and tracked `resources/workflows` resources。
- Removed `WorkflowInvoke` from protocol/TUI/web generated protocol surfaces and slash workflow completion behavior。
- Removed `.yoi/workflow` memory/workspace authority and workflow usage-source handling。
- Updated prompts/docs/config/role launch code to remove resident workflow and workflow invocation wording。
- Updated Ticket role launch/config handling to use typed role launch prompts without workflow slugs。
- Added bounded old-session handling: old persisted `kind: "workflow"` `SystemItem`s deserialize as `LegacyIgnored` and replay only a non-authority ignore diagnostic。
- Confirmed broad first-class Skills support was not implemented in this Ticket。
Files/resources touched include:
- `crates/worker/src/active_workflow.rs` removed。
- `crates/worker/src/workflow/mod.rs` removed。
- `crates/workflow/*` removed。
- `resources/workflows/*` removed。
- Protocol/TUI/web generated protocol surfaces updated。
- Prompt/docs/config references updated。
Coder-reported validation passed:
- `rg "ActiveWorkflow|active_workflow|Active workflow"`: no matches。
- `rg "resources/workflows|\\.yoi/workflow|workflow invocation|Resident workflows"`: no matches。
- `git diff --check`
- `cargo test -p session-store --lib --tests`
- `cargo test -p worker --lib --tests`
- `cargo test -p yoi --tests`
- `cargo check -p yoi`
- `yoi ticket doctor`
- `nix build .#yoi --no-link`
Next action:
- Route to external Reviewer Pod before merge/close decisions。
---
<!-- event: review author: reviewer at: 2026-07-15T20:53:02Z status: request_changes -->
## Review: request changes
External review result: request_changes
Evidence reviewed:
- Implementation worktree was clean。
- `rg "ActiveWorkflow|active_workflow|Active workflow"` and `rg "resources/workflows|\\.yoi/workflow|workflow invocation|Resident workflows"` were clean。
Blockers:
1. `WorkflowInvoke` web/generated surfaces remain active。
- `web/workspace/src/lib/generated/protocol.ts:82` still includes `{ kind: "workflow_invoke", slug: string }`
- `web/workspace/src/lib/workspace/console/composer-command.ts:191` still emits `workflow_invoke`
- `web/workspace/src/lib/workspace/console/model.ts:322` still handles it。
- `composer-command.test.ts:28` still expects it。
- This violates workflow resource/invocation removal acceptance。
2. Relevant tests fail after removal。
- `cargo test -p ticket --lib scaffold_config_includes_backend_and_all_fixed_roles -- --nocapture` fails because `crates/ticket/src/config.rs:1115-1120` still asserts role `workflow = ...`
- `cargo test -p tui --lib live_system_item_workflow_appends_system_message_block -- --nocapture` fails because `crates/tui/src/app.rs:3326-3340` still expects legacy workflow SystemItem rendering instead of ignored/drop/diagnostic behavior。
- This violates validation acceptance。
3. Stale docs/config wording remains。
- `docs/manifest.toml:283-293` still documents loading `skills.directories` “as Workflows,” conflicting with prompt/docs cleanup and Workflow authority removal。
Non-blocking note:
- Core Rust ActiveWorkflow/resource deletions appear largely in place, and broad new Skills support was not introduced in the inspected diff。
---
<!-- event: implementation_report author: orchestrator at: 2026-07-15T21:07:01Z -->
## Implementation report
Follow-up implementation progress report after request_changes:
- Coder Pod `yoi-coder-00001KXKJGYGD-workflow-removal` completed the focused review-blocker fix in `/home/hare/Projects/yoi/.worktree/00001KXKJGYGD-remove-workflow-tracking`
- Follow-up commit: `d30dca2d99812f457324b91fd3549f4f126b6d7c` (`fix: remove remaining workflow invoke surfaces`)。
- Orchestrator inspected worktree status, branch log, commit stats, `git diff --check 83ad7506..HEAD`, and grep checks. Worktree was clean and diff check passed。
Fix summary:
- Removed stale web `workflow_invoke` surfaces:
- `web/workspace/src/lib/generated/protocol.ts`
- `web/workspace/src/lib/workspace/console/composer-command.ts`
- `web/workspace/src/lib/workspace/console/model.ts`
- `web/workspace/src/lib/workspace/console/composer-command.test.ts`
- Updated generated TypeScript protocol to match Rust protocol without `WorkflowInvoke`
- Removed stale Ticket role scaffold test expectation for `workflow = ...`
- Replaced stale TUI workflow SystemItem rendering test with legacy workflow SystemItem ignore-path test。
- Removed docs/config wording that advertised loading Skills as Workflows。
- Stabilized Worker discovery restore-planning test by sharing the runtime-dir sandbox used by allocation tests。
Coder-reported validation passed:
- `rg "ActiveWorkflow|active_workflow|Active workflow"`: no matches。
- `rg "resources/workflows|\\.yoi/workflow|workflow invocation|Resident workflows|workflow_invoke"`: no matches。
- `git diff --check`
- `cargo test -p ticket --lib scaffold_config_includes_backend_and_all_fixed_roles -- --nocapture`
- `cargo test -p tui --lib live_legacy_workflow_system_item_is_ignored -- --nocapture`
- `cargo test -p session-store --lib --tests`
- `cargo test -p worker --lib --tests`
- `cargo test -p yoi --tests`
- `cd web/workspace && deno task check && deno task test`
- `cargo check -p yoi`
- `yoi ticket doctor`
- `nix build .#yoi --no-link`
Knowledge removal Ticket `00001KXKP2A71` remains queued and will be re-routed after this Workflow removal Ticket is merged/closed, per human preference to proceed after completion rather than combined branch。
---
<!-- event: review author: reviewer at: 2026-07-15T21:10:11Z status: request_changes -->
## Review: request changes
Focused re-review result: request_changes
Evidence:
- Prior Rust/TUI test blockers are fixed:
- `cargo test -p ticket --lib scaffold_config_includes_backend_and_all_fixed_roles` passes。
- `cargo test -p tui --lib live_legacy_workflow_system_item_is_ignored` passes。
- `ActiveWorkflow` / workflow resource greps over `crates web resources docs` are clean。
- `workflow_invoke` is removed from generated protocol/composer/model。
- Legacy workflow SystemItems are ignored in TUI。
- `docs/manifest.toml` no longer says Skills load “as Workflows”。
Remaining blocker:
- Slash workflow completion path remains active, violating acceptance item for removing workflow resource/invocation/completion surfaces。
- `web/workspace/src/lib/workspace/console/composer-completion.ts:1,4,38,82-83` still treats `/` as workflow completion。
- `web/workspace/src/lib/workspace/console/composer-completion.test.ts:35` still asserts `completionTokenAt("run /work")?.kind === "workflow"`
- `web/workspace/src/routes/w/[workspaceId]/+page.svelte:354-361` still posts `token.kind` to the completions endpoint, and result type still allows `"workflow"` at `+page.svelte:48-49`
- `crates/protocol/src/lib.rs:604-608,619` comments still advertise `/ -> Workflow` and workflow resolvers。
Blocker: remove the remaining slash workflow completion surface and stale protocol comments/types, without introducing hidden Workflow compatibility。
---
<!-- event: implementation_report author: orchestrator at: 2026-07-15T21:23:24Z -->
## Implementation report
Second follow-up implementation progress report after request_changes:
- Coder Pod `yoi-coder-00001KXKJGYGD-workflow-removal` completed the remaining slash workflow completion fix。
- Follow-up commit: `0b56052a447adeb12ee641f1136d121431fbd798` (`fix: remove workflow completion surface`)。
- Orchestrator inspected worktree status, branch log, commit stats, and `git diff --check 83ad7506..HEAD`; worktree was clean and diff check passed。
Fix summary:
- Removed `/` as a web console completion sigil。
- Web console completions now only recognize commands, file refs, and Knowledge refs。
- Removed `workflow` from web console completion result typing。
- Updated route-side completion response typing to exclude `workflow`
- Updated web completion tests so slash text remains plain text / no completion token。
- Removed stale Workflow completion/resolver wording from protocol comments。
- Did not add hidden Workflow compatibility or broad Skills replacement。
Coder-reported validation passed:
- `rg "ActiveWorkflow|active_workflow|Active workflow"`: no matches。
- Focused completion-surface grep for `workflow_invoke`, workflow completion kind, slash Workflow resolver wording, `resources/workflows`, `.yoi/workflow`, `workflow invocation`, `Resident workflows`: no matches。
- Focused web/protocol grep for `kind.*workflow`: no matches; remaining project-wide `kind: "workflow"` mentions are only legacy persisted `SystemItem` ignore tests for bounded compatibility。
- `git diff --check`
- `cd web/workspace && deno task check && deno task test`
- `cargo test -p yoi --tests`
- `cargo check -p yoi`
- `yoi ticket doctor`
- `nix build .#yoi --no-link`
Next action:
- Request focused re-review of the remaining workflow completion blocker and overall acceptance before merge/close decisions。
---
<!-- event: review author: reviewer at: 2026-07-15T21:25:42Z status: request_changes -->
## Review: request changes
Focused re-review result: request_changes
Evidence:
- Slash workflow completion blocker appears fixed。
- Focused greps for `workflow_invoke`, workflow completion/resolver wording, `kind.*workflow`, `resources/workflows`, `.yoi/workflow`, `workflow invocation`, and `Resident workflows` in protocol/web/resources/docs were clean。
- Web console completion no longer exposes workflow invocation。
- ActiveWorkflow/resource greps remain clean。
- Reviewer did not see broad new Skills support beyond existing manifest/config docs。
Remaining blocker:
- A stale TUI unit test still uses a legacy `"kind": "workflow"` SystemItem as an active task-store snapshot carrier and now fails because workflow SystemItems are ignored。
- Location: `crates/tui/src/app.rs:3568-3600`, including comment at `3587-3588`, JSON kind at `3591`
- Failing command: `cargo test -p tui --lib live_system_snapshot_replaces_task_store -- --nocapture`
- Failure at `crates/tui/src/app.rs:3599` (`left: 1`, `right: 4`)。
- This leaves relevant tests failing and contradicts bounded legacy workflow behavior / test cleanup expectation。
Required fix:
- Update the test fixture to use a non-legacy active SystemItem kind or otherwise align it with workflow ignore semantics。
---

View File

@ -1,21 +0,0 @@
{
"version": 1,
"relations": [
{
"ticket_id": "00001KXKMX0QM",
"kind": "depends_on",
"target": "00001KXKJGYGD",
"note": "Skills support follows removal of legacy workflow tracking/resource paths.",
"author": "yoi ticket",
"at": "2026-07-15T19:45:40Z"
},
{
"ticket_id": "00001KXKMX0QM",
"kind": "depends_on",
"target": "00001KXKP2A71",
"note": "Agent Skills support should be implemented after obsolete Knowledge support is removed.",
"author": "yoi ticket",
"at": "2026-07-15T20:04:47Z"
}
]
}

View File

@ -1,166 +0,0 @@
---
title: 'Implement Agent Skills support'
state: 'queued'
created_at: '2026-07-15T19:43:46Z'
updated_at: '2026-07-15T21:26:15Z'
assignee: null
queued_by: 'workspace-panel'
queued_at: '2026-07-15T21:26:15Z'
---
## 背景
Yoi は再利用可能な作業手順、作法、報告形式、補助資料を、LLM-facing な手順資源として扱う必要がある。これらは external state や authority を直接動かす仕組みではなく、Agent が必要な時に参照・活用する procedural guidance として定義する。
Agent Skills 標準に従い、workspace-local Skills は `.yoi/skills/` 配下の `SKILL.md` package として扱う。Skill discovery / lint / catalog / activation は Workspace backend を authority とし、Worker は Workspace API から Skill metadata / body を受け取る。Ticket、Worker、workdir、repository、network などの外部状態変更は typed feature/tool surface が持ち、Skill はその使い方や作業の進め方を記述する。
## 要件
### Agent Skills 形式
- Skill の workspace ディレクトリは `.yoi/skills/` とする。
- Skill は Agent Skills 標準に従い、各 Skill をディレクトリとして配置する。
```text
.yoi/skills/
<skill-name>/
SKILL.md # required
scripts/ # optional
references/ # optional
assets/ # optional
```
- `SKILL.md` は YAML frontmatter + Markdown body とする。
- `SKILL.md` frontmatter は少なくとも以下を扱う。
- `name` required。
- parent directory name と一致する。
- 1-64 characters。
- lowercase letters / numbers / hyphen のみ。
- 先頭/末尾 hyphen と連続 hyphen を禁止する。
- `description` required。
- 1-1024 characters。
- Skill が何をするか、いつ使うかを具体的に書く。
- `license` optional。
- `compatibility` optional。
- `metadata` optional string map。
- `allowed-tools` optional / experimental。実装可否は要決定事項とする。
- `SKILL.md` body は LLM-facing procedural guidance として読み込む。
- 推奨内容: step-by-step instructions、input/output examples、edge cases、report shape。
- main `SKILL.md` は大きくしすぎず、詳細は `references/` 等へ分ける。
- `scripts/` / `references/` / `assets/` は Skill root 相対 path で参照する。
- `references/` は必要時に読む追加 docs。
- `scripts/` は agent が実行し得る補助 code。
- `assets/` は templates / static resources。
- Workflow projection のために作られた標準外の独自仕様は Skill schema から削除する。
- Skill を `WorkflowRecord` に変換するための `model_invokation` / `user_invocable` 相当の概念を Skill frontmatter / catalog に持ち込まない。
- workflow invocation や workflow graph 前提の field / semantics を Agent Skills として受理しない。
- 既存 parser が workflow 互換のために受けている field がある場合、標準 field か Yoi の明示拡張として再分類し、不要なものは warning ではなく lint error または unsupported として扱う。
- Agent Skills 標準外の Yoi 拡張を残す場合は、projection 由来ではなく独立した必要性を説明し、namespace / compatibility 方針を明示する。
### Workspace authority / API
- Skill discovery / lint / catalog / activation の authority は Workspace backend とする。
- `.yoi/skills/` は tracked workspace data の保存場所であり、Worker が各自で filesystem scan する source of truth ではない。
- Web Workspace UI、Runtime Worker、embedded Worker、CLI は Workspace backend API 経由で同じ Skill view を得る。
- Ticket backend API と同様に、Worker / Runtime process が Workspace backend 経由で Skill を扱える typed API を整備する。
- Skill catalog/list endpoint。
- Skill detail/read endpoint。
- Skill lint/diagnostics endpoint。
- Skill activation body endpoint。
- references / assets の resource read endpoint、または backend-resolved authority を返す endpoint。
- 必要なら Worker-facing Skill operation endpoint を用意し、Worker-local filesystem scan に依存しない。
- Workspace backend は Skill catalog API を提供する。
- `name` / `description` / source / provenance / override status / diagnostics を返す。
- invalid Skill は catalog diagnostics として報告し、Worker ごとにばらばらな parse behavior を持たせない。
- Workspace backend は Skill activation/read API を提供する。
- `/skill-slug` activation は Workspace backend から full `SKILL.md` body を取得し、Worker history に append / commit してから LLM context に載せる。
- Worker は `WorkspaceClient::Http` がある場合、Skill body を local file path から直接読まない。
- Local-only / legacy Worker path が必要な場合も、同じ Workspace skill loader logic を共有し、別 schema / 別 precedence を作らない。
- Workspace settings / Profile / role が advertised skills や default visible skill catalog を指定する場合も、最終的な解決は Workspace backend の catalog authority に寄せる。
### Progressive disclosure / loading
- Skill metadata は Worker initialization 時に Workspace backend から軽量 catalog として取得できるようにする。
- model-visible な常時情報は `name``description` を中心にする。
- Full `SKILL.md` body は Skill が activate / select された時だけ Workspace backend から取得して LLM context に入れる。
- `references/` / `scripts/` / `assets/` の内容はさらに必要時だけ読む。
- Builtin skill と workspace skill の discovery / override / provenance を定義する。
- workspace `.yoi/skills/<name>/SKILL.md` が同名 builtin skill を override できるかを明示する。
- Skill catalog には source / provenance / override status を含める。
### Skill と他概念の境界
- Knowledge と Skill の境界を明確にする。
- Knowledge は facts / rationale / reference。
- Skill は task execution guidance / reusable procedure / report shape。
- Plugin / feature との境界を明確にする。
- Skill は prompt/resource であり、外部状態を直接制御しない。
- Ticket / Worker / workdir / queue / repository / network などの authority と状態変更は typed feature/tool surface が持つ。
- Coder review cycle や Orchestrator queue consumption は、Skill + Workspace/Ticket/Worker feature tools の組み合わせで設計する。
- 既存 role prompts / internal prompts / docs は Skill の利用を前提に整理する。
### Validation / tooling
- Skill lint を追加する。
- `SKILL.md` frontmatter required fields / naming convention / parent directory match を検証する。
- unknown / unsupported fields は方針に従って warning または error にする。
- relative file references の安全性を検証する。
- Skill discovery tests を追加する。
- builtin skill loading。
- workspace `.yoi/skills` loading。
- override priority。
- provenance reporting。
- invalid Skill diagnostics。
- Workspace Skill API tests を追加する。
- catalog/list が Workspace settings と `.yoi/skills` を authority として使うこと。
- detail/read/activation body が同じ Skill resolution を使うこと。
- lint diagnostics が Worker / Web / CLI で共有できる response schema になること。
- Worker-facing API 経由で Skill activation でき、Worker-local scan と結果が分岐しないこと。
## 参照
実装中に Agent Skills 標準や harness 側裁量を確認する必要が出た場合は、以下を参照する。
- Agent Skills home: https://agentskills.io/home
- Agent Skills specification: https://agentskills.io/specification
- Client implementation guide: https://agentskills.io/client-implementation/adding-skills-support
- Skill creation best practices: https://agentskills.io/skill-creation/best-practices
- Reference implementation / validator: https://github.com/agentskills/agentskills/tree/main/skills-ref
## 要決定事項
- Skill activation surface。
- `/skill-name` のような slash command を持つか、Skill selection / activation tool にするか。
- Skill catalog を model-visible にする方法。
- 起動時 prompt に metadata 一覧を常時載せるか、Workspace SkillList / SkillRead API-backed tools で探索させるか。
- `allowed-tools` の扱い。
- Agent Skills 標準では experimental なので、Yoi で authority として尊重するか、lint metadata として読むだけにするか。
- 尊重する場合、既存 feature/tool permission とどう合成するか。
- `scripts/` の実行 authority。
- Skill bundled script を通常 Bash / tool authority で実行するだけにするか、Skill script 実行専用 tool を作るか。
- script dependencies / shebang / executable bit / sandbox の扱い。
- Skill file access。
- `references/` / `assets/` を Workspace Skill resource API 経由で読むか、通常 `Read` に Workspace backend-resolved path authority を渡すか。
- deeply nested reference chain をどこまで許すか。
- Builtin skill の配置場所。
- `resources/skills/<name>/SKILL.md` とするか、別の runtime resource layout にするか。
- Workspace override policy。
- workspace skill が builtin skill を完全 override するか、同名 conflict を error にするか。
- Profile / role との接続。
- Profile で default active skills / advertised skills を指定するか。
- Orchestrator / Coder / Reviewer roles にどの Skill catalog を見せるか。
- Web Workspace UI。
- `.yoi/skills` の一覧 / validation / edit を Web で扱うか、初期実装では backend / CLI のみにするか。
## 受け入れ条件
- `.yoi/skills/<skill-name>/SKILL.md` が Agent Skills 標準の required frontmatter と naming rules で Workspace backend によって lint される。
- builtin/workspace Skill の loading、override priority、provenance、lint が Workspace backend API tests で確認されている。
- Ticket backend API と同様に、Worker / Runtime process / Web / CLI が共有できる Skill catalog/read/lint/activation API が整備され、tests で確認されている。
- Worker / Web / CLI が同じ Workspace backend Skill catalog を参照し、Worker-local filesystem scan が primary authority になっていないことを tests で確認している。
- Full `SKILL.md` body は activation 時にのみ Workspace backend から取得されて LLM context に入り、metadata catalog と progressive disclosure の分離が tests で確認されている。
- Workflow projection 由来の標準外 Skill fields / semantics が Skill schema から除去され、Agent Skills 標準 field と明示的な Yoi 拡張だけが受理されることを tests で確認している。
- role prompts / internal prompts / docs が Skill 前提に更新されている。
- Ticket queue、Worker spawn、workdir 管理などの外部状態制御は Skill ではなく feature/tool surface に残る。
- Skill implementation decision points のうち初期実装で決めないものは、明示的に unsupported / ignored / warning として扱われる。
- affected crates の `cargo test``nix build .#yoi` が通る。

View File

@ -1,137 +0,0 @@
<!-- event: create author: "yoi ticket" at: 2026-07-15T19:43:46Z -->
## 作成
LocalTicketBackend によって作成されました。
---
<!-- event: comment author: hare at: 2026-07-15T19:56:41Z -->
## Comment
## Current implementation check: Skills support
現行の Skills support は存在するが、first-class Skill ではなく **Workflow ingestion の一部**として実装されている。
### 既にあるもの
- `crates/workflow/src/skill.rs``SKILL.md` parser がある。
- `<skills-root>/<name>/SKILL.md` を読む。
- YAML frontmatter + Markdown body を要求する。
- `name` / `description` は required。
- `name` は parent directory name と一致する必要がある。
- `name` は既存 `Slug` validation を通る必要がある。
- `description` は non-empty かつ `WORKFLOW_DESCRIPTION_HARD_CAP` 以内。
- `license` / `compatibility` / `metadata` / `allowed-tools` は parse 上は受理する。
- unknown frontmatter fields は warning で無視する。
- `allowed-tools` は warning を出すだけで enforcement しない。
- `load_skills_from_dir(root)` がある。
- root 直下の directory だけを見て、各 `<name>/SKILL.md` を読む。
- broken skill は warning で skip し、siblings は読み続ける。
- missing root は empty 扱い。
- deterministic order で読み込む。
- `manifest``[skills] directories = [...]` がある。
- path は manifest/profile base から resolve される。
- validation 後は absolute path required。
- profile/manifest merge で directories は extend される。
- Worker startup で `[skills].directories` が scope read allow に追加される。
- skill root は recursive read allow になる。
- `SKILL.md` / `scripts/` / `references/` / `assets/` 全体を通常 Read できる。
- Worker startup で Skills が workflow registry に取り込まれる。
- `SkillRecord::into_workflow_record()` により Skill が `WorkflowRecord` に変換される。
- `model_invokation = true`、`user_invocable = true`。
- つまり Skill は `/<slug>` で呼べる Workflow として扱われる。
- shadowing support がある。
- internal/builtin/workspace Workflow が同 slug を持つ Skill より優先される。
- 複数 skill directories 間では first-fed wins。
- shadowed Skill は Worker notification に `[Skill shadowed] ...` として流れる。
- tests は parser / directory loading / Workflow projection / scope allow / ingest_skills をある程度カバーしている。
### 現行の限界
- Skill は first-class ではなく、WorkflowRecord へ projection されている。
- Skill catalog / Skill activation / Skill provenance は Workflow registry に埋もれる。
- Workflow 削除 Ticket と衝突する構造になっている。
- `.yoi/skills/` は implicit workspace Skill root ではない。
- 現在は `[skills].directories` に明示された root だけを読む。
- default workspace path として `.yoi/skills` を probe しない。
- builtin skills はない。
- `resources/skills/<name>/SKILL.md` のような bundled layout は未定義。
- Progressive disclosure は不完全。
- metadata は resident workflow entry として出るが、Skill catalog と full body activation が分離されていない。
- full `SKILL.md` body は workflow invocation body として扱われるだけ。
- `references/` / `scripts/` / `assets/` はただ read-scope に入るだけで、Skill-aware access/tooling はない。
- `allowed-tools` は authority に反映されない。
- parser は認識するが warning のみ。
- feature/tool permission と合成しない。
- frontmatter optional fields は保持されない。
- `license` / `compatibility` / `metadata` は parse acceptance のみで、SkillRecord / catalog には残らない。
- lint surface が独立していない。
- parser tests はあるが、`yoi skill lint` のような user-facing lint/check はない。
- Skill resource safety は最小限。
- skill root を recursive read allow にしているだけ。
- relative references / nested reference chain / script execution policy は未実装。
- Web Workspace UI / backend API はない。
- `.yoi/skills` の list / validation / edit surface は未実装。
- current docs/comments は Skill を Workflow ingestion として説明しており、今後の方針と逆。
- `crates/workflow/src/skill.rs` の module docs も「ingests it as a Workflow」と明記している。
### 現在地まとめ
現在の Skills support は **Agent Skills parser + manifest-configured directory ingest + Workflow compatibility projection** まで。Agent Skills 標準の directory shape と basic frontmatter validation は部分的にあるが、Yoi の Skill としての lifecycle / catalog / activation / provenance / lint / UI / authority boundary は未実装。
この Ticket の実装では、まず Workflow projection から切り離して、`.yoi/skills/<skill-name>/SKILL.md` を workspace Skill authority として扱う first-class Skill loader/catalog へ移す必要がある。
---
<!-- event: decision author: hare at: 2026-07-15T20:43:42Z -->
## Decision
Workspace backend を Skill discovery / lint / catalog / activation の authority とする。
`.yoi/skills/` は tracked workspace data の保存場所だが、Runtime Worker が各自で直接 scan して別々の Skill view を持つ構造にはしない。Web Workspace UI、Runtime Worker、embedded Worker、CLI は Workspace backend API から同じ catalog / diagnostics / activation body を取得する。
これにより、Skill の override/provenance/lint diagnostics、Web からの編集・検証、Worker への progressive disclosure を同じ authority に寄せる。
---
<!-- event: decision author: hare at: 2026-07-15T21:02:33Z -->
## Decision
Skill support should mirror the Ticket backend direction: Workspace backend owns the shared authority and exposes typed APIs for Worker / Runtime process / Web / CLI access.
Required API surface includes catalog/list, detail/read, lint/diagnostics, activation body retrieval, and resource access or backend-resolved authority for references/assets. Worker-local filesystem scanning must not become the primary authority when `WorkspaceClient::Http` is available.
---
<!-- event: intake_summary author: hare at: 2026-07-15T21:17:01Z -->
## Intake summary
Marked ready by `yoi ticket state`.
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-15T21:17:01Z from: planning to: ready reason: cli_state field: state -->
## State changed
Marked ready by `yoi ticket state`.
---
<!-- event: state_changed author: workspace-panel at: 2026-07-15T21:26:15Z from: ready to: queued reason: queued field: state -->
## State changed
Ticket を `workspace-panel` が queued にしました。
---

View File

@ -1,2 +0,0 @@
{"id":"orch-plan-20260715-202731-1","ticket_id":"00001KXKP2A71","kind":"after","related_ticket":"00001KXKJGYGD","note":"この Ticket は Knowledge removal 自体には blocker relation がないが、現在 `00001KXKJGYGD` が inprogress で Workflow tracking/resource removal を実装中。両者は `crates/worker` prompt/context、`crates/protocol`/session item、resource/docs cleanup、および Workflow required Knowledge dependency 周辺で重なる。Ticket body も Workflow required Knowledge dependency は Workflow removal 側で消える前提としているため、`00001KXKJGYGD` の review/merge/close 後に開始する。","author":"orchestrator","at":"2026-07-15T20:27:31Z"}
{"id":"orch-plan-20260715-202739-2","ticket_id":"00001KXKP2A71","kind":"waiting_capacity_note","note":"Queue routing inspected. `00001KXKP2A71` is concrete and has no typed blocker relation, but it should not be started in parallel with active `00001KXKJGYGD`: both remove old context/resource machinery and likely touch shared prompt/context/session/protocol/resource/docs surfaces. Running both now would create conflict and review-boundary risk. Leave queued until `00001KXKJGYGD` is approved/merged/closed, then re-route Knowledge removal before Skills support `00001KXKMX0QM`.","author":"orchestrator","at":"2026-07-15T20:27:39Z"}

View File

@ -1,50 +0,0 @@
---
title: 'Remove Knowledge support'
state: 'queued'
created_at: '2026-07-15T20:04:08Z'
updated_at: '2026-07-15T20:27:51Z'
assignee: null
queued_by: 'workspace-panel'
queued_at: '2026-07-15T20:27:02Z'
---
## 背景
Knowledge は `.yoi/knowledge`、`KnowledgeQuery`、`#<slug>` reference、resident Knowledge injection、Workflow `requires` dependency などとして残っているが、現状の dogfooding ではほぼ使われていない。再利用可能な手順・参照資源は Agent Skills / `SKILL.md` と typed feature/tool surface へ寄せる方針にするため、Knowledge 固有機能を先に削除して概念境界を単純化する。
Memory は durable summary / decisions / requests として残すが、Knowledge は separate record kind としては廃止する。
## 要件
- `.yoi/knowledge` を active workspace record authority として扱わない。
- model-visible Knowledge tools を削除する。
- `KnowledgeQuery` を削除する。
- `MemoryRead` / `MemoryWrite` / `MemoryEdit` / `MemoryDelete``kind = knowledge` を削除する。
- LLM prompt / tool guidance から Knowledge 利用指示を削除し、Memory / Ticket / Skill / repository files の使い分けに更新する。
- resident Knowledge injection を削除する。
- `model_invokation` / `ResidentKnowledgeEntry` / resident knowledge collection を削除する。
- compaction / rehydration / prompt rendering に Knowledge section が残らないようにする。
- TUI / protocol の `#<slug>` Knowledge reference と completion を削除する。
- parser / chips / styling / completion kind / system item handling を整理する。
- `#` sigil が不要なら未使用または将来予約として扱う。
- Workflow の required Knowledge dependency は、Workflow removal Ticket 側で消える前提とし、残る intermediate code では Knowledge dependency を増やさない。
- memory lint / memory consolidation / memory extraction から Knowledge record kind を削除または無効化する。
- consolidation prompt は Knowledge 作成・更新を指示しない。
- memory linter は `.yoi/knowledge` を対象にしない。
- docs / reports / README / crate docs の Knowledge 前提を整理する。
- historical report は必要ならそのままでよいが、current design docs は Knowledge を active feature として説明しない。
- 既存 `.yoi/knowledge` data がある場合の扱いを明示する。
- 初期実装では migration code を増やしすぎず、ignore / diagnostic / manual archive のいずれかにする。
- Skills support Ticket は Knowledge removal 後に進める。
- Skill は Knowledge replacement ではなく、procedural guidance の first-class resource として扱う。
## 受け入れ条件
- `KnowledgeQuery``kind = knowledge` が model-visible tool schema から消えている。
- Worker prompt / resident context / compaction / rehydration に Knowledge section が出ない。
- TUI / protocol から `#<slug>` Knowledge ref path が削除されている、または明示的に unsupported になる。
- memory lint / consolidation が Knowledge record を作成・更新・検査しない。
- current docs で Knowledge が active supported feature として説明されていない。
- Skills support Ticket `00001KXKMX0QM` がこの Ticket を prerequisite として扱う。
- affected crates の `cargo test``nix build .#yoi` が通る。

View File

@ -1,67 +0,0 @@
<!-- event: create author: "yoi ticket" at: 2026-07-15T20:04:08Z -->
## 作成
LocalTicketBackend によって作成されました。
---
<!-- event: intake_summary author: hare at: 2026-07-15T20:04:47Z -->
## Intake summary
Marked ready by `yoi ticket state`.
---
<!-- event: state_changed author: "yoi ticket" at: 2026-07-15T20:04:47Z from: planning to: ready reason: cli_state field: state -->
## State changed
Marked ready by `yoi ticket state`.
---
<!-- event: state_changed author: workspace-panel at: 2026-07-15T20:27:02Z from: ready to: queued reason: queued field: state -->
## State changed
Ticket を `workspace-panel` が queued にしました。
---
<!-- event: decision author: orchestrator at: 2026-07-15T20:27:51Z -->
## Decision
Routing decision: hold_queued_due_conflict_with_active_removal_work
Reason:
- Dashboard queue authorization was inspected, and this Ticket is concrete with no outgoing blocker relation。
- However, `TicketList(inprogress)` shows active Ticket `00001KXKJGYGD` (`Remove workflow tracking and workflow resources`) currently in implementation with Coder Pod running。
- This Knowledge removal Ticket and Workflow removal Ticket likely touch overlapping surfaces:
- `crates/worker` prompt/context/resident injection paths。
- `crates/protocol` / `crates/session-store` reference/system-item state paths。
- resource cleanup under prompts/resources/docs。
- Workflow required Knowledge dependency, which this Ticket body explicitly says is expected to disappear via Workflow removal。
- Starting this Ticket now would create avoidable merge conflicts and make reviewer acceptance boundaries ambiguous。
- Therefore this pass leaves the Ticket `queued` and does not record `queued -> inprogress`, create a worktree, or spawn role Pods。
Evidence checked:
- Ticket body / thread / relations。
- `TicketRelationQuery(00001KXKP2A71)`: incoming `depends_on` from Skills support Ticket `00001KXKMX0QM`; no blocker for this Ticket。
- `TicketOrchestrationPlanQuery(00001KXKP2A71)`: no prior records; recorded `after 00001KXKJGYGD` and waiting note in this pass。
- `TicketList(inprogress)`: `00001KXKJGYGD` active。
- Orchestrator worktree status and worktree list: active Workflow removal worktree exists。
- Visible Pods: Workflow removal Coder Pod is running。
Next action:
- Wait for `00001KXKJGYGD` to be reviewed, merged, validated, and closed。
- Then re-route `00001KXKP2A71` before starting dependent Skills support `00001KXKMX0QM`
Escalate if:
- Human explicitly wants Knowledge removal and Workflow removal combined in one branch/worktree, or accepts parallel conflict/review-boundary risk。
---

View File

@ -1,26 +1,3 @@
workspace_id = "0197a949-4b6b-7f2a-9d9a-1f87e3a4c5b6"
created_at = "2026-06-23T00:00:00Z"
display_name = "yoi"
[ticket]
language = "Japanese"
[ticket.backend]
provider = "builtin:yoi_local"
root = ".yoi/tickets"
[ticket.roles.intake]
profile = "builtin:intake"
workflow = "ticket-intake-workflow"
[ticket.roles.orchestrator]
profile = "builtin:orchestrator"
workflow = "ticket-orchestrator-routing"
[ticket.roles.coder]
profile = "builtin:coder"
workflow = "multi-agent-workflow"
[ticket.roles.reviewer]
profile = "builtin:reviewer"
workflow = "multi-agent-workflow"

103
Cargo.lock generated
View File

@ -548,6 +548,16 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation"
version = "0.10.1"
@ -1658,9 +1668,11 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"socket2",
"system-configuration",
"tokio",
"tower-service",
"tracing",
"windows-registry",
]
[[package]]
@ -2096,8 +2108,6 @@ name = "llm-engine"
version = "0.2.1"
dependencies = [
"async-trait",
"base64",
"chrono",
"clap",
"dotenv",
"eventsource-stream",
@ -2221,7 +2231,6 @@ dependencies = [
"serde",
"serde_ignored",
"serde_json",
"serial_test",
"sha2 0.10.9",
"tempfile",
"thiserror 2.0.18",
@ -2898,6 +2907,28 @@ dependencies = [
"uuid",
]
[[package]]
name = "provider"
version = "0.1.0"
dependencies = [
"async-trait",
"base64",
"chrono",
"llm-engine",
"manifest",
"reqwest",
"secrets",
"serde",
"serde_json",
"serial_test",
"tempfile",
"thiserror 2.0.18",
"tokio",
"toml",
"tracing",
"wiremock",
]
[[package]]
name = "pulldown-cmark"
version = "0.13.3"
@ -3260,6 +3291,7 @@ checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-channel",
"futures-core",
"futures-util",
@ -3273,6 +3305,7 @@ dependencies = [
"hyper-util",
"js-sys",
"log",
"mime",
"native-tls",
"percent-encoding",
"pin-project-lite",
@ -3414,7 +3447,7 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
dependencies = [
"core-foundation",
"core-foundation 0.10.1",
"core-foundation-sys",
"jni",
"log",
@ -3468,6 +3501,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scc"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc"
dependencies = [
"sdd",
]
[[package]]
name = "schannel"
version = "0.1.29"
@ -3508,6 +3550,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sdd"
version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
[[package]]
name = "secrets"
version = "0.1.0"
@ -3526,7 +3574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
"bitflags 2.11.0",
"core-foundation",
"core-foundation 0.10.1",
"core-foundation-sys",
"libc",
"security-framework-sys",
@ -3663,23 +3711,24 @@ dependencies = [
[[package]]
name = "serial_test"
version = "3.5.0"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d"
checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f"
dependencies = [
"futures-executor",
"futures-util",
"log",
"once_cell",
"parking_lot",
"scc",
"serial_test_derive",
]
[[package]]
name = "serial_test_derive"
version = "3.5.0"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c"
checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9"
dependencies = [
"proc-macro2",
"quote",
@ -3967,6 +4016,27 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "system-configuration"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
dependencies = [
"bitflags 2.11.0",
"core-foundation 0.9.4",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "tar"
version = "0.4.46"
@ -4532,6 +4602,7 @@ dependencies = [
"manifest",
"minijinja",
"protocol",
"provider",
"pulldown-cmark",
"ratatui",
"secrets",
@ -5389,6 +5460,17 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-registry"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
dependencies = [
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-result"
version = "0.4.1"
@ -5722,12 +5804,11 @@ dependencies = [
"memory",
"minijinja",
"protocol",
"provider",
"reqwest",
"schemars",
"secrets",
"serde",
"serde_json",
"serial_test",
"session-metrics",
"session-store",
"tempfile",

View File

@ -13,6 +13,7 @@ members = [
"crates/plugin-pdk",
"crates/yoi",
"crates/protocol",
"crates/provider",
"crates/session-metrics",
"crates/session-analytics",
"crates/lint-common",
@ -38,6 +39,7 @@ default-members = [
"crates/plugin-pdk",
"crates/yoi",
"crates/protocol",
"crates/provider",
"crates/session-metrics",
"crates/session-analytics",
"crates/lint-common",
@ -70,6 +72,7 @@ worker-runtime = { path = "crates/worker-runtime" }
yoi-plugin-pdk = { path = "crates/plugin-pdk" }
yoi = { path = "crates/yoi" }
protocol = { path = "crates/protocol" }
provider = { path = "crates/provider" }
session-metrics = { path = "crates/session-metrics" }
session-analytics = { path = "crates/session-analytics" }
session-store = { path = "crates/session-store" }

View File

@ -80,6 +80,12 @@ cargo check --workspace --all-targets
cargo test --workspace
```
Optional Nix validation:
```sh
nix build .#yoi --no-link
```
E2E testing with real spawned processes is not yet designed. Keep changes scoped, preserve durable authority boundaries, and prefer clear type-safe structure over short-term compatibility layers.
License: MIT. See [`LICENSE`](LICENSE).

View File

@ -253,7 +253,7 @@ pub enum TicketRoleLaunchError {
#[error(transparent)]
LaunchConfig(#[from] TicketRoleLaunchConfigError),
#[error(
"Ticket role `{role}` profile selector `{selector}` is not resolvable before launch: {message}. Configure `[ticket.roles.{role}].profile` with an executable concrete profile selector such as `builtin:default` or a project/user profile"
"Ticket role `{role}` profile selector `{selector}` is not resolvable before launch: {message}. Configure `[roles.{role}].profile` with an executable concrete profile selector such as `builtin:default` or a project/user profile"
)]
ProfileResolution {
role: TicketRole,
@ -287,7 +287,7 @@ pub enum TicketRoleLaunchError {
RunAcceptanceTimeout,
}
/// Load Ticket policy from `.yoi/workspace.toml` and construct a launch plan.
/// Load `.yoi/ticket.config.toml` from the workspace and construct a launch plan.
pub fn plan_ticket_role_launch(
context: TicketRoleLaunchContext,
) -> Result<TicketRoleLaunchPlan, TicketRoleLaunchError> {
@ -700,14 +700,14 @@ mod tests {
fn write_config(workspace: &std::path::Path, content: &str) {
let dir = workspace.join(".yoi");
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("workspace.toml"), content).unwrap();
std::fs::write(dir.join("ticket.config.toml"), content).unwrap();
}
fn write_builtin_role_config(workspace: &std::path::Path, roles: &[TicketRole]) {
let mut config = String::from("[ticket]\n");
let mut config = String::new();
for role in roles {
config.push_str(&format!(
"\n[ticket.roles.{role}]\nprofile = \"builtin:default\"\n"
"\n[roles.{role}]\nprofile = \"builtin:default\"\n"
));
}
write_config(workspace, &config);
@ -883,7 +883,7 @@ mod tests {
err.to_string()
.contains("Ticket role `coder` is not launch-configured")
);
assert!(err.to_string().contains("[ticket.roles.coder]"));
assert!(err.to_string().contains("[roles.coder]"));
}
#[test]
@ -913,7 +913,7 @@ root = ".yoi/tickets"
write_config(
temp.path(),
r#"
[ticket.roles.intake]
[roles.intake]
profile = "inherit"
"#,
);
@ -931,7 +931,7 @@ profile = "inherit"
write_config(
temp.path(),
r#"
[ticket.roles.intake]
[roles.intake]
profile = "project:no-such-ticket-role-profile"
"#,
);
@ -944,7 +944,7 @@ profile = "project:no-such-ticket-role-profile"
"profile selector `project:no-such-ticket-role-profile` is not resolvable"
)
);
assert!(err.to_string().contains("[ticket.roles.intake].profile"));
assert!(err.to_string().contains("[roles.intake].profile"));
}
#[test]
@ -968,7 +968,7 @@ profile = "project:no-such-ticket-role-profile"
[ticket]
language = "Japanese"
[ticket.roles.intake]
[roles.intake]
profile = "builtin:default"
"#,
);
@ -1035,7 +1035,7 @@ profile = "builtin:default"
write_config(
temp.path(),
r#"
[ticket.roles.reviewer]
[roles.reviewer]
profile = "builtin:default"
launch_prompt = "$workspace/ticket/reviewer/launch"
workflow = "ticket-review-workflow"
@ -1226,7 +1226,7 @@ workflow = "ticket-review-workflow"
write_config(
temp.path(),
r#"
[ticket.roles.coder]
[roles.coder]
profile = "./coder.toml"
"#,
);
@ -1243,7 +1243,7 @@ profile = "./coder.toml"
write_config(
temp.path(),
r#"
[ticket.roles.coder]
[roles.coder]
profile = "inherit"
system_instruction = "$workspace/not-supported"
"#,

View File

@ -20,7 +20,7 @@ Does not own:
## Design notes
Macros reduce boilerplate, but they must not imply capability. A generated tool definition is still subject to host permissions, application scope, and runtime policy.
Macros reduce boilerplate, but they must not imply capability. A generated tool definition is still subject to manifest permissions, Worker scope, and runtime policy.
## See also

View File

@ -5,10 +5,6 @@ version = "0.2.1"
edition.workspace = true
license.workspace = true
[features]
default = []
codex = ["dep:base64", "dep:chrono"]
[dependencies]
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
@ -16,13 +12,11 @@ thiserror = { workspace = true }
tracing = { workspace = true }
async-trait = { workspace = true }
futures = { workspace = true }
tokio = { workspace = true, features = ["fs", "macros", "rt-multi-thread", "sync", "time"] }
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] }
tokio-util = "0.7"
reqwest = { version = "0.13", default-features = false, features = ["stream", "json", "native-tls", "http2"] }
eventsource-stream = "0.2"
zstd = "0.13"
base64 = { version = "0.22.1", optional = true }
chrono = { version = "0.4", default-features = false, features = ["serde", "clock"], optional = true }
llm-engine-macros = { workspace = true }
[dev-dependencies]

View File

@ -16,10 +16,10 @@ Owns:
Does not own:
- Host application names, sockets, process lifecycle, or scope delegation
- Product CLI shape
- Provider catalog and secret resolution
- Durable application state outside engine history
- Worker names, sockets, process lifecycle, or scope delegation (`worker`)
- product CLI shape (`yoi`)
- provider catalog and secret resolution (`provider`, `secrets`)
- durable Worker current state (`session-store` worker metadata)
## Design notes

View File

@ -213,9 +213,9 @@ pub struct Engine<C: LlmClient, S: EngineState = Mutable> {
/// stream events become visible.
lifecycle_trace_cbs: Vec<Arc<dyn Fn(usize, usize, &str, &Value) + Send + Sync>>,
/// Non-fatal warning callbacks. Invoked when the Engine wants to
/// surface an advisory message to the caller so it can be forwarded
/// to the user — distinct from `tracing::warn!`, which is for
/// developer-facing logs.
/// surface an advisory message to the upper layer (e.g. Worker) so it
/// can be forwarded to the user — distinct from `tracing::warn!`,
/// which is for developer-facing logs.
warning_cbs: Vec<Box<dyn Fn(&str) + Send + Sync>>,
/// Tool-result callbacks. Invoked once per completed tool call
/// after post-execution interceptors and the output byte-cap
@ -253,8 +253,8 @@ pub struct Engine<C: LlmClient, S: EngineState = Mutable> {
/// Plumbed into [`Request::cache_anchor`] at request build time.
cache_anchor: Option<usize>,
/// Conversation-scoped cache key, set by higher layers. Plumbed into
/// [`Request::cache_key`] at request build time. Callers should pass a
/// stable conversation identifier when the backend benefits from one.
/// [`Request::cache_key`] at request build time. Worker 側では
/// `SegmentId` を渡す。
cache_key: Option<String>,
/// State marker
_state: PhantomData<S>,
@ -486,8 +486,8 @@ impl<C: LlmClient, S: EngineState> Engine<C, S> {
///
/// Fired after `post_tool_call` interceptors and any `content`
/// truncation from `tool_output_limits`, so the callback observes
/// exactly what is persisted to history. Intended for callers that need
/// to forward tool results to clients.
/// exactly what is persisted to history. Intended for upper layers
/// (e.g. Worker) to forward tool results to clients.
pub fn on_tool_result(&mut self, callback: impl Fn(&ToolResult) + Send + Sync + 'static) {
self.tool_result_cbs.push(Box::new(callback));
}
@ -1121,10 +1121,10 @@ impl<C: LlmClient, S: EngineState> Engine<C, S> {
}
// Drain interceptor-side inputs that are meant to land in
// history (notifications, external events, system reminders).
// These are committed *before* the per-request clone so they
// participate in the LLM request below and get persisted by
// the caller that owns durable history.
// history (notifications, cross-Worker events, system
// reminders). These are committed *before* the per-request
// clone so they participate in the LLM request below and
// get persisted by the upper layer that owns history.json.
let pending = self.interceptor.pending_history_appends().await;
if !pending.is_empty() {
self.append_history_items(pending);
@ -1301,7 +1301,9 @@ impl<C: LlmClient, S: EngineState> Engine<C, S> {
self.turn_count += 1;
// Collect and commit assistant items. Routed through
// `append_history_items` so observers see each item as it lands.
// `append_history_items` so observers (e.g. the
// Worker-side per-item session-log committer) see each item
// as it lands.
let reasoning_items = self.thinking_block_collector.take_collected();
let text_blocks = self.text_block_collector.take_collected();
let tool_calls = self.tool_call_collector.take_collected();
@ -1601,7 +1603,8 @@ impl<C: LlmClient, S: EngineState> Engine<C, S> {
}
Ok(ToolExecutionResult::Completed(results)) => {
// Route per-result pushes through the callback path so
// observers see each tool result as it lands.
// observers (e.g. the Worker-side per-item session-log
// committer) see each tool result as it lands.
let items = results.into_iter().map(|result| {
Item::tool_result_item(
&result.tool_use_id,
@ -1704,9 +1707,9 @@ impl<C: LlmClient> Engine<C, Mutable> {
/// Install byte-size caps for tool execution `content`.
///
/// Passing `None` (the default) disables truncation. Callers translate
/// their own configuration into a concrete [`ToolOutputLimits`] and
/// install it here.
/// Passing `None` (the default) disables truncation. Higher layers
/// (e.g. Worker) translate manifest configuration into a concrete
/// [`ToolOutputLimits`] and install it here.
pub fn set_tool_output_limits(&mut self, limits: Option<ToolOutputLimits>) {
self.tool_output_limits = limits;
}

View File

@ -1,8 +1,9 @@
//! Interceptor - control flow delegation for the Engine execution loop
//!
//! Defines the [`Interceptor`] trait that callers implement to inject
//! orchestration decisions (approval, skip, pause, abort) into the Engine's
//! turn loop without the Engine knowing about host-application concepts.
//! Defines the [`Interceptor`] trait that upper layers (e.g. Worker) implement
//! to inject orchestration decisions (approval, skip, pause, abort)
//! into the Engine's turn loop without the Engine knowing about
//! higher-level concepts.
use std::sync::Arc;
@ -131,8 +132,8 @@ pub struct ToolResultInfo {
/// Intercepts the Engine execution loop at key decision points.
///
/// All methods have default implementations that let the Engine
/// proceed without intervention. Callers provide richer implementations for
/// approval flows, permission checks, etc.
/// proceed without intervention. Upper layers (e.g. Worker) provide
/// richer implementations for approval flows, permission checks, etc.
#[async_trait]
pub trait Interceptor: Send + Sync {
/// Called after receiving user input, before adding to history.
@ -148,7 +149,7 @@ pub trait Interceptor: Send + Sync {
///
/// Use this for inputs that arrive from outside the LLM and need
/// to be reflected in the on-disk history — notifications,
/// external events, system reminders. Do **not** use
/// cross-Worker events, system reminders. Do **not** use
/// [`Self::pre_llm_request`] for that purpose: it mutates a
/// per-request clone, so any committed assistant response that
/// reacts to the injection would have no visible trigger on the

View File

@ -45,7 +45,6 @@ pub(crate) mod callback;
pub mod event;
pub mod interceptor;
pub mod llm_client;
pub mod providers;
pub mod prune;
pub mod state;
pub mod timeline;

View File

@ -1,11 +1,15 @@
//! `Scheme` 実装と通信層が要求する認証要件、および動的認証プロバイダ。
//!
//! マニフェスト側の型(`ModelConfig` / `SchemeKind` / `AuthRef`)は
//! `crates/manifest` に置き、llm-engine はそれを知らずに済む。
//! `AuthRequirement` は scheme が宣言する「この scheme はどんな認証を
//! 期待するか」のランタイム記述で、設定ファイルや環境変数などから
//! [`super::transport::ResolvedAuth`] を組み立てる責務は呼び出し側にある。
//! 期待するか」のランタイム記述で、manifest 側の `AuthRef` との
//! 照合(`AuthRef → ResolvedAuth` 変換の適否)は `crates/provider`
//! で行う。
//!
//! リクエスト毎にトークンが変わり得る認証は [`AuthProvider`] trait を
//! 実装し、[`super::transport::ResolvedAuth::Custom`] 経由で transport に渡す。
//! Codex OAuth のようにリクエスト毎にトークンが変わり得る認証は
//! [`AuthProvider`] trait を `crates/provider` 側で実装し、
//! [`super::transport::ResolvedAuth::Custom`] 経由で transport に渡す。
use async_trait::async_trait;
use reqwest::header::{HeaderName, HeaderValue};
@ -23,15 +27,16 @@ pub enum AuthRequirement {
XApiKey,
/// クエリパラメータ `?<name>=<token>`Gemini 形式)
QueryParam { name: &'static str },
/// 複合ヘッダ(呼び出し側が [`AuthProvider`] で解決)
/// 複合ヘッダ(Codex OAuth 等、`crates/provider` 側で解決)
Custom,
}
/// リクエスト毎に認証ヘッダを動的に組み立てるプロバイダ。
///
/// access token が refresh で更新されたり、複数ヘッダを同時に注入する
/// 必要があるケースで使う。実体は呼び出し側に置き、llm-engine は
/// trait を知るだけ。
/// Codex OAuth のように access_token が refresh で更新されたり、
/// `ChatGPT-Account-Id` / `X-OpenAI-Fedramp` のような複数ヘッダを
/// 同時に注入する必要があるケースで使う。実体は `crates/provider`
/// 側に置き、llm-engine は trait を知るだけ。
///
/// 返したヘッダはそのまま `HeaderMap` に挿入される。`Authorization`
/// 含む scheme 既定の認証ヘッダは送出されないので、必要なら
@ -40,4 +45,13 @@ pub enum AuthRequirement {
pub trait AuthProvider: Send + Sync + std::fmt::Debug {
/// 1 リクエスト分の認証ヘッダを返す。refresh が必要なら内部で行う。
async fn headers(&self) -> Result<Vec<(HeaderName, HeaderValue)>, ClientError>;
/// ChatGPT Codex backend 向けの複合認証かどうか。
///
/// transport は provider crate の具象型を知らないため、この hook だけで
/// Codex CLI 互換の wire behaviorconversation header / request compression 等)
/// を切り替える。
fn is_codex_backend(&self) -> bool {
false
}
}

View File

@ -1,15 +1,15 @@
//! LLM response stream を開く前の transient error 向けリトライポリシー。
//!
//! `LlmClient::stream` の open error に対して `is_retryable` を見て
//! retry / backoff / cancellation をまとめて管理する。
//! Engine が `LlmClient::stream` の open error に対して `is_retryable` を見て
//! retry / backoff / TUI event / cancellation をまとめて管理する。
//! SSE 読み出し開始後の失敗は対象外。
use std::time::Duration;
/// 指数バックオフ + ジッター + 累積タイムアウトを表すポリシー。
///
/// `Default` は llm-engine 全体の固定値を返す。呼び出し側からの上書きが
/// 必要になったら拡張する
/// `Default` は llm-engine 全体の固定値を返す。manifest 経由の上書きが
/// 必要になったら拡張する(現状は不要 → `tickets/llm-engine-transient-retry.md`
#[derive(Debug, Clone)]
pub struct RetryPolicy {
/// 指数の基準値。`base * 2^attempt` を `cap` で頭打ちにした上限から

View File

@ -1,7 +1,7 @@
//! Anthropic scheme の wire-level 既定 capability。
//!
//! モデル ID 固有のテーブル(`claude-*` など)は client construction layer
//! の責務。ここでは未知モデルでも「この wire で
//! モデル ID 固有のテーブル(`claude-*` など)は高レベル構築層
//! (`provider::capability`)の責務。ここでは未知モデルでも「この wire で
//! 安全に送れる最小共通項」を返すだけに留める。
use crate::llm_client::capability::{

View File

@ -1,7 +1,7 @@
//! Gemini scheme の wire-level 既定 capability。
//!
//! モデル ID 固有のテーブル(`gemini-*` バージョン別の reasoning 有無)は
//! client construction layer の責務。ここでは wire の
//! 高レベル構築層(`provider::capability`)の責務。ここでは wire の
//! 保守的 default のみ。
use crate::llm_client::capability::{

View File

@ -45,8 +45,8 @@ pub trait Scheme: Clone + Send + Sync + 'static {
/// プロバイダもあるため、モデル ID を受け取る。
fn path(&self, model_id: &str) -> String;
/// この scheme が要求する認証形式。呼び出し側は client 構築時に
/// 設定された認証情報と照合する。
/// この scheme が要求する認証形式。`build_client` 時に
/// `manifest::AuthRef` と照合する。
fn required_auth(&self) -> AuthRequirement;
/// `Content-Type` 以外の追加ヘッダ。`anthropic-version` / `anthropic-beta` 等。
@ -78,7 +78,8 @@ pub trait Scheme: Clone + Send + Sync + 'static {
/// scheme 既定の capability。モデル ID に関係なく、この wire で
/// 安全に送れる最小共通項を返す。既知モデル ID の能力テーブルは
/// 高レベルの client 構築層の責務で、scheme はここには関与しない。
/// `provider::capability::lookup` 側(高レベル構築層)の責務で、
/// scheme はここには関与しない。
fn default_capability(&self) -> ModelCapability;
/// scheme 側でサポートしていない `RequestConfig` フィールドを

View File

@ -1,7 +1,7 @@
//! OpenAI Chat Completions scheme の wire-level 既定 capability。
//!
//! モデル ID 固有のテーブル(`gpt-5` 系など)は client construction layer
//! の責務。ここでは wire の保守的 default のみ。
//! モデル ID 固有のテーブル(`gpt-5` 系など)は高レベル構築層
//! (`provider::capability`)の責務。ここでは wire の保守的 default のみ。
use crate::llm_client::capability::{
CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport,

View File

@ -1,7 +1,7 @@
//! OpenAI Responses scheme の wire-level 既定 capability。
//!
//! モデル ID 固有の能力テーブルは高レベルの client 構築層の責務。
//! ここでは wire の保守的 default のみ。
//! モデル ID 固有のテーブル(`gpt-5` / `codex-` 系など)は高レベル構築層
//! (`provider::capability`)の責務。ここでは wire の保守的 default のみ。
use crate::llm_client::capability::{
CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport,

View File

@ -2,7 +2,7 @@
//!
//! `response.*` 名前空間の SSE を共通の [`Event`](crate::llm_client::event::Event)
//! に変換する。Responses の (output_index, content_index) 2 次元座標と
//! この crate の 1 次元 `BlockStart/Delta/Stop::index` のマッピングは
//! yoi 側 1 次元 `BlockStart/Delta/Stop::index` のマッピングは
//! [`OpenAIResponsesState`] が保持する。
use std::collections::{BTreeMap, HashMap};

View File

@ -2,7 +2,7 @@
//!
//! Chat Completions とは別物の item-based wire format。reasoning item と
//! function_call item が first-class で、SSE イベントも `response.*` 名前空間で
//! 流れる。
//! 流れる。ChatGPT OAuth 経路 (codex) は本 scheme 必須。
//!
//! - リクエスト JSON 生成: [`request`]
//! - SSE イベントパース → [`Event`](crate::llm_client::event::Event) 変換: [`events`]
@ -19,10 +19,10 @@ pub use scheme_impl::OpenAIResponsesState;
/// `store` / `include_encrypted_content` / `send_max_output_tokens` /
/// `send_sampling_params` は scheme 固定の wire 設定で、デフォルトは
/// 公式 OpenAI Responses API 向け (stateless + ZDR + `max_output_tokens`
/// / `temperature` / `top_p` 送出可)。受理パラメータが subset
/// 互換 backend では client 構築層で `send_max_output_tokens=false` /
/// `send_sampling_params=false` に上書きする。`ModelCapability` には
/// 入れない(モデル能力ではなく wire policy
/// / `temperature` / `top_p` 送出可)。ChatGPT backend (codex-oauth)
/// ように受理パラメータが subset の経路では provider 層で
/// `send_max_output_tokens=false` / `send_sampling_params=false` に
/// 上書きする。`ModelCapability` には入れない(モデル能力ではなく wire policy
#[derive(Debug, Clone)]
pub struct OpenAIResponsesScheme {
/// サーバ側に response を保存するか。ZDR/stateless 運用では `false`。
@ -31,12 +31,12 @@ pub struct OpenAIResponsesScheme {
/// `store=false` で reasoning を使うなら必須。
pub include_encrypted_content: bool,
/// `max_output_tokens` を body に載せるか。公式 OpenAI Responses API は
/// 受理するが、互換 backend によっては `Unsupported parameter` で
/// 400 を返すため、その経路では `false` にする。
/// 受理するが、ChatGPT backend (codex-oauth) は `Unsupported parameter`
/// 400 を返すため、その経路では `false` にする。
pub send_max_output_tokens: bool,
/// `temperature` / `top_p` を body に載せるか。公式 OpenAI Responses API
/// は受理するが、互換 backend によっては `Unsupported parameter` で
/// 400 を返すため、その経路では `false` にする。
/// は受理するが、ChatGPT backend (codex-oauth) は `Unsupported parameter`
/// 400 を返すため、その経路では `false` にする。
pub send_sampling_params: bool,
}

View File

@ -38,21 +38,21 @@ pub(crate) struct ResponsesRequest {
/// `["reasoning.encrypted_content"]` 等。
#[serde(skip_serializing_if = "Vec::is_empty")]
pub include: Vec<&'static str>,
/// 公式 OpenAI Responses API では受理されるが、互換 backend によっては
/// 400 で弾く。scheme の `send_max_output_tokens` が `false` のときは
/// `None` のまま送る (skip_serializing_if で除外)。
/// 公式 OpenAI Responses API では受理されるが、ChatGPT backend
/// (codex-oauth) は 400 で弾く。scheme の `send_max_output_tokens`
/// が `false` のときは `None` のまま送る (skip_serializing_if で除外)。
#[serde(skip_serializing_if = "Option::is_none")]
pub max_output_tokens: Option<u32>,
/// 公式 OpenAI Responses API では受理されるが、互換 backend によっては
/// `temperature` / `top_p` を 400 で弾く。scheme の
/// 公式 OpenAI Responses API では受理されるが、ChatGPT backend
/// (codex-oauth) は `temperature` / `top_p` を 400 で弾く。scheme の
/// `send_sampling_params` が `false` のときは `None` のまま送る。
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
/// 会話単位の安定キー。明示キーを必要とする backend では、
/// 呼び出し側が安定した conversation identifier を渡す。
/// `Request::cache_key` が `None` のときはキー自体を送らない。
/// 会話単位の安定キー。ChatGPT backend (codex-oauth) は明示キーが
/// 無いとプロンプトキャッシュがほぼ効かない。worker 側は `SegmentId`
/// を渡す。`Request::cache_key` が `None` のときはキー自体を送らない。
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_cache_key: Option<String>,
}
@ -74,9 +74,10 @@ pub(crate) struct ReasoningConfig {
#[serde(tag = "type", rename_all = "snake_case")]
pub(crate) enum InputItem {
/// 会話メッセージ。user / assistant / developer のいずれか。
/// `Role::System` items は `developer` として投影する。OpenAI
/// Responses 互換 backend の一部は `role: "system"` を拒否するため、
/// system 相当の挿入には `role: "developer"` を使う。
/// `Role::System` items は `developer` として投影するChatGPT
/// backend が `role: "system"` を拒否するため。Codex CLI も
/// system 相当の挿入には DeveloperInstructions = `role: "developer"`
/// を使う)。
Message {
role: &'static str,
content: Vec<InputContent>,
@ -402,9 +403,10 @@ mod tests {
#[test]
fn system_role_item_is_projected_as_developer() {
// Some compatible backends reject `role: "system"` in input[].
// Project in-conversation system notes as `role: "developer"` so
// both official and compatible backends can accept them.
// ChatGPT backend (codex-oauth) は input[] の `role: "system"` を
// "System messages are not allowed" で 400 拒否する。in-conversation
// な system note (notify / fs_view auto-read / compaction summary) は
// `role: "developer"` として投影し、両 backend で受理されるようにする。
let scheme = OpenAIResponsesScheme::new();
let req = Request::new()
.user("hi")
@ -521,9 +523,11 @@ mod tests {
fn reasoning_summary_field_is_always_serialized() {
// Responses API は reasoning item に `summary` を必須で要求する。
// summary が空でも wire 上に `summary: []` として残らないと、
// backend によっては missing required parameter として拒否される。
// reasoning effort 未指定のターンでは summary text が付かないことが
// あるため、空のままでも skip しないこと。
// ChatGPT backend (codex-oauth) が
// 400 invalid_request_error: Missing required parameter:
// 'input[N].summary'.
// で弾く。GPT-5 + reasoning effort 未指定のターンでは summary text
// が付かないことがあるため、空のままでも skip しないこと。
let scheme = OpenAIResponsesScheme::new();
let item = Item::reasoning("").with_encrypted_content("ENC");
let req = Request::new().user("hi").item(item);

View File

@ -20,8 +20,9 @@ impl Scheme for OpenAIResponsesScheme {
type State = OpenAIResponsesState;
fn default_base_url(&self) -> &'static str {
// `/v1` は base_url 側に寄せる。互換 backend を使う場合も、
// base URL を差し替えるだけで同じ `/responses` path を使える。
// `/v1` は base_url 側に寄せる。ChatGPT OAuth 経由のときは
// `https://chatgpt.com/backend-api/codex` を base にすれば同じ
// `/responses` path で両系統を吸収できるCodex CLI 準拠)。
"https://api.openai.com/v1"
}
@ -58,29 +59,27 @@ impl Scheme for OpenAIResponsesScheme {
fn validate_config(&self, config: &RequestConfig) -> Vec<ConfigWarning> {
let mut warnings = Vec::new();
// Some compatible backends reject `max_output_tokens` with HTTP 400.
// If the scheme was built with `send_max_output_tokens=false`, body
// projection is already disabled; only notify that the user's intent
// was dropped.
// ChatGPT backend (codex-oauth) は `max_output_tokens` を 400 で弾く。
// scheme 構築時に `send_max_output_tokens=false` で組まれていれば
// body 投影は止まっているので、ユーザの意図が落ちることだけを通知する。
if !self.send_max_output_tokens && config.max_tokens.is_some() {
warnings.push(ConfigWarning::unsupported(
"max_tokens",
"OpenAI Responses compatible backend",
"OpenAI Responses (ChatGPT backend)",
));
}
// Same for `temperature` / `top_p` on compatible backends that
// reject unsupported sampling parameters.
// 同上、`temperature` / `top_p` も ChatGPT backend では 400 で弾かれる。
if !self.send_sampling_params {
if config.temperature.is_some() {
warnings.push(ConfigWarning::unsupported(
"temperature",
"OpenAI Responses compatible backend",
"OpenAI Responses (ChatGPT backend)",
));
}
if config.top_p.is_some() {
warnings.push(ConfigWarning::unsupported(
"top_p",
"OpenAI Responses compatible backend",
"OpenAI Responses (ChatGPT backend)",
));
}
}

View File

@ -1,8 +1,8 @@
//! `HttpTransport<S: Scheme>`: すべての LLM wire scheme を共通の 1 本の
//! HTTP クライアントで扱う。
//!
//! scheme 固有の差分は [`Scheme`] trait 実装に委譲し、backend 固有の
//! HTTP policy は [`TransportPolicy`] で明示的に差し込む
//! 旧 `providers/{anthropic,openai,gemini,ollama}.rs` を置き換える。
//! scheme 固有の差分は [`Scheme`] trait 実装に委譲する
use std::pin::Pin;
use std::sync::Arc;
@ -28,11 +28,11 @@ use super::types::{Request, RequestConfig};
pub const DEFAULT_STREAM_OPEN_TIMEOUT: Duration = Duration::from_secs(20);
pub const DEFAULT_FIRST_STREAM_EVENT_TIMEOUT: Duration = Duration::from_secs(30);
/// 認証設定をリクエスト時に使える形へ解決したランタイム表現
/// `AuthRef` を解決したランタイム表現。`crates/provider` が構築する
///
/// - `None`: 認証ヘッダを送らないOllama 等の opt-out
/// - `ApiKey`: 静的な API key 文字列
/// - `Custom`: リクエスト毎に動的にヘッダを組み立てる
/// - `Custom`: リクエスト毎に動的にヘッダを組み立てるCodex OAuth 等)
#[derive(Debug, Clone)]
pub enum ResolvedAuth {
None,
@ -63,71 +63,6 @@ impl ResolvedAuth {
}
}
/// Request body encoding policy used by [`HttpTransport`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RequestBodyEncoding {
/// Send the request body as plain JSON.
Json,
/// Send the request body as zstd-compressed JSON with `Content-Encoding: zstd`.
ZstdJson,
}
impl Default for RequestBodyEncoding {
fn default() -> Self {
Self::Json
}
}
/// Conversation header policy used by [`HttpTransport`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConversationHeaderPolicy {
/// Do not derive any transport headers from [`Request::cache_key`].
None,
/// Send OpenAI-compatible conversation headers from [`Request::cache_key`].
OpenAiCompatible {
/// Send the legacy `session_id` header in addition to `session-id`.
include_legacy_session_id: bool,
/// Send `thread-id` with the same value as `session-id`.
include_thread_id: bool,
/// Send `x-client-request-id` with the same value as `session-id`.
include_client_request_id: bool,
},
}
impl Default for ConversationHeaderPolicy {
fn default() -> Self {
Self::None
}
}
/// Backend-specific HTTP transport policy.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct TransportPolicy {
/// How to serialize and encode the HTTP request body.
pub request_body_encoding: RequestBodyEncoding,
/// Optional backend-specific conversation headers derived from request metadata.
pub conversation_headers: ConversationHeaderPolicy,
}
impl TransportPolicy {
/// Plain JSON requests with no derived conversation headers.
pub fn standard() -> Self {
Self::default()
}
/// OpenAI-compatible backend profile that uses zstd JSON bodies and
/// conversation headers derived from [`Request::cache_key`].
pub fn openai_compatible_zstd() -> Self {
Self {
request_body_encoding: RequestBodyEncoding::ZstdJson,
conversation_headers: ConversationHeaderPolicy::OpenAiCompatible {
include_legacy_session_id: true,
include_thread_id: true,
include_client_request_id: true,
},
}
}
}
fn header_value_for_diagnostics(headers: &HeaderMap, name: &str) -> Option<String> {
headers
.get(name)
@ -185,7 +120,6 @@ pub struct HttpTransport<S: Scheme> {
base_url: String,
auth: ResolvedAuth,
capability: ModelCapability,
policy: TransportPolicy,
}
impl<S: Scheme> HttpTransport<S> {
@ -207,16 +141,9 @@ impl<S: Scheme> HttpTransport<S> {
base_url,
auth,
capability,
policy: TransportPolicy::default(),
}
}
/// Set a backend-specific HTTP transport policy.
pub fn with_transport_policy(mut self, policy: TransportPolicy) -> Self {
self.policy = policy;
self
}
/// カスタム HTTP クライアントを差し込む(テスト等)。
pub fn with_http_client(mut self, client: reqwest::Client) -> Self {
self.http_client = client;
@ -279,6 +206,13 @@ impl<S: Scheme> HttpTransport<S> {
Ok(headers)
}
fn is_codex_backend(&self) -> bool {
match &self.auth {
ResolvedAuth::Custom(provider) => provider.is_codex_backend(),
_ => false,
}
}
fn apply_stream_headers(
&self,
headers: &mut HeaderMap,
@ -286,26 +220,19 @@ impl<S: Scheme> HttpTransport<S> {
) -> Result<(), ClientError> {
headers.insert(ACCEPT, HeaderValue::from_static("text/event-stream"));
if let ConversationHeaderPolicy::OpenAiCompatible {
include_legacy_session_id,
include_thread_id,
include_client_request_id,
} = self.policy.conversation_headers
if self.is_codex_backend()
&& let Some(cache_key) = request.cache_key.as_deref()
{
let value = HeaderValue::from_str(cache_key).map_err(|e| {
ClientError::Config(format!("invalid conversation header value: {e}"))
ClientError::Config(format!("invalid Codex conversation header: {e}"))
})?;
// Codex CLI sends hyphenated session/thread headers to the
// ChatGPT Codex backend. Keep the legacy underscore header for
// existing traces/backends while exposing the current Codex shape.
headers.insert(HeaderName::from_static("session-id"), value.clone());
if include_thread_id {
headers.insert(HeaderName::from_static("thread-id"), value.clone());
}
if include_legacy_session_id {
headers.insert(HeaderName::from_static("session_id"), value.clone());
}
if include_client_request_id {
headers.insert(HeaderName::from_static("x-client-request-id"), value);
}
headers.insert(HeaderName::from_static("thread-id"), value.clone());
headers.insert(HeaderName::from_static("session_id"), value.clone());
headers.insert(HeaderName::from_static("x-client-request-id"), value);
}
Ok(())
@ -316,22 +243,19 @@ impl<S: Scheme> HttpTransport<S> {
body: &serde_json::Value,
headers: &mut HeaderMap,
) -> Result<RequestBody, ClientError> {
match self.policy.request_body_encoding {
RequestBodyEncoding::Json => Ok(RequestBody::Json(body.clone())),
RequestBodyEncoding::ZstdJson => {
let raw = serde_json::to_vec(body)?;
let raw_json_bytes = raw.len();
let compressed =
zstd::stream::encode_all(std::io::Cursor::new(raw), 3).map_err(|e| {
ClientError::Config(format!("failed to zstd-compress request: {e}"))
})?;
headers.insert(CONTENT_ENCODING, HeaderValue::from_static("zstd"));
Ok(RequestBody::CompressedJson {
bytes: compressed,
raw_json_bytes,
})
}
if !self.is_codex_backend() {
return Ok(RequestBody::Json(body.clone()));
}
let raw = serde_json::to_vec(body)?;
let raw_json_bytes = raw.len();
let compressed = zstd::stream::encode_all(std::io::Cursor::new(raw), 3)
.map_err(|e| ClientError::Config(format!("failed to zstd-compress request: {e}")))?;
headers.insert(CONTENT_ENCODING, HeaderValue::from_static("zstd"));
Ok(RequestBody::CompressedJson {
bytes: compressed,
raw_json_bytes,
})
}
}
@ -460,7 +384,6 @@ impl<S: Scheme + Clone> Clone for HttpTransport<S> {
base_url: self.base_url.clone(),
auth: self.auth.clone(),
capability: self.capability.clone(),
policy: self.policy.clone(),
}
}
}
@ -524,8 +447,7 @@ impl<S: Scheme + Clone + 'static> LlmClient for HttpTransport<S> {
"path": path,
"auth_kind": auth_kind(&self.auth),
"required_auth": format!("{:?}", self.scheme.required_auth()),
"request_body_encoding": format!("{:?}", self.policy.request_body_encoding),
"conversation_headers": format!("{:?}", self.policy.conversation_headers),
"codex_backend": self.is_codex_backend(),
"cache_key_present": request.cache_key.is_some(),
"stream_open_timeout_ms": DEFAULT_STREAM_OPEN_TIMEOUT.as_millis() as u64,
}),
@ -756,7 +678,9 @@ mod tests {
use serde_json::json;
#[derive(Debug)]
struct TestAuthProvider;
struct TestAuthProvider {
codex: bool,
}
#[async_trait]
impl AuthProvider for TestAuthProvider {
@ -772,6 +696,10 @@ mod tests {
),
])
}
fn is_codex_backend(&self) -> bool {
self.codex
}
}
#[derive(Clone)]
@ -900,9 +828,10 @@ mod tests {
}
#[tokio::test]
async fn transport_policy_adds_conversation_headers_and_zstd_body() {
let transport = transport(ResolvedAuth::Custom(Arc::new(TestAuthProvider)))
.with_transport_policy(TransportPolicy::openai_compatible_zstd());
async fn codex_backend_adds_conversation_headers_and_zstd_body() {
let transport = transport(ResolvedAuth::Custom(Arc::new(TestAuthProvider {
codex: true,
})));
let request = Request::new().user("hello").cache_key("segment-123");
let mut headers = transport.build_headers().await.unwrap();
transport
@ -941,7 +870,7 @@ mod tests {
raw_json_bytes,
} = encoded
else {
panic!("transport policy should zstd-compress request body");
panic!("Codex backend request body must be zstd-compressed");
};
assert!(raw_json_bytes > 0);
let decoded = zstd::stream::decode_all(std::io::Cursor::new(compressed)).unwrap();
@ -950,7 +879,7 @@ mod tests {
}
#[tokio::test]
async fn standard_policy_does_not_get_conversation_headers_or_compression() {
async fn non_codex_request_does_not_get_codex_only_headers_or_compression() {
let transport = transport(ResolvedAuth::ApiKey("api-key".to_string()));
let request = Request::new().user("hello").cache_key("segment-123");
let mut headers = transport.build_headers().await.unwrap();
@ -972,7 +901,7 @@ mod tests {
assert!(headers.get(CONTENT_ENCODING).is_none());
let RequestBody::Json(decoded) = encoded else {
panic!("standard transport policy should keep request body as JSON");
panic!("non-Codex request body must remain normal JSON");
};
assert_eq!(decoded["prompt_cache_key"], "segment-123");
}

View File

@ -1,6 +1,6 @@
//! LLM Client Common Types
//!
//! Core conversation types for LLM interaction.
//! Core conversation types for yoi's LLM interaction model.
//! The core abstraction is `Item` which represents different types of conversation elements:
//! - Message items (user/assistant messages with content parts)
//! - ToolCall items (tool invocations)
@ -521,8 +521,9 @@ pub struct Request {
/// Providers without prompt caching ignore the field.
pub cache_anchor: Option<usize>,
/// 会話単位の安定キー。`prompt_cache_key` として送られる
/// (OpenAI Responses)。明示キーを必要とする backend では、
/// 呼び出し側が安定した conversation identifier を渡す。
/// (OpenAI Responses)。ChatGPT backend (codex-oauth) は明示キーが
/// 無いと org/project ハッシュ衝突でプロンプトキャッシュが
/// ほぼヒットしないため、worker 側で `SegmentId` を渡す運用を想定。
/// `cache_anchor` と違い名前空間キーであり、`prefix anchor` とは
/// 別の概念。`cache_anchor` を読まない provider と同じく、
/// `prompt_cache_key` を持たない provider は無視する。

View File

@ -1,4 +0,0 @@
//! Optional built-in provider/backend helpers.
#[cfg(feature = "codex")]
pub mod codex;

View File

@ -8,8 +8,8 @@
//!
//! Prune は **コンテキスト射影** であり、history の変換ではない。
//! この crate が提供するのは pure な候補抽出 [`prunable_indices`] のみで、
//! 射影の適用は LLM に送る一時コンテキストに対してだけ行う。
//! Engine の永続履歴は決して変更されない。
//! 射影の適用は上位層(`worker::prune_hook` 等)が LLM に送る一時コンテキスト
//! に対してだけ行う。Engine の永続履歴は決して変更されない。
//!
//! 保護境界は末尾 token budget で決めるが、この crate は usage 履歴を
//! 所有しない。prefix ごとの token 推定値と savings 推定は上位層から
@ -75,7 +75,7 @@ pub enum PruneDecision {
}
/// Optional observer invoked after each prune evaluation, regardless of
/// branch. Callers can install this to publish metrics or traces.
/// branch. Worker 等の上位層が install して metrics を発行する。
pub type PruneObserver = Box<dyn Fn(&PruneEvaluation) + Send + Sync>;
/// Configuration for the Prune algorithm.

View File

@ -130,13 +130,13 @@ mod tests {
let mut timeline = Timeline::new();
timeline.on_tool_use_block(collector.clone());
timeline.dispatch(&Event::tool_use_start(0, "tool_empty", "ListItems"));
timeline.dispatch(&Event::tool_use_start(0, "tool_empty", "ListWorkers"));
timeline.dispatch(&Event::tool_use_stop(0));
let calls = collector.take_collected();
assert_eq!(calls.len(), 1);
assert_eq!(calls[0].id, "tool_empty");
assert_eq!(calls[0].name, "ListItems");
assert_eq!(calls[0].name, "ListWorkers");
assert!(calls[0].input.is_object());
assert_eq!(
calls[0].input,

View File

@ -75,9 +75,10 @@ impl ToolServerHandle {
/// Execute all pending factories and register the resulting tools.
///
/// Called implicitly by `Engine::lock()` before the first turn.
/// Exposed as `pub` so callers can force-materialise tools earlier —
/// for example when building a system-prompt template context that needs
/// the list of registered tool names. Redundant calls are no-ops.
/// Exposed as `pub` so higher layers (e.g. Worker) can force-materialise
/// tools earlier — for example when building a system-prompt template
/// context that needs the list of registered tool names. Redundant
/// calls are no-ops.
///
/// # Panics
///

View File

@ -2,8 +2,9 @@
//!
//! 1 リクエストの送信時点での「ある history prefix 長で計測した占有量」を
//! 1 件分にまとめたもの。`UsageEvent` (provider stream イベント) を
//! 受けて呼び出し側が組み立て、永続化層に流したり、token accounting
//! (`token_counter`) で履歴として参照したりする。
//! 受けて呼び出し側 (typically Worker) が組み立て、永続化層
//! (session-store) に流したり、token accounting (`token_counter`) で
//! 履歴として参照したりする。
/// LLM リクエスト送信時点での占有量スナップショット。
#[derive(Debug, Clone, PartialEq, Eq)]

View File

@ -18,5 +18,4 @@ toml = { workspace = true }
tracing = { workspace = true }
[dev-dependencies]
serial_test = "3.4.0"
tempfile = { workspace = true }

View File

@ -723,7 +723,7 @@ fn resolve_auth_file(auth: &mut Option<AuthRef>, base: &Path) {
/// モデル宣言に含まれる `auth.file` が絶対パスであることを検証する。
/// ref / scheme / model_id 等の論理的な有効性ref があるか、inline が
/// 揃っているか)の検証は [`crate::model_catalog`] で行う。
/// 揃っているか)の検証はカタログを知る `crates/provider` 側で行う。
fn validate_model_paths(model: &ModelManifest, field: &'static str) -> Result<(), ResolveError> {
if let Some(AuthRef::ApiKey { file: Some(p), .. }) = &model.auth {
ensure_absolute(field, p)?;

View File

@ -1,7 +1,6 @@
mod config;
pub mod defaults;
mod model;
pub mod model_catalog;
pub mod paths;
pub mod plugin;
mod profile;

View File

@ -5,7 +5,7 @@
//! 直書き)の両方を受け入れるため、すべてのフィールドを `Option` として
//! 持つ 1 つの型 [`ModelManifest`] に統合している。実解決ref をプロバイダ
//! カタログ / モデルカタログから引いて `scheme` や `model_id` を埋める)
//! は [`crate::model_catalog`] の責務で、本モジュールはデータ表現のみを提供する。
//! は `crates/provider` の責務で、本モジュールはデータ表現のみを提供する。
//!
//! 同じ型を partialカスケード層と完成形最終マニフェストの両方で
//! 使うことで、merge と最終変換の重複を避ける。
@ -24,7 +24,7 @@ pub use llm_engine::llm_client::capability::{ModelCapability, ReasoningControl,
/// - ref + 一部 override: ref で基底を引き、`auth` 等だけ書き換え
/// - 完全 inline: `ref` を省略して `scheme` / `model_id` / `auth` を直書き
///
/// どの形が有効かの判定は [`crate::model_catalog::resolve_model_manifest`] が担う。
/// どの形が有効かの判定は `provider::resolve_model_manifest` が担う。
/// 本クレートは「どこから取るか」を表現するだけで、未設定かどうかを
/// 理由にした hard error は出さない。
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
@ -98,7 +98,7 @@ pub enum SchemeKind {
/// 認証の参照。
///
/// 実際のトークン値の解決local secret store / file 読取、OAuth refresh 等)は
/// worker-side factory で行う。ここはあくまで「どこから取るか」の宣言。
/// `crates/provider` で行う。ここはあくまで「どこから取るか」の宣言。
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum AuthRef {
@ -113,7 +113,7 @@ pub enum AuthRef {
#[serde(default)]
file: Option<PathBuf>,
},
/// ChatGPT OAuth`~/.codex/auth.json`)。
/// ChatGPT OAuth`~/.codex/auth.json`)。実装は `llm-auth-codex-oauth` チケット
#[serde(rename = "codex_oauth")]
CodexOAuth,
/// Typed local secret-store reference. The profile resolver preserves this

View File

@ -970,12 +970,6 @@ fn builtin_default_profile_artifact() -> serde_json::Value {
"model": { "ref": "codex-oauth/gpt-5.5" },
"session": { "record_event_trace": true },
"engine": { "reasoning": "high" },
"compaction": {
"kind": "tokens",
"threshold": 240000,
"request_threshold": 270000,
"worker_context_max_tokens": 100000
},
"feature": {
"task": { "enabled": true },
"memory": { "enabled": true },
@ -1447,22 +1441,6 @@ mod tests {
assert!(companion.delegation_scope.allow.is_empty());
assert_eq!(companion.model.ref_.as_deref(), Some("codex-oauth/gpt-5.5"));
assert!(companion.web.is_some());
assert_eq!(
companion.compaction.as_ref().unwrap().threshold,
Some(240000)
);
assert_eq!(
companion.compaction.as_ref().unwrap().request_threshold,
Some(270000)
);
assert_eq!(
companion
.compaction
.as_ref()
.unwrap()
.worker_context_max_tokens,
100000
);
let intake = resolve("intake");
assert!(intake.feature.task.enabled);
@ -1472,7 +1450,6 @@ mod tests {
assert!(intake.delegation_scope.allow.is_empty());
assert_eq!(intake.model.ref_.as_deref(), Some("codex-oauth/gpt-5.5"));
assert!(intake.web.is_some());
assert!(intake.compaction.is_some());
assert!(!intake.feature.ticket_orchestration.enabled);
let orchestrator = resolve("orchestrator");
@ -1487,7 +1464,6 @@ mod tests {
Some("codex-oauth/gpt-5.5")
);
assert!(orchestrator.web.is_some());
assert!(orchestrator.compaction.is_some());
let coder = resolve("coder");
assert!(coder.feature.task.enabled);
@ -1496,7 +1472,6 @@ mod tests {
assert!(coder.delegation_scope.allow.is_empty());
assert_eq!(coder.model.ref_.as_deref(), Some("codex-oauth/gpt-5.5"));
assert!(coder.web.is_some());
assert!(coder.compaction.is_some());
let reviewer = resolve("reviewer");
assert!(reviewer.feature.task.enabled);
@ -1506,7 +1481,6 @@ mod tests {
assert!(reviewer.delegation_scope.allow.is_empty());
assert_eq!(reviewer.model.ref_.as_deref(), Some("codex-oauth/gpt-5.5"));
assert!(reviewer.web.is_some());
assert!(reviewer.compaction.is_some());
}
#[test]

View File

@ -0,0 +1,25 @@
[package]
name = "provider"
version = "0.1.0"
edition.workspace = true
license.workspace = true
[dependencies]
async-trait = { workspace = true }
base64 = "0.22.1"
chrono = { version = "0.4", default-features = false, features = ["serde", "clock"] }
llm-engine = { workspace = true }
manifest = { workspace = true }
secrets = { workspace = true }
reqwest = { version = "0.13", features = ["json", "native-tls"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["sync", "fs", "rt", "time"] }
toml = { workspace = true }
tracing = { workspace = true }
[dev-dependencies]
serial_test = "3.4.0"
tempfile = { workspace = true }
wiremock = "0.6.5"

33
crates/provider/README.md Normal file
View File

@ -0,0 +1,33 @@
# provider
## Role
`provider` resolves model/provider configuration and constructs provider-specific LLM clients for `llm-engine`.
## Boundaries
Owns:
- builtin and user provider/model catalog resolution
- model reference expansion into concrete model config
- auth reference resolution through supported mechanisms
- provider/scheme capability and context-window metadata
- provider-specific client construction
Does not own:
- Engine turn lifecycle (`llm-engine`)
- secret storage internals (`secrets`)
- Worker lifecycle (`worker`)
- product CLI parsing (`yoi`)
## Design notes
Provider API facts drift. Keep wire-format, auth, catalog, and capability differences here so Engine semantics remain stable.
Codex OAuth is a separate integration from normal provider secret refs because its local file shape and lifecycle differ.
## See also
- [`../../docs/design/provider-model-boundary.md`](../../docs/design/provider-model-boundary.md)
- [`../../docs/design/profiles-manifests-prompts.md`](../../docs/design/profiles-manifests-prompts.md)

View File

@ -4,19 +4,19 @@
//! - builtin モデル: `resources/models/builtin.toml`
//! - user override: `<config_dir>/{providers,models}.toml`
//!
//! `<config_dir>` の解決は [`crate::paths::config_dir`] を参照。
//! `<config_dir>` の解決は [`manifest::paths::config_dir`] を参照。
//! どちらの override も「あれば builtin を置換、無ければ builtin」と
//! いう一方向の差し替えマージしない。providers / models は独立に
//! 読み、片方だけ user override も可。
//!
//! [`resolve_model_manifest`] が [`crate::ModelManifest`]ref / inline
//! [`resolve_model_manifest`] が `manifest::ModelManifest`ref / inline
//! 両形)を最終的な [`ModelConfig`] に解決する単一の入口で、wire 層
//! に渡す前のバリデーションもここで行う。
use std::path::{Path, PathBuf};
use crate::{AuthRef, ModelManifest, SchemeKind};
use llm_engine::llm_client::capability::ModelCapability;
use manifest::{AuthRef, ModelManifest, SchemeKind};
use serde::{Deserialize, Serialize};
const BUILTIN_PROVIDERS: &str = include_str!("../../../resources/providers/builtin.toml");
@ -178,7 +178,7 @@ fn auth_hint_to_ref(hint: &AuthHint) -> AuthRef {
/// ユーザーが書いた設定が silent に無視されて builtin に戻る挙動は
/// 気付きにくいため)。
pub fn load_providers() -> Result<Vec<ProviderEntry>, CatalogError> {
if let Some(path) = crate::paths::user_catalog_override("providers.toml")
if let Some(path) = manifest::paths::user_catalog_override("providers.toml")
&& path.is_file()
{
return load_providers_from(&path);
@ -211,7 +211,7 @@ pub fn load_providers_from(path: &Path) -> Result<Vec<ProviderEntry>, CatalogErr
/// builtin + user override を解決してモデルカタログを返す。
pub fn load_models() -> Result<Vec<ModelEntry>, CatalogError> {
if let Some(path) = crate::paths::user_catalog_override("models.toml")
if let Some(path) = manifest::paths::user_catalog_override("models.toml")
&& path.is_file()
{
return load_models_from(&path);

View File

@ -1,7 +1,7 @@
//! `~/.codex/auth.json` の読み書き。
//!
//! Codex CLI と schema を共有するが、知らないフィールドを失わないよう
//! ファイル全体を `serde_json::Value` で保持し、必要箇所
//! Codex CLI と schema を共有するが、yoi は知らないフィールドを
//! 失わないようファイル全体を `serde_json::Value` で保持し、必要箇所
//! のみアクセスする。書込は `mode 0o600` を再設定Codex CLI 同様)、
//! ファイルロックは取らないmanager 側で guarded reload
@ -106,7 +106,7 @@ pub async fn load(path: &Path) -> Result<AuthSnapshot, CodexAuthError> {
/// 既存ファイルを再読込し、`tokens.{id_token,access_token,refresh_token}` と
/// `last_refresh` を更新して書き戻す。Codex CLI の `persist_tokens` 相当。
///
/// 並行する Codex CLI / 別プロセスが先に refresh していた場合の
/// 並行する Codex CLI / 別 yoi プロセスが先に refresh していた場合の
/// fields を保護するため、書込前に再 load して merge する。
pub async fn persist_refreshed(
path: &Path,

View File

@ -6,7 +6,7 @@
use std::path::PathBuf;
use crate::llm_client::ClientError;
use llm_engine::llm_client::ClientError;
use thiserror::Error;
#[derive(Debug, Error)]

View File

@ -4,11 +4,11 @@
//!
//! 設計:
//!
//! - HTTP transport は [`AuthProvider`] trait だけを見て、実体である
//! [`CodexAuthProvider`] はこの optional module に置く
//! - llm-engine は [`AuthProvider`] trait しか知らず、実体である
//! [`CodexAuthProvider`] はこのクレートに置くfeedback_llm_engine_scope
//! - access_token JWT の `exp` を読み、`now` 以下で proactive refresh
//! Codex CLI と同じバッファなし)
//! - 並行する Codex CLI / 別プロセスの refresh と取り違えないよう、
//! - 並行する Codex CLI / 別 yoi の refresh と取り違えないよう、
//! refresh 直前に再 load して account_id 一致を確認guarded reload
//! - ファイルロックは取らず、書込前に再 load + diff merge で吸収
//! - Codex の Keyring storage は対象外。auth.json 不在ならエラーで案内
@ -21,9 +21,9 @@ mod refresh;
use std::path::PathBuf;
use std::sync::Arc;
use crate::llm_client::{ClientError, auth::AuthProvider};
use async_trait::async_trait;
use chrono::{Duration, Utc};
use llm_engine::llm_client::{ClientError, auth::AuthProvider};
use reqwest::header::{HeaderName, HeaderValue};
use tokio::sync::Mutex;
@ -152,7 +152,7 @@ impl CodexAuthProvider {
.map_err(|e| CodexAuthError::InvalidHeader(format!("ChatGPT-Account-Id: {e}")))?;
out.push((HeaderName::from_static("chatgpt-account-id"), acc_val));
// Cloudflare WAF は互換 backend アクセス元を `originator` /
// Cloudflare WAF は ChatGPT backend アクセス元を `originator` /
// `User-Agent` で識別する。Codex CLI が送る固定値を流用しないと
// HTML challenge (403) を返されて SSE に到達できない。
out.push((
@ -188,6 +188,10 @@ impl AuthProvider for CodexAuthProvider {
.map_err(CodexAuthError::to_client_error)?;
Self::build_headers(&snap).map_err(CodexAuthError::to_client_error)
}
fn is_codex_backend(&self) -> bool {
true
}
}
/// `access_token` の JWT `exp` を見て、期限切れなら true。

View File

@ -80,7 +80,7 @@ async fn response_with_timeout(
.await
.map_err(|_| {
CodexAuthError::RefreshTransient(format!(
"oauth_token_refresh timed out after {}s",
"codex_oauth_refresh timed out after {}s",
timeout.as_secs()
))
})?

View File

@ -1,4 +1,5 @@
//! [`ModelManifest`] を [`Box<dyn LlmClient>`] に落とす worker-side factory。
//! Worker マニフェストの [`ModelManifest`] を [`Box<dyn LlmClient>`]
//! に落とすファクトリ。
//!
//! 段階:
//! 1. `ModelManifest` を [`catalog::resolve_model_manifest`] で
@ -10,7 +11,11 @@
//! `catalog::resolve_model_manifest` が [`ModelConfig`] に詰め込む)
//!
//! llm-engine は低レベル基盤に留める方針なので、高レベル側で必要に
//! なる認証ストア解決と secret store 解決は worker 側で行う。
//! なる認証ストア解決Codex OAuth の `~/.codex/auth.json` 読取等)は
//! このクレートに追加する。
pub mod catalog;
pub mod codex_oauth;
use std::sync::Arc;
@ -21,14 +26,13 @@ use llm_engine::llm_client::{
Scheme, anthropic::AnthropicScheme, gemini::GeminiScheme, openai_chat::OpenAIScheme,
openai_responses::OpenAIResponsesScheme,
},
transport::{HttpTransport, ResolvedAuth, TransportPolicy},
transport::{HttpTransport, ResolvedAuth},
};
use llm_engine::providers::codex::CodexAuthProvider;
use manifest::{AuthRef, ModelManifest, SchemeKind, model_catalog as catalog};
use manifest::{AuthRef, ModelManifest, SchemeKind};
use secrets::{SecretStore, SecretValue};
pub use manifest::model_catalog::{ModelConfig, ResolveError as CatalogResolveError};
pub use catalog::{ModelConfig, ResolveError as CatalogResolveError};
/// プロバイダ構築時のエラー。
#[derive(Debug, thiserror::Error)]
@ -107,7 +111,7 @@ fn resolve_auth_with_resolver(
Err(ProviderError::ApiKeyMissing { scheme })
}
AuthRef::CodexOAuth => {
let provider = CodexAuthProvider::from_default_home()
let provider = codex_oauth::CodexAuthProvider::from_default_home()
.map_err(|e| ProviderError::Config(e.to_string()))?;
Ok(ResolvedAuth::Custom(Arc::new(provider)))
}
@ -140,7 +144,6 @@ fn build_transport<S: Scheme>(
scheme: S,
config: &ModelConfig,
resolved: ResolvedAuth,
policy: TransportPolicy,
) -> Result<Box<dyn LlmClient>, ProviderError> {
if !resolved.matches(scheme.required_auth()) {
return Err(ProviderError::AuthMismatch {
@ -157,39 +160,21 @@ fn build_transport<S: Scheme>(
.clone()
.unwrap_or_else(|| scheme.default_capability());
let base_url = effective_base_url(&scheme, config);
Ok(Box::new(
HttpTransport::new(
scheme,
config.model_id.clone(),
base_url,
resolved,
capability,
)
.with_transport_policy(policy),
))
Ok(Box::new(HttpTransport::new(
scheme,
config.model_id.clone(),
base_url,
resolved,
capability,
)))
}
fn build_from_config(config: &ModelConfig) -> Result<Box<dyn LlmClient>, ProviderError> {
let resolved = resolve_auth(config.scheme, &config.auth)?;
match config.scheme {
SchemeKind::Anthropic => build_transport(
AnthropicScheme::new(),
config,
resolved,
TransportPolicy::standard(),
),
SchemeKind::OpenaiChat => build_transport(
OpenAIScheme::new(),
config,
resolved,
TransportPolicy::standard(),
),
SchemeKind::Gemini => build_transport(
GeminiScheme::new(),
config,
resolved,
TransportPolicy::standard(),
),
SchemeKind::Anthropic => build_transport(AnthropicScheme::new(), config, resolved),
SchemeKind::OpenaiChat => build_transport(OpenAIScheme::new(), config, resolved),
SchemeKind::Gemini => build_transport(GeminiScheme::new(), config, resolved),
SchemeKind::OpenaiResponses => {
// ChatGPT backend (codex-oauth) は `max_output_tokens` /
// `temperature` / `top_p` を 400 で弾くため、その経路では
@ -198,12 +183,7 @@ fn build_from_config(config: &ModelConfig) -> Result<Box<dyn LlmClient>, Provide
let scheme = OpenAIResponsesScheme::new()
.with_send_max_output_tokens(send_to_official)
.with_send_sampling_params(send_to_official);
let policy = if matches!(config.auth, AuthRef::CodexOAuth) {
TransportPolicy::openai_compatible_zstd()
} else {
TransportPolicy::standard()
};
build_transport(scheme, config, resolved, policy)
build_transport(scheme, config, resolved)
}
}
}
@ -228,34 +208,10 @@ mod tests {
use super::*;
use serial_test::serial;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
struct TestSecrets(std::collections::BTreeMap<String, String>);
struct ConfigDirGuard {
prev: Option<String>,
}
impl ConfigDirGuard {
fn new(path: &Path) -> Self {
let prev = std::env::var("YOI_CONFIG_DIR").ok();
// SAFETY: tests using this guard are marked `#[serial]`.
unsafe { std::env::set_var("YOI_CONFIG_DIR", path) };
Self { prev }
}
}
impl Drop for ConfigDirGuard {
fn drop(&mut self) {
unsafe {
match &self.prev {
Some(v) => std::env::set_var("YOI_CONFIG_DIR", v),
None => std::env::remove_var("YOI_CONFIG_DIR"),
}
}
}
}
impl SecretResolver for TestSecrets {
fn get_secret(&self, id: &str) -> Result<SecretValue, secrets::Error> {
self.0
@ -355,11 +311,7 @@ mod tests {
}
#[test]
#[serial]
fn ref_manifest_builds_client() {
let dir = tempfile::tempdir().unwrap();
let _guard = ConfigDirGuard::new(dir.path());
// Ollama は AuthRef::None で構築できる end-to-end path。
let manifest = ModelManifest {
ref_: Some("ollama-local/llama3.1".into()),
@ -374,11 +326,7 @@ mod tests {
}
#[test]
#[serial]
fn inline_manifest_builds_client() {
let dir = tempfile::tempdir().unwrap();
let _guard = ConfigDirGuard::new(dir.path());
// Form C: 完全直書き。Ollama 相当を AuthRef::None で構築。
let manifest = ModelManifest {
scheme: Some(SchemeKind::Anthropic),

View File

@ -1,12 +1,9 @@
//! Workspace-local Ticket orchestration configuration.
//!
//! Durable Ticket policy lives under the `[ticket]` table in tracked
//! `.yoi/workspace.toml` workspace settings. The legacy
//! `.yoi/ticket.config.toml` file is only a read-only migration fallback when
//! workspace settings do not contain any Ticket policy. The config intentionally
//! stores lightweight string references for Profile selectors, launch prompts,
//! and workflows so this crate remains independent from `worker` and `manifest`
//! runtime resolution.
//! The config file lives at `.yoi/ticket.config.toml` under a workspace root.
//! It intentionally stores lightweight string references for Profile selectors,
//! launch prompts, and workflows so this crate remains independent from `worker`
//! and `manifest` runtime resolution.
use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
@ -16,8 +13,6 @@ use std::path::{Component, Path, PathBuf};
use serde::{Deserialize, Serialize};
use thiserror::Error;
pub const WORKSPACE_SETTINGS_RELATIVE_PATH: &str = ".yoi/workspace.toml";
/// Legacy Ticket config path. This is kept as a narrow read-only migration fallback only.
pub const TICKET_CONFIG_RELATIVE_PATH: &str = ".yoi/ticket.config.toml";
/// Workspace-relative default root for the built-in local Ticket backend.
pub const DEFAULT_TICKET_BACKEND_RELATIVE_PATH: &str = ".yoi/tickets";
@ -25,17 +20,13 @@ const DEFAULT_ORCHESTRATION_BRANCH: &str = "orchestration";
const DEFAULT_ORCHESTRATION_WORKTREE_DIR: &str = ".worktree";
const DEFAULT_ORCHESTRATION_WORKTREE_NAME: &str = "orchestration";
/// Return the explicit Workspace settings Ticket policy scaffold written by `yoi ticket init`.
/// Return the explicit workspace Ticket config scaffold written by `yoi ticket init`.
///
/// The scaffold is a valid `.yoi/workspace.toml` fragment rooted at `[ticket]`.
/// It intentionally configures every fixed Ticket role with a concrete profile
/// so strict role launch planning can validate the config without runtime
/// The scaffold intentionally configures every fixed Ticket role with a concrete
/// profile so strict role launch planning can validate the config without runtime
/// fallback.
pub fn ticket_config_scaffold() -> String {
let mut out = String::from(
"[ticket]\n# Optional durable Ticket record language. When unset, generated Ticket text keeps current defaults.\n# language = \"Japanese\"\n",
);
out.push_str("\n[ticket.backend]\n");
let mut out = String::from("[backend]\n");
out.push_str(&format!(
"provider = \"{}\"\n",
TicketBackendProvider::BuiltinYoiLocal.as_str()
@ -45,11 +36,14 @@ pub fn ticket_config_scaffold() -> String {
DEFAULT_TICKET_BACKEND_RELATIVE_PATH
));
out.push_str(
"\n# Optional Panel Orchestrator worktree settings. When unset, Panel uses branch `orchestration` at `.worktree/orchestration`.\n# [ticket.orchestration]\n# branch = \"orchestration\"\n# worktree_dir = \".worktree\"\n# worktree_name = \"orchestration\"\n",
"\n# Optional durable Ticket record language. When unset, generated Ticket text keeps current defaults.\n# [ticket]\n# language = \"Japanese\"\n",
);
out.push_str(
"\n# Optional Panel Orchestrator worktree settings. When unset, Panel uses branch `orchestration` at `.worktree/orchestration`.\n# [orchestration]\n# branch = \"orchestration\"\n# worktree_dir = \".worktree\"\n# worktree_name = \"orchestration\"\n",
);
for role in TicketRole::ALL {
out.push_str(&format!(
"\n[ticket.roles.{role}]\nprofile = \"{}\"\nworkflow = \"{}\"\n",
"\n[roles.{role}]\nprofile = \"{}\"\nworkflow = \"{}\"\n",
role.default_profile(),
role.default_workflow()
));
@ -228,81 +222,15 @@ impl TicketConfig {
pub fn load_workspace(workspace_root: impl AsRef<Path>) -> Result<Self, TicketConfigError> {
let workspace_root = workspace_root.as_ref();
let workspace_settings_path = workspace_root.join(WORKSPACE_SETTINGS_RELATIVE_PATH);
match fs::read_to_string(&workspace_settings_path) {
Ok(content) => {
if let Some(config) = Self::from_workspace_settings_toml(
workspace_root,
&workspace_settings_path,
&content,
)? {
return Ok(config);
}
}
Err(source) if source.kind() == std::io::ErrorKind::NotFound => {}
Err(source) => {
return Err(TicketConfigError::Read {
path: workspace_settings_path,
source,
});
}
}
// Narrow read-only migration fallback for pre-workspace-settings projects.
// As soon as `.yoi/workspace.toml` contains any `[ticket]` table, the
// workspace settings authority wins and this legacy file is ignored.
let legacy_path = workspace_root.join(TICKET_CONFIG_RELATIVE_PATH);
let legacy_content = match fs::read_to_string(&legacy_path) {
let path = workspace_root.join(TICKET_CONFIG_RELATIVE_PATH);
let content = match fs::read_to_string(&path) {
Ok(content) => content,
Err(source) if source.kind() == std::io::ErrorKind::NotFound => {
return Ok(Self::default_for_workspace(workspace_root));
}
Err(source) => {
return Err(TicketConfigError::Read {
path: legacy_path,
source,
});
}
Err(source) => return Err(TicketConfigError::Read { path, source }),
};
Self::from_toml(workspace_root, &legacy_path, &legacy_content)
}
pub fn from_workspace_settings_toml(
workspace_root: impl AsRef<Path>,
path: impl AsRef<Path>,
content: &str,
) -> Result<Option<Self>, TicketConfigError> {
let workspace_root = workspace_root.as_ref();
let path = path.as_ref();
let value: toml::Value =
toml::from_str(content).map_err(|source| TicketConfigError::Parse {
path: path.to_path_buf(),
source,
})?;
let Some(ticket_value) = value.get("ticket").cloned() else {
return Ok(None);
};
let raw: RawWorkspaceTicketConfig =
ticket_value
.try_into()
.map_err(|source| TicketConfigError::Parse {
path: path.to_path_buf(),
source,
})?;
raw.resolve(workspace_root, path).map(Some)
}
pub fn workspace_settings_has_ticket_config(
path: impl AsRef<Path>,
content: &str,
) -> Result<bool, TicketConfigError> {
let path = path.as_ref();
let value: toml::Value =
toml::from_str(content).map_err(|source| TicketConfigError::Parse {
path: path.to_path_buf(),
source,
})?;
Ok(value.get("ticket").is_some())
Self::from_toml(workspace_root, &path, &content)
}
pub fn from_toml(
@ -559,11 +487,11 @@ impl Default for TicketRoleProfiles {
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum TicketRoleLaunchConfigError {
#[error(
"Ticket role `{role}` is not launch-configured; add `[ticket.roles.{role}]` with the role builtin profile or another executable concrete profile selector"
"Ticket role `{role}` is not launch-configured; add `[roles.{role}]` with the role builtin profile or another executable concrete profile selector"
)]
MissingRoleTable { role: TicketRole },
#[error(
"Ticket role `{role}` has no launch profile; set `[ticket.roles.{role}].profile` to the role builtin profile or another executable concrete profile selector"
"Ticket role `{role}` has no launch profile; set `[roles.{role}].profile` to the role builtin profile or another executable concrete profile selector"
)]
MissingProfile { role: TicketRole },
#[error(
@ -747,19 +675,6 @@ struct RawTicketConfig {
roles: BTreeMap<String, RawTicketRoleConfig>,
}
#[derive(Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
struct RawWorkspaceTicketConfig {
#[serde(default)]
backend: RawBackendConfig,
#[serde(default)]
language: Option<TicketRecordLanguage>,
#[serde(default)]
orchestration: RawTicketOrchestrationConfig,
#[serde(default)]
roles: BTreeMap<String, RawTicketRoleConfig>,
}
#[derive(Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
struct RawTicketOrchestrationConfig {
@ -808,76 +723,39 @@ impl RawTicketConfig {
workspace_root: &Path,
path: &Path,
) -> Result<TicketConfig, TicketConfigError> {
resolve_ticket_config_parts(
self.backend,
self.ticket.resolve(),
self.orchestration,
self.roles,
workspace_root,
path,
)
}
}
impl RawWorkspaceTicketConfig {
fn resolve(
self,
workspace_root: &Path,
path: &Path,
) -> Result<TicketConfig, TicketConfigError> {
resolve_ticket_config_parts(
self.backend,
TicketRecordConfig {
language: self.language,
},
self.orchestration,
self.roles,
workspace_root,
path,
)
}
}
fn resolve_ticket_config_parts(
backend: RawBackendConfig,
ticket: TicketRecordConfig,
orchestration: RawTicketOrchestrationConfig,
raw_roles: BTreeMap<String, RawTicketRoleConfig>,
workspace_root: &Path,
path: &Path,
) -> Result<TicketConfig, TicketConfigError> {
let mut roles = TicketRoleProfiles::default();
for (name, raw_role) in raw_roles {
let role = TicketRole::parse(&name).ok_or_else(|| TicketConfigError::Invalid {
path: path.to_path_buf(),
message: format!(
"unsupported Ticket role `{name}`; supported fixed roles: {}",
TicketRole::supported_names().join(", ")
),
})?;
let profile_configured = raw_role.profile.is_some();
roles.inner.insert(role, raw_role.resolve(role));
roles.configured_roles.insert(role);
if profile_configured {
roles.profile_configured_roles.insert(role);
let mut roles = TicketRoleProfiles::default();
for (name, raw_role) in self.roles {
let role = TicketRole::parse(&name).ok_or_else(|| TicketConfigError::Invalid {
path: path.to_path_buf(),
message: format!(
"unsupported Ticket role `{name}`; supported fixed roles: {}",
TicketRole::supported_names().join(", ")
),
})?;
let profile_configured = raw_role.profile.is_some();
roles.inner.insert(role, raw_role.resolve(role));
roles.configured_roles.insert(role);
if profile_configured {
roles.profile_configured_roles.insert(role);
}
}
Ok(TicketConfig {
backend: self.backend.resolve(workspace_root).map_err(|message| {
TicketConfigError::Invalid {
path: path.to_path_buf(),
message,
}
})?,
ticket: self.ticket.resolve(),
orchestration: self.orchestration.resolve().map_err(|message| {
TicketConfigError::Invalid {
path: path.to_path_buf(),
message,
}
})?,
roles,
})
}
Ok(TicketConfig {
backend: backend
.resolve(workspace_root)
.map_err(|message| TicketConfigError::Invalid {
path: path.to_path_buf(),
message,
})?,
ticket,
orchestration: orchestration
.resolve()
.map_err(|message| TicketConfigError::Invalid {
path: path.to_path_buf(),
message,
})?,
roles,
})
}
#[derive(Debug, Default, Deserialize)]
@ -965,12 +843,6 @@ mod tests {
std::fs::write(dir.join("ticket.config.toml"), content).unwrap();
}
fn write_workspace_settings(workspace: &Path, content: &str) {
let dir = workspace.join(".yoi");
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("workspace.toml"), content).unwrap();
}
#[test]
fn missing_config_returns_documented_defaults() {
let temp = TempDir::new().unwrap();
@ -1003,124 +875,40 @@ mod tests {
}
}
#[test]
fn workspace_settings_take_precedence_over_legacy_ticket_config() {
let temp = TempDir::new().unwrap();
write_workspace_settings(
temp.path(),
r#"
[ticket]
language = "Japanese"
[ticket.backend]
provider = "builtin:yoi_local"
root = "workspace-tickets"
"#,
);
write_config(
temp.path(),
r#"
[backend]
provider = "builtin:yoi_local"
root = "legacy-tickets"
[ticket]
language = "English"
"#,
);
let config = TicketConfig::load_workspace(temp.path()).unwrap();
assert_eq!(config.backend.root, temp.path().join("workspace-tickets"));
assert_eq!(config.ticket_record_language(), Some("Japanese"));
}
#[test]
fn legacy_ticket_config_is_read_only_migration_fallback() {
let temp = TempDir::new().unwrap();
write_workspace_settings(
temp.path(),
r#"
workspace_id = "00000000-0000-7000-8000-000000000000"
display_name = "legacy-fallback"
"#,
);
write_config(
temp.path(),
r#"
[backend]
provider = "builtin:yoi_local"
root = "legacy-tickets"
[ticket]
language = "Japanese"
"#,
);
let config = TicketConfig::load_workspace(temp.path()).unwrap();
assert_eq!(config.backend.root, temp.path().join("legacy-tickets"));
assert_eq!(config.ticket_record_language(), Some("Japanese"));
}
#[test]
fn empty_workspace_ticket_table_uses_workspace_defaults_and_ignores_legacy() {
let temp = TempDir::new().unwrap();
write_workspace_settings(
temp.path(),
r#"
[ticket]
"#,
);
write_config(
temp.path(),
r#"
[backend]
provider = "builtin:yoi_local"
root = "legacy-tickets"
[ticket]
language = "Japanese"
"#,
);
let config = TicketConfig::load_workspace(temp.path()).unwrap();
assert_eq!(config.backend.root, temp.path().join(".yoi/tickets"));
assert_eq!(config.ticket_record_language(), None);
}
#[test]
fn full_config_parses_fixed_role_refs() {
let temp = TempDir::new().unwrap();
write_workspace_settings(
write_config(
temp.path(),
r#"
[ticket]
language = "Japanese"
[ticket.backend]
[backend]
provider = "builtin:yoi_local"
root = "custom-tickets"
[ticket.orchestration]
[ticket]
language = "Japanese"
[orchestration]
branch = "orchestration/custom-panel"
worktree_dir = "custom-worktrees"
worktree_name = "custom-orchestrator"
[ticket.roles.intake]
[roles.intake]
profile = "project:intake"
launch_prompt = "$workspace/ticket/intake/launch"
workflow = "ticket-intake-workflow"
[ticket.roles.orchestrator]
[roles.orchestrator]
profile = "project:orchestrator"
launch_prompt = "$workspace/ticket/orchestrator/launch"
workflow = "ticket-orchestrator-routing"
[ticket.roles.coder]
[roles.coder]
profile = "inherit"
launch_prompt = "$workspace/ticket/coder/launch"
workflow = "multi-agent-workflow"
[ticket.roles.reviewer]
[roles.reviewer]
profile = "project:reviewer"
launch_prompt = "$workspace/ticket/reviewer/launch"
workflow = "multi-agent-workflow"
@ -1172,30 +960,28 @@ workflow = "multi-agent-workflow"
let temp = TempDir::new().unwrap();
let scaffold = ticket_config_scaffold();
assert!(scaffold.contains("[ticket]\n"));
assert!(scaffold.contains("[ticket.backend]\n"));
assert!(scaffold.contains("[backend]\n"));
assert!(scaffold.contains("provider = \"builtin:yoi_local\""));
assert!(scaffold.contains("root = \".yoi/tickets\""));
assert!(scaffold.contains("# language = \"Japanese\""));
assert!(scaffold.contains("# [ticket]\n# language = \"Japanese\""));
assert!(scaffold.contains(
"# [ticket.orchestration]\n# branch = \"orchestration\"\n# worktree_dir = \".worktree\"\n# worktree_name = \"orchestration\""
"# [orchestration]\n# branch = \"orchestration\"\n# worktree_dir = \".worktree\"\n# worktree_name = \"orchestration\""
));
for role in TicketRole::ALL {
assert!(scaffold.contains(&format!("[ticket.roles.{role}]")));
assert!(scaffold.contains(&format!("[roles.{role}]")));
assert!(scaffold.contains(&format!(
"[ticket.roles.{role}]\nprofile = \"{}\"\nworkflow = \"{}\"",
"[roles.{role}]\nprofile = \"{}\"\nworkflow = \"{}\"",
role.default_profile(),
role.default_workflow()
)));
}
assert!(!scaffold.contains("[ticket.roles.investigator]"));
assert!(!scaffold.contains("[roles.investigator]"));
let config = TicketConfig::from_workspace_settings_toml(
let config = TicketConfig::from_toml(
temp.path(),
temp.path().join(WORKSPACE_SETTINGS_RELATIVE_PATH),
temp.path().join(TICKET_CONFIG_RELATIVE_PATH),
&scaffold,
)
.unwrap()
.unwrap();
assert_eq!(config.backend_root(), temp.path().join(".yoi/tickets"));
assert_eq!(config.orchestration.branch_name(), None);

View File

@ -82,7 +82,7 @@ fn io_err(path: impl Into<PathBuf>, source: io::Error) -> TicketError {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TicketStatus {
Open,
Closed,
@ -111,7 +111,7 @@ impl fmt::Display for TicketStatus {
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ExtensibleTicketStatus {
Open,
Closed,
@ -155,8 +155,7 @@ impl From<TicketStatus> for ExtensibleTicketStatus {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TicketWorkflowState {
Planning,
Ready,
@ -222,7 +221,7 @@ impl fmt::Display for TicketWorkflowState {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MarkdownText(pub String);
impl MarkdownText {
@ -247,7 +246,7 @@ impl From<String> for MarkdownText {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TicketIdOrSlug {
Id(String),
Slug(String),
@ -274,8 +273,7 @@ impl From<String> for TicketIdOrSlug {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TicketEventKind {
Create,
Comment,
@ -342,8 +340,7 @@ impl From<&str> for TicketEventKind {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TicketReviewResult {
Approve,
RequestChanges,
@ -378,13 +375,13 @@ impl From<&str> for TicketReviewResult {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketReference {
pub kind: String,
pub target: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewTicketEvent {
pub kind: TicketEventKind,
pub author: Option<String>,
@ -403,7 +400,7 @@ impl NewTicketEvent {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketStateChange {
pub from: String,
pub to: String,
@ -431,7 +428,7 @@ impl TicketStateChange {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketIntakeSummary {
pub author: Option<String>,
pub body: MarkdownText,
@ -448,7 +445,7 @@ impl TicketIntakeSummary {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketReview {
pub result: TicketReviewResult,
pub author: Option<String>,
@ -473,7 +470,7 @@ impl TicketReview {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewTicket {
pub title: String,
pub slug: Option<String>,
@ -510,7 +507,7 @@ impl NewTicket {
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TicketFilter {
pub state: Option<TicketWorkflowState>,
}
@ -525,7 +522,7 @@ impl TicketFilter {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketRef {
pub id: String,
pub slug: String,
@ -571,7 +568,7 @@ impl fmt::Display for TicketRelationKind {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewTicketRelation {
pub kind: TicketRelationKind,
pub target: String,
@ -599,7 +596,7 @@ struct TicketRelationArtifact {
relations: Vec<TicketRelation>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DerivedTicketRelation {
pub source_ticket: String,
pub inverse_kind: String,
@ -609,7 +606,7 @@ pub struct DerivedTicketRelation {
pub at: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketRelationBlocker {
pub blocking_ticket: String,
pub reason_kind: String,
@ -618,14 +615,14 @@ pub struct TicketRelationBlocker {
pub blocking_state: TicketWorkflowState,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketRelationNotice {
pub related_ticket: String,
pub kind: TicketRelationKind,
pub message: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TicketRelationView {
pub outgoing: Vec<TicketRelation>,
pub incoming: Vec<DerivedTicketRelation>,
@ -705,7 +702,7 @@ pub struct AcceptedOrchestrationPlan {
pub role_plan: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewOrchestrationPlanRecord {
pub kind: OrchestrationPlanKind,
pub related_ticket: Option<String>,
@ -730,7 +727,7 @@ pub struct OrchestrationPlanRecord {
pub at: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketMeta {
pub id: String,
pub slug: String,
@ -751,7 +748,7 @@ pub struct TicketMeta {
pub raw: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketSummary {
pub id: String,
pub slug: String,
@ -768,31 +765,31 @@ pub struct TicketSummary {
pub updated_at: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketInvalidRecord {
pub label: String,
pub reason: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TicketPartialList {
pub tickets: Vec<TicketSummary>,
pub invalid_records: Vec<TicketInvalidRecord>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketPartial {
pub ticket: Ticket,
pub invalid_records: Vec<TicketInvalidRecord>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketDocument {
pub body: MarkdownText,
pub raw_frontmatter: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketEvent {
pub kind: TicketEventKind,
pub author: Option<String>,
@ -808,13 +805,13 @@ pub struct TicketEvent {
pub attributes: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketArtifactRef {
/// Path relative to the ticket's `artifacts/` directory.
pub relative_path: PathBuf,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ticket {
pub meta: TicketMeta,
pub document: TicketDocument,
@ -824,20 +821,20 @@ pub struct Ticket {
pub resolution: Option<MarkdownText>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TicketDoctorSeverity {
Error,
Warning,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketDoctorDiagnostic {
pub severity: TicketDoctorSeverity,
pub message: String,
pub path: Option<PathBuf>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TicketDoctorReport {
pub diagnostics: Vec<TicketDoctorDiagnostic>,
}
@ -872,7 +869,6 @@ impl TicketDoctorReport {
}
pub trait TicketBackend {
fn default_intake_ready_state_change_body(&self, from: &str) -> String;
fn list(&self, filter: TicketFilter) -> Result<Vec<TicketSummary>>;
fn show(&self, id: TicketIdOrSlug) -> Result<Ticket>;
fn create(&self, input: NewTicket) -> Result<TicketRef>;
@ -919,195 +915,6 @@ pub trait TicketBackend {
fn doctor(&self) -> Result<TicketDoctorReport>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "operation", rename_all = "snake_case")]
pub enum TicketBackendOperation {
DefaultIntakeReadyStateChangeBody {
from: String,
},
List {
filter: TicketFilter,
},
Show {
id: TicketIdOrSlug,
},
Create {
input: NewTicket,
},
AddEvent {
id: TicketIdOrSlug,
event: NewTicketEvent,
},
AddStateChanged {
id: TicketIdOrSlug,
change: TicketStateChange,
},
AddIntakeSummary {
id: TicketIdOrSlug,
summary: TicketIntakeSummary,
},
SetStateField {
id: TicketIdOrSlug,
field: String,
change: TicketStateChange,
},
SetWorkflowState {
id: TicketIdOrSlug,
change: TicketStateChange,
},
MarkIntakeReady {
id: TicketIdOrSlug,
summary: TicketIntakeSummary,
change: TicketStateChange,
},
QueueReady {
id: TicketIdOrSlug,
queued_by: String,
},
Review {
id: TicketIdOrSlug,
review: TicketReview,
},
Close {
id: TicketIdOrSlug,
resolution: MarkdownText,
},
AddTicketRelation {
id: TicketIdOrSlug,
relation: NewTicketRelation,
},
QueryTicketRelations {
ticket: Option<TicketIdOrSlug>,
kind: Option<TicketRelationKind>,
},
RelationView {
id: TicketIdOrSlug,
},
AddOrchestrationPlanRecord {
id: TicketIdOrSlug,
record: NewOrchestrationPlanRecord,
},
QueryOrchestrationPlanRecords {
ticket: Option<TicketIdOrSlug>,
kind: Option<OrchestrationPlanKind>,
},
Doctor,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "result", content = "value", rename_all = "snake_case")]
pub enum TicketBackendOperationResult {
Text(String),
Unit,
Tickets(Vec<TicketSummary>),
Ticket(Ticket),
TicketRef(TicketRef),
Relation(TicketRelation),
Relations(Vec<TicketRelation>),
RelationView(TicketRelationView),
OrchestrationPlanRecord(OrchestrationPlanRecord),
OrchestrationPlanRecords(Vec<OrchestrationPlanRecord>),
DoctorReport(TicketDoctorReport),
}
pub fn execute_ticket_backend_operation<B>(
backend: &B,
operation: TicketBackendOperation,
) -> Result<TicketBackendOperationResult>
where
B: TicketBackend + ?Sized,
{
Ok(match operation {
TicketBackendOperation::DefaultIntakeReadyStateChangeBody { from } => {
TicketBackendOperationResult::Text(
backend.default_intake_ready_state_change_body(&from),
)
}
TicketBackendOperation::List { filter } => {
TicketBackendOperationResult::Tickets(backend.list(filter)?)
}
TicketBackendOperation::Show { id } => {
TicketBackendOperationResult::Ticket(backend.show(id)?)
}
TicketBackendOperation::Create { input } => {
TicketBackendOperationResult::TicketRef(backend.create(input)?)
}
TicketBackendOperation::AddEvent { id, event } => {
backend.add_event(id, event)?;
TicketBackendOperationResult::Unit
}
TicketBackendOperation::AddStateChanged { id, change } => {
backend.add_state_changed(id, change)?;
TicketBackendOperationResult::Unit
}
TicketBackendOperation::AddIntakeSummary { id, summary } => {
backend.add_intake_summary(id, summary)?;
TicketBackendOperationResult::Unit
}
TicketBackendOperation::SetStateField { id, field, change } => {
backend.set_state_field(id, &field, change)?;
TicketBackendOperationResult::Unit
}
TicketBackendOperation::SetWorkflowState { id, change } => {
backend.set_workflow_state(id, change)?;
TicketBackendOperationResult::Unit
}
TicketBackendOperation::MarkIntakeReady {
id,
summary,
change,
} => {
backend.mark_intake_ready(id, summary, change)?;
TicketBackendOperationResult::Unit
}
TicketBackendOperation::QueueReady { id, queued_by } => {
backend.queue_ready(id, &queued_by)?;
TicketBackendOperationResult::Unit
}
TicketBackendOperation::Review { id, review } => {
backend.review(id, review)?;
TicketBackendOperationResult::Unit
}
TicketBackendOperation::Close { id, resolution } => {
backend.close(id, resolution)?;
TicketBackendOperationResult::Unit
}
TicketBackendOperation::AddTicketRelation { id, relation } => {
TicketBackendOperationResult::Relation(backend.add_ticket_relation(id, relation)?)
}
TicketBackendOperation::QueryTicketRelations { ticket, kind } => {
TicketBackendOperationResult::Relations(backend.query_ticket_relations(ticket, kind)?)
}
TicketBackendOperation::RelationView { id } => {
TicketBackendOperationResult::RelationView(backend.relation_view(id)?)
}
TicketBackendOperation::AddOrchestrationPlanRecord { id, record } => {
TicketBackendOperationResult::OrchestrationPlanRecord(
backend.add_orchestration_plan_record(id, record)?,
)
}
TicketBackendOperation::QueryOrchestrationPlanRecords { ticket, kind } => {
TicketBackendOperationResult::OrchestrationPlanRecords(
backend.query_orchestration_plan_records(ticket, kind)?,
)
}
TicketBackendOperation::Doctor => {
TicketBackendOperationResult::DoctorReport(backend.doctor()?)
}
})
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "snake_case")]
pub enum TicketBackendHttpResponse {
Ok {
result: TicketBackendOperationResult,
},
Error {
message: String,
},
}
#[derive(Debug, Clone)]
pub struct LocalTicketBackend {
root: PathBuf,
@ -1585,10 +1392,6 @@ impl LocalTicketBackend {
}
impl TicketBackend for LocalTicketBackend {
fn default_intake_ready_state_change_body(&self, from: &str) -> String {
self.default_intake_ready_state_change_body(from)
}
fn list(&self, filter: TicketFilter) -> Result<Vec<TicketSummary>> {
let mut tickets = Vec::new();
for dir in self.iter_ticket_dirs(filter)? {

View File

@ -13,11 +13,10 @@ use serde_json::{Value, json};
use crate::{
AcceptedOrchestrationPlan, LocalTicketBackend, MarkdownText, NewOrchestrationPlanRecord,
NewTicket, NewTicketEvent, NewTicketRelation, OrchestrationPlanKind, OrchestrationPlanRecord,
Result as TicketResult, Ticket, TicketBackend, TicketDoctorDiagnostic, TicketDoctorReport,
TicketDoctorSeverity, TicketError, TicketEventKind, TicketIdOrSlug, TicketIntakeSummary,
TicketRef, TicketRelation, TicketRelationKind, TicketRelationView, TicketReview,
TicketReviewResult, TicketStateChange, TicketSummary, TicketWorkflowState,
NewTicket, NewTicketEvent, NewTicketRelation, OrchestrationPlanKind, Ticket, TicketBackend,
TicketDoctorDiagnostic, TicketDoctorReport, TicketDoctorSeverity, TicketError, TicketEventKind,
TicketIdOrSlug, TicketIntakeSummary, TicketRelationKind, TicketReview, TicketReviewResult,
TicketStateChange, TicketSummary, TicketWorkflowState,
};
const DEFAULT_LIST_LIMIT: usize = 50;
@ -167,160 +166,6 @@ pub fn ticket_tool_description(name: &str, record_language: Option<&str>) -> Str
description
}
/// Backend object used by the LLM-facing Ticket tools.
///
/// Tool execution is intentionally parameterized by this wrapper rather than by
/// `LocalTicketBackend` so Worker hosts can supply an API-backed implementation
/// without changing the model-visible tool surface.
#[derive(Clone)]
pub struct TicketToolBackend {
backend: Arc<dyn TicketBackend + Send + Sync>,
record_language: Option<String>,
}
impl TicketToolBackend {
pub fn new<B>(backend: B) -> Self
where
B: TicketBackend + Send + Sync + 'static,
{
Self {
backend: Arc::new(backend),
record_language: None,
}
}
pub fn with_record_language(mut self, language: Option<&str>) -> Self {
self.record_language = language
.map(str::trim)
.filter(|language| !language.is_empty())
.map(str::to_string);
self
}
pub fn record_language(&self) -> Option<&str> {
self.record_language.as_deref()
}
}
impl From<LocalTicketBackend> for TicketToolBackend {
fn from(backend: LocalTicketBackend) -> Self {
let record_language = backend.record_language().map(str::to_string);
Self::new(backend).with_record_language(record_language.as_deref())
}
}
impl TicketBackend for TicketToolBackend {
fn default_intake_ready_state_change_body(&self, from: &str) -> String {
self.backend.default_intake_ready_state_change_body(from)
}
fn list(&self, filter: crate::TicketFilter) -> TicketResult<Vec<TicketSummary>> {
self.backend.list(filter)
}
fn show(&self, id: TicketIdOrSlug) -> TicketResult<Ticket> {
self.backend.show(id)
}
fn create(&self, input: NewTicket) -> TicketResult<TicketRef> {
self.backend.create(input)
}
fn add_event(&self, id: TicketIdOrSlug, event: NewTicketEvent) -> TicketResult<()> {
self.backend.add_event(id, event)
}
fn add_state_changed(&self, id: TicketIdOrSlug, change: TicketStateChange) -> TicketResult<()> {
self.backend.add_state_changed(id, change)
}
fn add_intake_summary(
&self,
id: TicketIdOrSlug,
summary: TicketIntakeSummary,
) -> TicketResult<()> {
self.backend.add_intake_summary(id, summary)
}
fn set_state_field(
&self,
id: TicketIdOrSlug,
field: &str,
change: TicketStateChange,
) -> TicketResult<()> {
self.backend.set_state_field(id, field, change)
}
fn set_workflow_state(
&self,
id: TicketIdOrSlug,
change: TicketStateChange,
) -> TicketResult<()> {
self.backend.set_workflow_state(id, change)
}
fn mark_intake_ready(
&self,
id: TicketIdOrSlug,
summary: TicketIntakeSummary,
change: TicketStateChange,
) -> TicketResult<()> {
self.backend.mark_intake_ready(id, summary, change)
}
fn queue_ready(&self, id: TicketIdOrSlug, queued_by: &str) -> TicketResult<()> {
self.backend.queue_ready(id, queued_by)
}
fn review(&self, id: TicketIdOrSlug, review: TicketReview) -> TicketResult<()> {
self.backend.review(id, review)
}
fn close(&self, id: TicketIdOrSlug, resolution: MarkdownText) -> TicketResult<()> {
self.backend.close(id, resolution)
}
fn add_ticket_relation(
&self,
id: TicketIdOrSlug,
relation: NewTicketRelation,
) -> TicketResult<TicketRelation> {
self.backend.add_ticket_relation(id, relation)
}
fn query_ticket_relations(
&self,
ticket: Option<TicketIdOrSlug>,
kind: Option<TicketRelationKind>,
) -> TicketResult<Vec<TicketRelation>> {
self.backend.query_ticket_relations(ticket, kind)
}
fn relation_view(&self, id: TicketIdOrSlug) -> TicketResult<TicketRelationView> {
self.backend.relation_view(id)
}
fn add_orchestration_plan_record(
&self,
id: TicketIdOrSlug,
record: NewOrchestrationPlanRecord,
) -> TicketResult<OrchestrationPlanRecord> {
self.backend.add_orchestration_plan_record(id, record)
}
fn query_orchestration_plan_records(
&self,
ticket: Option<TicketIdOrSlug>,
kind: Option<OrchestrationPlanKind>,
) -> TicketResult<Vec<OrchestrationPlanRecord>> {
self.backend.query_orchestration_plan_records(ticket, kind)
}
fn doctor(&self) -> TicketResult<TicketDoctorReport> {
self.backend.doctor()
}
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
struct TicketCreateParams {
/// Ticket title. Must not be empty.
@ -720,67 +565,67 @@ struct TicketDoctorOutput {
#[derive(Clone)]
struct TicketCreateTool {
backend: TicketToolBackend,
backend: LocalTicketBackend,
}
#[derive(Clone)]
struct TicketListTool {
backend: TicketToolBackend,
backend: LocalTicketBackend,
}
#[derive(Clone)]
struct TicketShowTool {
backend: TicketToolBackend,
backend: LocalTicketBackend,
}
#[derive(Clone)]
struct TicketCommentTool {
backend: TicketToolBackend,
backend: LocalTicketBackend,
}
#[derive(Clone)]
struct TicketReviewTool {
backend: TicketToolBackend,
backend: LocalTicketBackend,
}
#[derive(Clone)]
struct TicketIntakeReadyTool {
backend: TicketToolBackend,
backend: LocalTicketBackend,
}
#[derive(Clone)]
struct TicketWorkflowStateTool {
backend: TicketToolBackend,
backend: LocalTicketBackend,
}
#[derive(Clone)]
struct TicketCloseTool {
backend: TicketToolBackend,
backend: LocalTicketBackend,
}
#[derive(Clone)]
struct TicketRelationRecordTool {
backend: TicketToolBackend,
backend: LocalTicketBackend,
}
#[derive(Clone)]
struct TicketRelationQueryTool {
backend: TicketToolBackend,
backend: LocalTicketBackend,
}
#[derive(Clone)]
struct TicketOrchestrationPlanRecordTool {
backend: TicketToolBackend,
backend: LocalTicketBackend,
}
#[derive(Clone)]
struct TicketOrchestrationPlanQueryTool {
backend: TicketToolBackend,
backend: LocalTicketBackend,
}
#[derive(Clone)]
struct TicketDoctorTool {
backend: TicketToolBackend,
backend: LocalTicketBackend,
}
#[async_trait]
@ -1463,9 +1308,9 @@ fn json_output(summary: String, value: impl Serialize) -> ToolOutput {
}
}
fn tool_definition<T>(name: &'static str, backend: TicketToolBackend) -> ToolDefinition
fn tool_definition<T>(name: &'static str, backend: LocalTicketBackend) -> ToolDefinition
where
T: Tool + From<TicketToolBackend> + 'static,
T: Tool + From<LocalTicketBackend> + 'static,
{
let description = ticket_tool_description(name, backend.record_language());
Arc::new(move || {
@ -1510,8 +1355,8 @@ fn input_schema(name: &str) -> Value {
macro_rules! impl_from_backend {
($tool:ident) => {
impl From<TicketToolBackend> for $tool {
fn from(backend: TicketToolBackend) -> Self {
impl From<LocalTicketBackend> for $tool {
fn from(backend: LocalTicketBackend) -> Self {
Self { backend }
}
}
@ -1532,9 +1377,8 @@ impl_from_backend!(TicketOrchestrationPlanRecordTool);
impl_from_backend!(TicketOrchestrationPlanQueryTool);
impl_from_backend!(TicketDoctorTool);
/// Build all MVP Ticket tool definitions over the supplied backend.
pub fn ticket_tools(backend: impl Into<TicketToolBackend>) -> Vec<ToolDefinition> {
let backend = backend.into();
/// Build all MVP Ticket tool definitions over one local backend root.
pub fn ticket_tools(backend: LocalTicketBackend) -> Vec<ToolDefinition> {
vec![
tool_definition::<TicketCreateTool>("TicketCreate", backend.clone()),
tool_definition::<TicketListTool>("TicketList", backend.clone()),
@ -1572,7 +1416,7 @@ mod tests {
tool
}
fn tool_by_name(backend: impl Into<TicketToolBackend>, name: &str) -> Arc<dyn Tool> {
fn tool_by_name(backend: LocalTicketBackend, name: &str) -> Arc<dyn Tool> {
ticket_tools(backend)
.into_iter()
.find_map(|definition| {
@ -1582,7 +1426,7 @@ mod tests {
.expect("tool exists")
}
fn tool_description_by_name(backend: impl Into<TicketToolBackend>, name: &str) -> String {
fn tool_description_by_name(backend: LocalTicketBackend, name: &str) -> String {
ticket_tools(backend)
.into_iter()
.find_map(|definition| {
@ -2459,7 +2303,7 @@ mod tests {
let temp = TempDir::new().unwrap();
let create = tool(tool_definition::<TicketCreateTool>(
"TicketCreate",
backend(&temp).into(),
backend(&temp),
));
let _ = create;
}

View File

@ -23,6 +23,7 @@ manifest = { workspace = true }
secrets = { workspace = true }
session-store = { workspace = true }
fs4 = { workspace = true }
provider = { workspace = true }
ticket = { workspace = true }
serde = { workspace = true, features = ["derive"] }
minijinja = "2.19.0"

View File

@ -2082,11 +2082,6 @@ impl App {
let value = serde_json::to_value(&item).expect("SystemItem is Serialize");
self.apply_system_item(&value);
}
session_store::LogEntry::Extension {
domain, payload, ..
} if domain == "yoi.compaction" => {
self.apply_compaction_extension(&payload);
}
// Non-history-bearing variants don't affect the block view.
_ => {}
}
@ -2097,58 +2092,6 @@ impl App {
/// Kind-based routing replaces the old free-text `[Notification]` /
/// `[File: …]` parsing path: each kind maps directly to a typed
/// block (`Block::Notify`, `Block::WorkerEvent`, …).
fn apply_compaction_extension(&mut self, payload: &serde_json::Value) {
if payload.get("kind").and_then(|value| value.as_str()) != Some("compaction_block") {
return;
}
match payload.get("state").and_then(|value| value.as_str()) {
Some("running") => {
if self.last_streaming_compact_mut().is_none() {
self.blocks.push(Block::Compact(CompactEvent::Streaming {
started_at: Instant::now(),
}));
}
}
Some("done") => {
let new_segment_id = payload
.get("new_segment_id")
.and_then(|value| value.as_str())
.and_then(|value| value.parse::<uuid::Uuid>().ok())
.unwrap_or_else(uuid::Uuid::nil);
if let Some(evt) = self.last_streaming_compact_mut() {
*evt = CompactEvent::Done {
new_segment_id,
elapsed_secs: None,
};
} else {
self.blocks.push(Block::Compact(CompactEvent::Done {
new_segment_id,
elapsed_secs: None,
}));
}
}
Some("failed") => {
let error = payload
.get("error")
.and_then(|value| value.as_str())
.unwrap_or("compact failed")
.to_string();
if let Some(evt) = self.last_streaming_compact_mut() {
*evt = CompactEvent::Failed {
error,
elapsed_secs: None,
};
} else {
self.blocks.push(Block::Compact(CompactEvent::Failed {
error,
elapsed_secs: None,
}));
}
}
_ => {}
}
}
fn apply_system_item(&mut self, value: &serde_json::Value) {
let Ok(item) = serde_json::from_value::<session_store::SystemItem>(value.clone()) else {
// Unknown / forward-compat shape: fall back to rendering the

View File

@ -94,13 +94,13 @@ fn ensure_and_restore_use_configured_orchestration_layout() {
write_test_ticket_config(
&root,
r#"
[ticket.orchestration]
[orchestration]
branch = "orchestration/custom-panel"
worktree_dir = "custom-worktrees"
worktree_name = "panel"
"#,
);
run_test_git(&root, &["add", ".yoi/workspace.toml"]).unwrap();
run_test_git(&root, &["add", ".yoi/ticket.config.toml"]).unwrap();
run_test_git(&root, &["commit", "-m", "ticket config"]).unwrap();
let resolved = resolved_orchestration_worktree_layout(&root).unwrap();
@ -126,7 +126,7 @@ fn invalid_configured_orchestration_branch_is_rejected_before_git_worktree_opera
write_test_ticket_config(
&root,
r#"
[ticket.orchestration]
[orchestration]
branch = "orchestration/bad:branch"
"#,
);
@ -145,11 +145,11 @@ fn restore_rejects_mismatched_configured_orchestration_branch_without_checkout()
write_test_ticket_config(
&root,
r#"
[ticket.orchestration]
[orchestration]
branch = "orchestration/custom-panel"
"#,
);
run_test_git(&root, &["add", ".yoi/workspace.toml"]).unwrap();
run_test_git(&root, &["add", ".yoi/ticket.config.toml"]).unwrap();
run_test_git(&root, &["commit", "-m", "ticket config"]).unwrap();
let layout = resolved_orchestration_worktree_layout(&root).unwrap();
run_test_git(
@ -259,7 +259,7 @@ fn existing_unrelated_repo_with_expected_branch_is_rejected_without_cleanup() {
fn write_test_ticket_config(root: &Path, content: &str) {
let config_dir = root.join(".yoi");
std::fs::create_dir_all(&config_dir).unwrap();
std::fs::write(config_dir.join("workspace.toml"), content).unwrap();
std::fs::write(config_dir.join("ticket.config.toml"), content).unwrap();
}
fn init_test_repo(root: &Path) {
@ -337,8 +337,8 @@ fn ticket_workspace(
)
.unwrap();
fs::write(
temp.path().join(".yoi/workspace.toml"),
"[ticket]\n\n[ticket.backend]\nprovider = \"builtin:yoi_local\"\nroot = \".yoi/tickets\"\n",
temp.path().join(".yoi/ticket.config.toml"),
"[backend]\nprovider = \"builtin:yoi_local\"\nroot = \".yoi/tickets\"\n",
)
.unwrap();
let backend = LocalTicketBackend::new(temp.path().join(".yoi/tickets"));
@ -712,7 +712,7 @@ async fn ticket_close_action_blocks_non_done_ticket_without_mutation() {
#[tokio::test]
async fn ticket_action_rejects_stale_absent_config_without_mutation() {
let (temp, ticket_id, backend) = ready_ticket_workspace("panel-no-config");
fs::remove_file(temp.path().join(".yoi/workspace.toml")).unwrap();
fs::remove_file(temp.path().join(".yoi/ticket.config.toml")).unwrap();
let error =
dispatch_ticket_action(request_for(&temp, ticket_id.clone(), NextUserAction::Queue))
@ -1059,8 +1059,8 @@ fn dashboard_ticket_action_rows_precede_pods_and_pod_actions_still_work() {
let temp = TempDir::new().unwrap();
fs::create_dir_all(temp.path().join(".yoi")).unwrap();
fs::write(
temp.path().join(".yoi/workspace.toml"),
"[ticket]\n\n[ticket.backend]\nprovider = \"builtin:yoi_local\"\nroot = \".yoi/tickets\"\n",
temp.path().join(".yoi/ticket.config.toml"),
"[backend]\nprovider = \"builtin:yoi_local\"\nroot = \".yoi/tickets\"\n",
)
.unwrap();
let backend = LocalTicketBackend::new(temp.path().join(".yoi/tickets"));

View File

@ -3,7 +3,7 @@ use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use manifest::model_catalog::{self as catalog, AuthHint, ModelEntry, ProviderEntry};
use provider::catalog::{self, AuthHint, ModelEntry, ProviderEntry};
use toml::Value;
use toml::map::Map;

View File

@ -6,8 +6,8 @@ use std::time::Instant;
use protocol::WorkerStatus;
use ticket::config::{
TICKET_CONFIG_RELATIVE_PATH, TicketConfig, TicketOrchestrationConfig,
WORKSPACE_SETTINGS_RELATIVE_PATH,
DEFAULT_TICKET_BACKEND_RELATIVE_PATH, TICKET_CONFIG_RELATIVE_PATH, TicketConfig,
TicketOrchestrationConfig,
};
use ticket::{
LocalTicketBackend, TicketBackend, TicketError, TicketEvent, TicketFilter, TicketIdOrSlug,
@ -567,41 +567,8 @@ pub(crate) fn decide_orchestrator_lifecycle(
}
pub(crate) fn ticket_config_availability(workspace_root: &Path) -> TicketConfigAvailability {
let settings_path = workspace_root.join(WORKSPACE_SETTINGS_RELATIVE_PATH);
match settings_path.symlink_metadata() {
Ok(metadata) if !metadata.is_file() => TicketConfigAvailability::Unusable(format!(
"{} exists but is not a regular file",
WORKSPACE_SETTINGS_RELATIVE_PATH
)),
Ok(_) => match std::fs::read_to_string(&settings_path) {
Ok(content) => {
match TicketConfig::workspace_settings_has_ticket_config(&settings_path, &content) {
Ok(true) => match TicketConfig::load_workspace(workspace_root) {
Ok(_) => TicketConfigAvailability::Usable,
Err(error) => TicketConfigAvailability::Unusable(error.to_string()),
},
Ok(false) => legacy_ticket_config_availability(workspace_root),
Err(error) => TicketConfigAvailability::Unusable(error.to_string()),
}
}
Err(error) => TicketConfigAvailability::Unusable(format!(
"could not read {}: {error}",
WORKSPACE_SETTINGS_RELATIVE_PATH
)),
},
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
legacy_ticket_config_availability(workspace_root)
}
Err(error) => TicketConfigAvailability::Unusable(format!(
"could not inspect {}: {error}",
WORKSPACE_SETTINGS_RELATIVE_PATH
)),
}
}
fn legacy_ticket_config_availability(workspace_root: &Path) -> TicketConfigAvailability {
let legacy_path = workspace_root.join(TICKET_CONFIG_RELATIVE_PATH);
match legacy_path.symlink_metadata() {
let config_path = workspace_root.join(TICKET_CONFIG_RELATIVE_PATH);
match config_path.symlink_metadata() {
Ok(metadata) if !metadata.is_file() => TicketConfigAvailability::Unusable(format!(
"{} exists but is not a regular file",
TICKET_CONFIG_RELATIVE_PATH
@ -641,14 +608,13 @@ fn load_orchestration_ticket_overlay(
}
match validate_orchestration_overlay_source(workspace_root, &layout) {
Ok(()) => {
load_orchestration_ticket_overlay_states(&layout.path).unwrap_or_else(|message| {
OrchestrationTicketOverlay {
load_orchestration_ticket_overlay_states(&layout.path, config.ticket_record_language())
.unwrap_or_else(|message| OrchestrationTicketOverlay {
states: BTreeMap::new(),
diagnostics: vec![bounded_panel_diagnostic(format!(
"Orchestration Ticket overlay unavailable: {message}"
))],
}
})
})
}
Err(message) => OrchestrationTicketOverlay {
states: BTreeMap::new(),
@ -673,25 +639,19 @@ fn orchestration_worktree_layout(
fn load_orchestration_ticket_overlay_states(
worktree_root: &Path,
record_language: Option<&str>,
) -> Result<OrchestrationTicketOverlay, String> {
let overlay_config = TicketConfig::load_workspace(worktree_root).map_err(|error| {
format!(
"orchestration worktree {} has unusable Ticket settings: {error}",
worktree_root.display()
)
})?;
let ticket_root = overlay_config.backend_root();
let ticket_root = worktree_root.join(DEFAULT_TICKET_BACKEND_RELATIVE_PATH);
if !ticket_root.is_dir() {
return Ok(OrchestrationTicketOverlay {
states: BTreeMap::new(),
diagnostics: vec![bounded_panel_diagnostic(format!(
"Orchestration worktree configured Ticket backend root {} is not a directory",
ticket_root.display()
"Orchestration worktree has no {} directory",
DEFAULT_TICKET_BACKEND_RELATIVE_PATH
))],
});
}
let backend = LocalTicketBackend::new(ticket_root.to_path_buf())
.with_record_language(overlay_config.ticket_record_language());
let backend = LocalTicketBackend::new(ticket_root).with_record_language(record_language);
let partial = backend
.list_partial(TicketFilter::all())
.map_err(|error| error.to_string())?;
@ -1803,17 +1763,11 @@ mod tests {
}
fn write_ticket_config(workspace_root: &Path) {
write_ticket_config_with_root(workspace_root, ".yoi/tickets");
}
fn write_ticket_config_with_root(workspace_root: &Path, root: &str) {
let config_dir = workspace_root.join(".yoi");
fs::create_dir_all(&config_dir).unwrap();
fs::write(
config_dir.join("workspace.toml"),
format!(
"[ticket]\n\n[ticket.backend]\nprovider = \"builtin:yoi_local\"\nroot = \"{root}\"\n"
),
config_dir.join("ticket.config.toml"),
"[backend]\nprovider = \"builtin:yoi_local\"\nroot = \".yoi/tickets\"\n",
)
.unwrap();
}
@ -1861,12 +1815,8 @@ mod tests {
}
fn copy_ticket_to_overlay(workspace_root: &Path, orchestration_root: &Path, id: &str) {
copy_ticket_to_overlay_root(workspace_root, &orchestration_root.join(".yoi/tickets"), id);
}
fn copy_ticket_to_overlay_root(workspace_root: &Path, overlay_root: &Path, id: &str) {
let local_ticket_dir = workspace_root.join(".yoi/tickets").join(id);
let overlay_ticket_dir = overlay_root.join(id);
let overlay_ticket_dir = orchestration_root.join(".yoi/tickets").join(id);
fs::create_dir_all(overlay_ticket_dir.parent().unwrap()).unwrap();
fs::create_dir_all(&overlay_ticket_dir).unwrap();
fs::copy(
@ -2037,40 +1987,6 @@ mod tests {
assert!(model.rows.iter().all(|row| row.title != "Overlay Only"));
}
#[test]
fn workspace_panel_orchestration_overlay_uses_configured_backend_root() {
let temp = TempDir::new().unwrap();
init_git_repo(temp.path());
write_ticket_config(temp.path());
let orchestration_root = add_orchestration_worktree(temp.path(), "orchestration");
write_ticket_config_with_root(&orchestration_root, "configured-overlay-tickets");
let backend = LocalTicketBackend::new(temp.path().join(".yoi/tickets"));
let id = create_ticket_with_id(&backend, "Overlay Custom Root", |input| {
input.workflow_state = Some(TicketWorkflowState::Queued);
});
let configured_overlay_root = orchestration_root.join("configured-overlay-tickets");
copy_ticket_to_overlay_root(temp.path(), &configured_overlay_root, &id);
let overlay_backend = LocalTicketBackend::new(&configured_overlay_root);
set_ticket_state(&overlay_backend, &id, TicketWorkflowState::InProgress);
let model = build_workspace_panel(temp.path(), &empty_pods());
assert!(model.header.diagnostics.is_empty());
let row = ticket_row_by_title(&model, "Overlay Custom Root");
assert_eq!(row.status, "q→prog");
assert_eq!(
row.ticket
.as_ref()
.unwrap()
.orchestration_overlay
.as_ref()
.unwrap()
.workflow_state,
TicketWorkflowState::InProgress
);
assert!(!orchestration_root.join(".yoi/tickets").exists());
}
#[test]
fn workspace_panel_displays_queued_plus_orchestration_inprogress_without_mutating_local_ticket()
{
@ -2317,8 +2233,8 @@ mod tests {
let config_dir = temp.path().join(".yoi");
fs::create_dir_all(&config_dir).unwrap();
fs::write(
config_dir.join("workspace.toml"),
"[ticket]\n\n[ticket.backend]\nprovider = \"unknown:provider\"\nroot = \".yoi/tickets\"\n",
config_dir.join("ticket.config.toml"),
"[backend]\nprovider = \"unknown:provider\"\nroot = \".yoi/tickets\"\n",
)
.unwrap();
@ -2762,11 +2678,11 @@ mod tests {
}
#[test]
fn existing_non_file_workspace_settings_is_unusable_not_absent() {
fn existing_non_file_ticket_config_is_unusable_not_absent() {
let temp = TempDir::new().unwrap();
let config_parent = temp.path().join(".yoi");
fs::create_dir_all(&config_parent).unwrap();
fs::create_dir(config_parent.join("workspace.toml")).unwrap();
fs::create_dir(config_parent.join("ticket.config.toml")).unwrap();
assert!(matches!(
ticket_config_availability(temp.path()),

View File

@ -167,12 +167,6 @@ pub struct WorkingDirectoryStatus {
pub summary: WorkingDirectorySummary,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct WorkspaceApiRef {
pub workspace_id: String,
pub base_url: String,
}
/// Canonical Runtime Worker creation request.
///
/// Browser/product launch semantics are resolved by a backend before this
@ -194,8 +188,6 @@ pub struct CreateWorkerRequest {
pub working_directory_request: Option<WorkingDirectoryRequest>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub working_directory: Option<WorkingDirectoryClaim>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub workspace_api: Option<WorkspaceApiRef>,
}
/// Worker lifecycle status for the in-memory embedded runtime.

View File

@ -393,20 +393,6 @@ pub trait WorkerExecutionBackend: Send + Sync + 'static {
"execution backend does not support cancelling workers",
)
}
#[cfg(feature = "ws-server")]
fn worker_snapshot(&self, _handle: &WorkerExecutionHandle) -> Option<protocol::Event> {
None
}
fn worker_completions(
&self,
_handle: &WorkerExecutionHandle,
_kind: protocol::CompletionKind,
_prefix: &str,
) -> Vec<protocol::CompletionEntry> {
Vec::new()
}
}
#[derive(Clone)]
@ -486,23 +472,6 @@ impl WorkerExecutionBackendRef {
pub(crate) fn cancel_worker(&self, handle: &WorkerExecutionHandle) -> WorkerExecutionResult {
self.backend.cancel_worker(handle)
}
#[cfg(feature = "ws-server")]
pub(crate) fn worker_snapshot(
&self,
handle: &WorkerExecutionHandle,
) -> Option<protocol::Event> {
self.backend.worker_snapshot(handle)
}
pub(crate) fn worker_completions(
&self,
handle: &WorkerExecutionHandle,
kind: protocol::CompletionKind,
prefix: &str,
) -> Vec<protocol::CompletionEntry> {
self.backend.worker_completions(handle, kind, prefix)
}
}
impl fmt::Debug for WorkerExecutionBackendRef {

View File

@ -146,10 +146,6 @@ pub fn runtime_http_router(runtime: Runtime, local_token: Option<String>) -> Rou
get(get_worker).delete(delete_worker),
)
.route("/v1/workers/{worker_id}/input", post(send_worker_input))
.route(
"/v1/workers/{worker_id}/completions",
post(worker_completions),
)
.route("/v1/workers/{worker_id}/stop", post(stop_worker))
.route("/v1/workers/{worker_id}/cancel", post(cancel_worker));
@ -232,20 +228,6 @@ pub struct RuntimeHttpWorkerInputResponse {
pub ack: WorkerInteractionAck,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeHttpWorkerCompletionsRequest {
pub kind: protocol::CompletionKind,
#[serde(default)]
pub prefix: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeHttpWorkerCompletionsResponse {
pub kind: protocol::CompletionKind,
pub prefix: String,
pub entries: Vec<protocol::CompletionEntry>,
}
/// Worker lifecycle request body used by stop/cancel endpoints.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeHttpWorkerLifecycleRequest {
@ -696,24 +678,6 @@ async fn send_worker_input(
Ok(Json(RuntimeHttpWorkerInputResponse { ack }))
}
async fn worker_completions(
State(state): State<RuntimeHttpState>,
Path(worker_id): Path<String>,
body: Result<Json<RuntimeHttpWorkerCompletionsRequest>, JsonRejection>,
) -> RestResult<RuntimeHttpWorkerCompletionsResponse> {
let worker_ref = worker_ref_for(&state.runtime, worker_id)?;
let Json(request) = body.map_err(RuntimeHttpRestError::json_rejection)?;
let entries = state
.runtime
.worker_completions(&worker_ref, request.kind, &request.prefix)
.map_err(RuntimeHttpRestError::runtime)?;
Ok(Json(RuntimeHttpWorkerCompletionsResponse {
kind: request.kind,
prefix: request.prefix,
entries,
}))
}
async fn stop_worker(
State(state): State<RuntimeHttpState>,
Path(worker_id): Path<String>,
@ -982,7 +946,6 @@ mod tests {
initial_input: None,
working_directory_request: None,
working_directory: None,
workspace_api: None,
}
}
@ -1294,7 +1257,6 @@ mod ws_tests {
initial_input: None,
working_directory_request: None,
working_directory: None,
workspace_api: None,
}
}

Some files were not shown because too many files have changed in this diff Show More