docs: split CLAUDE.md tasks into parallel todo tracks
Extract all unimplemented design plans and tasks from CLAUDE.md into 5 independent todo files, each targeting a separate branch for parallel implementation: - TASKS-1: Memory & Performance Optimization - TASKS-2: Subagent Orchestration (Container Model) - TASKS-3: Session DAG (SQLite Store) - TASKS-4: Mini App & Static Serving - TASKS-5: Heartbeat Worktree Management CLAUDE.md now retains only implemented references, coding guidelines, and a summary table linking to each todo file. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1122ccde71
commit
e6de7d0d62
6 changed files with 683 additions and 844 deletions
859
CLAUDE.md
859
CLAUDE.md
|
|
@ -19,471 +19,14 @@ Lint: `golangci-lint run`
|
|||
- **Interview tool filtering**: `interviewAllowedTools` in `pkg/agent/loop.go` is the single source of truth for tools available during interview/review phases. Both `filterInterviewTools` (strips definitions before LLM call) and `isToolAllowedDuringInterview` (argument-level gating) reference this map.
|
||||
- **History clear**: `/plan start clear` wipes session history and summary on transition to executing. The Mini App review UI offers two sliders: standard approve and approve-with-clear.
|
||||
|
||||
## Mini App — Static File Serving
|
||||
## Subagent Orchestration (実装済み部分)
|
||||
|
||||
現状は `pkg/miniapp/static/` 以下のファイルを `//go:embed` でバイナリに焼いて個別ルートで配信している。
|
||||
- **Startup flag**: `--orchestration` で on/off。`SubagentsConfig.Enabled` で gate。
|
||||
- **Conductor identity**: orchestration 有効時に conductor identity + spawn/subagent guidance を system prompt へ注入。
|
||||
- **Sandbox/Spawn**: `pkg/tools/sandbox.go`, `pkg/tools/spawn.go` 実装済み。
|
||||
- **AgentReporter**: `orch.AgentReporter` / `orch.Noop` / `orch.Broadcaster` で統一。main/heartbeat/subagent 全セッションが同一 Broadcaster に発火。Mini App は `agentLoop.GetOrchBroadcaster()` → `handler.SetOrchBroadcaster()` で受信。
|
||||
|
||||
```
|
||||
pkg/miniapp/static/
|
||||
index.html ← すべての JS/CSS をインライン
|
||||
map.js ← オーケストレーションルーム描画
|
||||
```
|
||||
|
||||
Go 側: `//go:embed static/index.html static/map.js` + `/miniapp/map.js` ルート。
|
||||
|
||||
### バンドラ導入の検討事項
|
||||
|
||||
- ビルドステップ(Vite / esbuild 等)を挟む場合、成果物ディレクトリ(`static/dist/` 等)を embed する形になる
|
||||
- Go 側は `//go:embed static` でディレクトリごと embed し、`http.FS` で一括配信すれば個別ルートが不要になる
|
||||
- `serveMapJS` など個別ルートは汎用 static ファイルサーバーに統合できる
|
||||
- `serveIndex` での `ORCH_ENABLED` 注入は、テンプレートエンジンまたはビルド時の環境変数注入に移行する必要がある
|
||||
|
||||
### 現時点でやっておける布石(任意)
|
||||
|
||||
- `/miniapp/` 以下を `http.FileServer(http.FS(staticFS))` で一括配信するよう `serveStatic` ハンドラを汎用化する
|
||||
- これにより `map.js`・将来の `assets/*.js` もルート追加なしに自動配信される
|
||||
|
||||
## Known Gaps
|
||||
|
||||
- **Mini App log viewer has no frontend tests**: `renderLogs()` in `pkg/miniapp/static/index.html` is inline vanilla JS with no unit/E2E test coverage. Backend (Go) tests cover `RecentLogs`, `SanitizeFields`, and JSON serialization, but nothing verifies the JS rendering. This allowed the Fields display bug (fields sent but not rendered) to ship undetected.
|
||||
- **No human intervention for heartbeat worktrees**: Heartbeat sessions create git worktrees (`.worktrees/heartbeat-YYYYMMDD/`) but there is no CLI or Mini App command to list, inspect, or manually dispose them. Need a `/plan worktrees` command (or similar) that shows active worktrees with branch/commit info and allows manual merge/dispose. `PruneOrphaned` on startup only removes directories without auto-committing first, so uncommitted changes in orphaned worktrees are silently lost.
|
||||
|
||||
---
|
||||
|
||||
## Subagent Orchestration Design
|
||||
|
||||
> Designed 2026-02-25 on branch `sub-agent-technical-breakdown`.
|
||||
|
||||
### なぜオーケストレーションか
|
||||
|
||||
単純な指示から可能性の木を広げることが目的。conductor は一人でやり遂げるのではなく、探索・深化・fork をサブエージェントに委ねながら大局観を保つ。
|
||||
|
||||
```
|
||||
without orchestration:
|
||||
human → conductor → (全部自分でやる) → result
|
||||
常にボトルネック、逐次処理
|
||||
|
||||
with orchestration:
|
||||
human → conductor ─┬─ scout A ─┐
|
||||
├─ scout B ─┼─ synthesize → deeper insight
|
||||
└─ scout C ─┘
|
||||
conductor は次を考えながら並走
|
||||
```
|
||||
|
||||
**3つの核心原則:**
|
||||
|
||||
1. **Fork** — 同じ問いに複数の切り口で同時探索。sequential queue ではなく tree の展開。
|
||||
2. **管理職の原則** — conductor は subagent の完了を待たない。spawn したら即座に次を設計する。人員を遊ばせないことがポイント。
|
||||
3. **会話の fork** — main thread (conductor ↔ human) は高レベル・戦略的に保つ。subagent への細かい指示出しは branch thread で行い、main thread を汚染しない。subagent の進捗はサマリーだけ main thread に上げる。
|
||||
|
||||
**spawn がデフォルト、subagent は例外:**
|
||||
```
|
||||
spawn = conductor が次を考え続けられる (正しい姿)
|
||||
subagent = conductor が止まる (結果が絶対必要な時だけ)
|
||||
```
|
||||
|
||||
### Architecture Overview
|
||||
|
||||
```
|
||||
Conductor goroutine
|
||||
│ ContainerRequest (task, preset, environment)
|
||||
▼
|
||||
Container goroutine: provision → run → finalize
|
||||
│ ContainerMessage (question / result / status)
|
||||
▼
|
||||
Conductor goroutine
|
||||
│ answer (question への回答)
|
||||
▼ (わからなければ human に escalate)
|
||||
Container goroutine (再開)
|
||||
```
|
||||
|
||||
**escalation chain:**
|
||||
```
|
||||
subagent (clarifying) → question → conductor
|
||||
→ conductor が答えられる: inCh に回答
|
||||
→ conductor もわからない: message tool で human に投げ、回答を転送
|
||||
```
|
||||
|
||||
### SubagentContainer
|
||||
|
||||
goroutine と channel でライフサイクルを表現。goroutine がブロックしている場所が現在の状態。
|
||||
|
||||
```go
|
||||
type ContainerMessage struct {
|
||||
Type string // "question" | "result" | "status"
|
||||
Content string
|
||||
}
|
||||
|
||||
type SubagentContainer struct {
|
||||
inCh chan string // conductor → subagent (回答)
|
||||
outCh chan ContainerMessage // subagent → conductor (質問・結果・進捗)
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
```
|
||||
|
||||
spawn (async) は outCh を返して即リターン。subagent (sync) はその場で result を待つ。
|
||||
|
||||
**tasks map 問題の解消:** goroutine 終了時に `defer orchestrator.active.Delete(id)` + `defer close(outCh)` で自動 GC。
|
||||
|
||||
### SubagentEnvironment (Context Injection)
|
||||
|
||||
conductor は subagent に必要なコンテキストを明示的に渡す。MEMORY.md からの自動注入で冗長な手動記述を排除。
|
||||
|
||||
```go
|
||||
type SubagentEnvironment struct {
|
||||
// 自動注入 (harness が埋める)
|
||||
Workspace string // workspace パス
|
||||
WorktreeDir string // write先、workDir として透過的に機能
|
||||
|
||||
// MEMORY.md から自動抽出 (inject_plan_context: true の場合)
|
||||
PlanTask string // > Task: の内容
|
||||
PlanContext string // ## Context セクション
|
||||
Commands string // ## Commands セクション (build/test/lint)
|
||||
CurrentPhase string // 対象 Phase の内容
|
||||
|
||||
// conductor が明示的に追加
|
||||
Background string // 追加の背景・意図
|
||||
Constraints string // 制約
|
||||
ContextFiles []string // 参照すべきファイルリスト
|
||||
}
|
||||
```
|
||||
|
||||
spawn パラメータ例:
|
||||
```json
|
||||
{
|
||||
"task": "Phase 2 Step 1: implement the rate limiter",
|
||||
"preset": "coder",
|
||||
"context_files": ["pkg/ratelimit/ratelimit.go"],
|
||||
"inject_plan_context": true
|
||||
}
|
||||
```
|
||||
|
||||
### SandboxConfig
|
||||
|
||||
ToolRegistry.Execute() の入口で一括 enforcement。subagent は普通に tool call するつもりで透過的に sandboxed になる。
|
||||
|
||||
```go
|
||||
type SandboxConfig struct {
|
||||
Preset string
|
||||
WriteRoot string // write 系ツールのパス制限
|
||||
AllowedTools map[string]bool
|
||||
ExecPolicy *ExecPolicy // nil = exec 不可
|
||||
SpawnablePresets []string // nil = spawn 不可
|
||||
}
|
||||
|
||||
type ExecPolicy struct {
|
||||
AllowPattern string // 先頭一致 regex; マッチしたコマンドだけ実行可
|
||||
}
|
||||
```
|
||||
|
||||
**透過的隔離:** workDir = worktreeDir として設定することで、AI は自分が隔離されていることに気づかずに振る舞う。picoclaw 側で CoW 的にファイルを引き渡せる。
|
||||
|
||||
### Presets (5種)
|
||||
|
||||
| preset | 性格 | write | exec | search | spawn |
|
||||
|---|---|---|---|---|---|
|
||||
| `scout` | Exploratory | ✗ | ✗ | ✓ | ✗ |
|
||||
| `analyst` | Exploratory | ✗ | go test/vet, git log/diff, grep | ✓ | ✗ |
|
||||
| `coder` | Deliberate | ✓ sandbox | test/lint/fmt 系 | ✓ | ✗ |
|
||||
| `worker` | Deliberate | ✓ sandbox | build/package manager 系 | ✓ | ✗ |
|
||||
| `coordinator` | Deliberate | ✓ sandbox | go/pnpm/bun/curl 系 | ✓ | scout〜worker のみ |
|
||||
|
||||
**性格の分類:**
|
||||
- **Exploratory** (scout/analyst): open-ended、見てきて報告。clarifying フェーズなし。
|
||||
- **Deliberate** (coder/worker/coordinator): 成果物を作る。目標があいまいだと失敗する。clarifying フェーズあり。
|
||||
|
||||
**境界:**
|
||||
- `coder` = 書いて自分で検証できる (package 追加・deploy 不可)
|
||||
- `worker` = インフラも含めてやりきる (package install, CI pipeline 等)
|
||||
- `coordinator` = coordinator を spawn できない (深さ自然制限)
|
||||
|
||||
**npm は全 preset で禁止:** git worktree に node_modules が作られると大量ファイルが生じるため。pnpm は symbolic link で済む、bun も同様。
|
||||
|
||||
**websearch/webfetch は全 preset で許可:** read-only・非破壊のため制限不要。
|
||||
|
||||
#### exec allowlist regex
|
||||
|
||||
```go
|
||||
var presetExecPatterns = map[string]string{
|
||||
"scout": ``,
|
||||
"analyst": `^(go\s+(test|vet)|git\s+(log|diff|status)|curl|wget|grep|find)\b`,
|
||||
"coder": `^(` +
|
||||
`go\s+(test|vet|fmt)|gofmt|goimports|golangci-lint|` +
|
||||
`prettier|eslint|` +
|
||||
`black|ruff|` +
|
||||
`cargo\s+(test|fmt|clippy)|` +
|
||||
`pnpm\s+(test|run\s+(test|lint|format))|` +
|
||||
`bun\s+(test|run\s+(test|lint|format))|` +
|
||||
`uv\s+run\s+` +
|
||||
`)\b`,
|
||||
"worker": `^(` +
|
||||
`go\s+|` +
|
||||
`pnpm\s+(install|add|run|test|build)|` +
|
||||
`bun\s+(install|add|run|test|build)|` +
|
||||
`uv\s+(run|sync|add|pip\s+install)|` +
|
||||
`pip\s+install|` +
|
||||
`cargo\s+` +
|
||||
`)\b`,
|
||||
"coordinator": `^(go\s+|pnpm\s+|bun\s+|curl|wget)\b`,
|
||||
}
|
||||
```
|
||||
|
||||
### Subagent Plan Mode
|
||||
|
||||
Deliberate な preset (coder/worker/coordinator) は in-memory のミニ plan mode を持つ。MEMORY.md には一切触れない (ファイル参照・編集を避けるため)。
|
||||
|
||||
```go
|
||||
type SubagentPlanState int
|
||||
|
||||
const (
|
||||
PlanStateNone SubagentPlanState = iota // Exploratory preset
|
||||
PlanStateClarifying // 目的・制約を確認中
|
||||
PlanStateReview // conductor の承認待ち
|
||||
PlanStateExecuting // 実行中
|
||||
)
|
||||
|
||||
type SubagentPlan struct {
|
||||
State SubagentPlanState
|
||||
Goal string // clarifying で合意した目的
|
||||
Approach []string // proposed なステップ
|
||||
QA []QAItem // 質問・回答の履歴
|
||||
mu sync.Mutex
|
||||
}
|
||||
```
|
||||
|
||||
SubagentContainer がフィールドとして保持。goroutine 終了とともに消える。
|
||||
|
||||
**Deliberate preset の system prompt:**
|
||||
```
|
||||
You are in clarifying mode. Before executing, confirm with the conductor:
|
||||
1. What is the exact goal?
|
||||
2. What are the constraints and acceptance criteria?
|
||||
3. Are there relevant files I should know about?
|
||||
Use the `message` tool to ask questions.
|
||||
When you have clear answers, propose your approach (steps) for review.
|
||||
Do NOT start executing until the conductor approves.
|
||||
```
|
||||
|
||||
**Exploratory preset の system prompt:**
|
||||
```
|
||||
Explore and return findings. Use your best judgment when encountering ambiguity.
|
||||
```
|
||||
|
||||
**fractal 構造:**
|
||||
```
|
||||
human
|
||||
↕ plan mode (MEMORY.md, file-based, 永続)
|
||||
conductor
|
||||
↕ subagent plan mode (in-memory, 揮発)
|
||||
subagent (deliberate)
|
||||
```
|
||||
|
||||
### MEMORY.md Orchestration Section
|
||||
|
||||
executing 中に conductor が自由に書き込める専用エリア。システムはパースしない。
|
||||
|
||||
```markdown
|
||||
## Orchestration
|
||||
|
||||
### Delegated
|
||||
<!-- subagent に委譲したタスクの記録 (spawn 直後に書く) -->
|
||||
- coder-1 (coder): rate limiter 実装 → Phase 2 Step 1
|
||||
- scout-1 (scout): pkg/auth の構造調査
|
||||
|
||||
### Findings
|
||||
<!-- subagent の結果から蓄積した知見 (結果受信後に書く) -->
|
||||
- pkg/auth は middleware パターン、入口は middleware.go (scout-1)
|
||||
- セッションストアは存在しない、JWT が有効 (scout-2)
|
||||
|
||||
### Decisions
|
||||
<!-- fork の意思決定ログ (方向選択時に書く) -->
|
||||
- auth: OAuth2 より JWT を選択 (外部依存なし、scout-2 推奨)
|
||||
```
|
||||
|
||||
conductor の guidance に追記:
|
||||
```
|
||||
After spawning a subagent, record the assignment in ## Orchestration > Delegated.
|
||||
When a subagent reports back, move key findings to ## Orchestration > Findings.
|
||||
When you choose one direction over another, log the rationale in ## Orchestration > Decisions.
|
||||
```
|
||||
|
||||
### Conductor Identity (System Prompt)
|
||||
|
||||
`pkg/agent/context.go` の `getIdentity()` に追加予定:
|
||||
|
||||
```
|
||||
You are picoclaw, a conductor AI agent. Your role is to orchestrate:
|
||||
break work into tasks, delegate them to subagents, and synthesize results —
|
||||
rather than doing everything inline yourself.
|
||||
|
||||
## Orchestration
|
||||
|
||||
You are the conductor, not the performer. Prefer delegation over doing everything inline.
|
||||
|
||||
Use `spawn` (non-blocking) when:
|
||||
- Tasks can run in parallel or in the background
|
||||
- Multiple independent tasks can run simultaneously (spawn each one)
|
||||
- You don't need the result to decide the next step
|
||||
- The operation is long-running (builds, fetches, analysis, file processing)
|
||||
|
||||
Use `subagent` (blocking) when:
|
||||
- You need the result before you can continue
|
||||
- Correctness of next steps depends on the outcome
|
||||
|
||||
Do inline only when:
|
||||
- It's a single fast tool call (read a file, quick search)
|
||||
- Delegation overhead clearly outweighs the benefit
|
||||
|
||||
Default bias: if a task involves more than 2-3 tool calls or can run
|
||||
independently, delegate it. When you spawn, immediately plan what comes next —
|
||||
blocking means you've stopped thinking.
|
||||
|
||||
Fork aggressively: explore multiple directions simultaneously.
|
||||
After spawning a subagent, record the assignment in ## Orchestration > Delegated.
|
||||
When results come back, synthesize and decide the next fork.
|
||||
```
|
||||
|
||||
### Startup Flag
|
||||
|
||||
テスト用途で起動時にオーケストレーション機能を on/off できるようにする。
|
||||
|
||||
**変更箇所:**
|
||||
1. `pkg/config/config.go` — `SubagentsConfig` に `Enabled bool` を追加
|
||||
2. `cmd/picoclaw/cmd_agent.go` — `--orchestration` フラグを追加 (default: false for now)
|
||||
3. `pkg/agent/loop.go` — `registerSharedTools()` で spawn tool 登録を `Enabled` で gate
|
||||
|
||||
```go
|
||||
// pkg/config/config.go
|
||||
type SubagentsConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
AllowAgents []string `json:"allow_agents,omitempty"`
|
||||
Model *AgentModelConfig `json:"model,omitempty"`
|
||||
}
|
||||
|
||||
// cmd/picoclaw/cmd_agent.go
|
||||
case "--orchestration":
|
||||
cfg.Agents.Defaults.Subagents.Enabled = true // or toggle
|
||||
```
|
||||
|
||||
### Implementation Files (予定)
|
||||
|
||||
```
|
||||
pkg/tools/
|
||||
container.go — SubagentContainer, ContainerRequest, ContainerMessage,
|
||||
SubagentEnvironment, SubagentPlan
|
||||
sandbox.go — SandboxConfig, ExecPolicy, preset 定義
|
||||
orchestrator.go — Orchestrator (SubagentManager を置き換え)
|
||||
spawn.go — preset / inject_plan_context パラメータ追加
|
||||
pkg/agent/
|
||||
context.go — conductor identity + orchestration guidance 追加
|
||||
```
|
||||
|
||||
### AgentReporter 抽象化 (実装済み 2026-02-25)
|
||||
|
||||
> branch `sub-agent-technical-breakdown`
|
||||
|
||||
`Broadcaster` を `SubagentManager` 内部で生成する密結合を解消し、
|
||||
`orch.AgentReporter` インターフェースを中心に置くリファクタリングを実施。
|
||||
|
||||
#### オーナーシップ
|
||||
|
||||
```
|
||||
AgentLoop
|
||||
├─ owns: *orch.Broadcaster (orchBroadcaster — nil when disabled)
|
||||
└─ holds: orch.AgentReporter (orchReporter = Broadcaster or Noop)
|
||||
├─ passes to → SubagentManager.reporter
|
||||
│ └─ passes to → ToolLoopConfig.Reporter
|
||||
└─ calls directly for main/heartbeat sessions
|
||||
├─ runAgentLoop: ReportSpawn / ReportGC
|
||||
└─ runLLMIteration: ReportStateChange
|
||||
|
||||
cmd_gateway.go
|
||||
└─ agentLoop.GetOrchBroadcaster() → handler.SetOrchBroadcaster()
|
||||
|
||||
miniapp.Handler
|
||||
└─ borrows *orch.Broadcaster for Subscribe/Snapshot (WS 配信)
|
||||
```
|
||||
|
||||
#### インターフェース (`pkg/orch/reporter.go`)
|
||||
|
||||
```go
|
||||
type AgentReporter interface {
|
||||
ReportSpawn(id, label, task string)
|
||||
ReportStateChange(id, state, tool string)
|
||||
ReportConversation(from, to, text string)
|
||||
ReportGC(id, reason string)
|
||||
}
|
||||
var Noop AgentReporter = &noopReporter{} // nil-free; 全メソッドが no-op
|
||||
```
|
||||
|
||||
`Broadcaster` は `AgentReporter` を満たす (`ReportSpawn` 等が `Publish` のラッパー)。
|
||||
|
||||
#### Noop パターン
|
||||
|
||||
```
|
||||
--orchestration なし: orchReporter = orch.Noop → 全 Report* が空振り (panic なし)
|
||||
--orchestration あり: orchReporter = *Broadcaster → WS 配信
|
||||
```
|
||||
|
||||
呼び出し側は `if reporter != nil` チェック不要。
|
||||
|
||||
#### イベント発火の責任分担
|
||||
|
||||
| 発火元 | イベント | 経由 |
|
||||
|--------|---------|------|
|
||||
| `runAgentLoop` | `ReportSpawn` / `ReportGC` | `al.reporter()` |
|
||||
| `runLLMIteration` | `ReportStateChange("waiting"/"toolcall")` | `al.reporter()` |
|
||||
| `SubagentManager.Spawn` | `ReportSpawn` | `sm.reporter` |
|
||||
| `SubagentManager.runTask` | `ReportConversation` / `ReportGC` | `sm.reporter` |
|
||||
| `RunToolLoop` | `ReportStateChange` | `config.Reporter` |
|
||||
|
||||
main / heartbeat / subagent の全セッションが同一 Broadcaster に発火するため、
|
||||
canvas には全エージェントが統一して表示される。
|
||||
|
||||
#### 変更ファイル
|
||||
|
||||
- `pkg/orch/reporter.go` — **新規** インターフェース + Noop
|
||||
- `pkg/orch/broadcaster.go` — `ReportSpawn/StateChange/Conversation/GC` 追加
|
||||
- `pkg/tools/toolloop.go` — `OnStateChange func` → `Reporter AgentReporter + AgentID`
|
||||
- `pkg/tools/subagent.go` — constructor に `reporter` 受け取り、内部 broadcaster 廃止、`GetBroadcaster()` 削除
|
||||
- `pkg/agent/loop.go` — `orchBroadcaster`/`orchReporter` フィールド追加、`SetOrchReporter`/`GetOrchBroadcaster` 追加、`registerSharedTools` シグネチャに `al *AgentLoop` 追加
|
||||
- `cmd/picoclaw/cmd_gateway.go` — `GetOrchBroadcaster()` → `handler.SetOrchBroadcaster()`
|
||||
|
||||
---
|
||||
|
||||
## Memory Optimization Notes
|
||||
|
||||
> Reviewed 2026-02-24 on branch `memory-optimization-review`.
|
||||
|
||||
### 設計レベルの根本原因
|
||||
|
||||
#### D-1. MemoryStore が「ファイル = 正」でパース済み表現をキャッシュできない
|
||||
|
||||
`GetMemoryContext()` 1回で `ReadLongTerm()` が5回以上呼ばれる連鎖。MEMORY.md を外部エディタが直接編集できる設計上、インメモリキャッシュを自然に導入できない。
|
||||
|
||||
対策: (a) content パススルー方式 — 高レベルメソッドだけが1回 ReadLongTerm() を呼び、content を private ヘルパーに渡す。(b) `*ParsedPlan` 常駐 — RAM が潤沢なので MemoryStore にパース済み構造体を持たせる。edit_file 後に `InvalidateCache()` を呼ぶ。
|
||||
|
||||
#### D-2. `FunctionCall.Arguments` が JSON 文字列のままドメイン型に
|
||||
|
||||
ストリーミングループ内の重複 Unmarshal の根本原因。`ToolCall.Arguments map[string]any` のパース済みフィールドも存在するが中途半端に共存している。
|
||||
|
||||
#### D-3. `ToolFunctionDefinition.Parameters` が `map[string]any`
|
||||
|
||||
プロバイダーへ送るたびに Marshal が必要。`json.RawMessage` にすれば一度の marshal で済む。
|
||||
|
||||
#### D-4. 検索プロバイダーに共通フォーマット抽象がない
|
||||
|
||||
`[]string + strings.Join` パターンが3箇所に複製。`Search()` 戻り値を `string` でなく構造体にすれば1箇所で済む。
|
||||
|
||||
#### D-5. `Session.Messages` が可変スライスで全コピーが必要
|
||||
|
||||
`GetHistory()` / `Save()` での防衛的コピーは意図的設計。COW または append-only immutable 構造で解消できる。
|
||||
|
||||
#### D-6. `MemoryStore` のメソッド境界が「ファイル操作単位」
|
||||
|
||||
呼び出し側は複数の値が必要でも複数回呼ぶしかない。D-1 の解決策 (ParsedPlan 常駐) と合わせて解消。
|
||||
|
||||
### コードの匂い — チェックリスト
|
||||
## コードの匂い — チェックリスト
|
||||
|
||||
新しいコードを書くとき・レビューするときの確認事項:
|
||||
|
||||
|
|
@ -495,388 +38,16 @@ canvas には全エージェントが統一して表示される。
|
|||
6. **`var x []T` から始まる容量なし append** → ソース長が既知なら `make([]T, 0, n)`
|
||||
7. **`[]rune(s)` 変換前に長さチェックなし** → `len(s) <= max` で ASCII fast path を先に
|
||||
|
||||
### ストレージ保護設計 (microSD 寿命)
|
||||
|
||||
| データ | 現状 | 推奨戦略 | 削減率 |
|
||||
|---|---|---|---|
|
||||
| `sessions/*.json` | メッセージ毎書き込み | write-behind (dirty flag + 5分タイマー) | 80% |
|
||||
| `state/stats.json` | LLM呼び出し毎 | 定期フラッシュのみ (5分) | 98% |
|
||||
| `memory/MEMORY.md` | 即時 (変えない) | ターンスコープキャッシュ (読み取りのみ最適化) | — |
|
||||
|
||||
**実装ポイント:**
|
||||
- `SessionManager` に `dirtyKeys map[string]bool` + バックグラウンドフラッシャー goroutine
|
||||
- `stats.Tracker` に `Close()` メソッド追加 (タイマー停止 + 最終 save)
|
||||
- SIGTERM/SIGINT でシャットダウンフック必須
|
||||
- MEMORY.md の edit_file 書き込み後にターンキャッシュを無効化 (`InvalidateCache()`)
|
||||
|
||||
---
|
||||
|
||||
## Session Management (Future)
|
||||
## 未実装タスク
|
||||
|
||||
現状の設計は「正確性」は成熟しているが「ライフサイクル」が欠落している。
|
||||
以下の `todo/` ファイルに分割。各ファイルは互いに依存関係がなく、別ブランチで並列実装可能。
|
||||
|
||||
**近期:**
|
||||
- `SessionManager.Delete(key)` + TTL エビクション
|
||||
- `sessionLocks sync.Map` (loop.go) の GC
|
||||
- 起動時の `loadSessions()` を遅延ロード化
|
||||
|
||||
**中期:**
|
||||
- チェックポイント / ロールバック (`Session.Messages` を append-only immutable に)
|
||||
- 名前付きセッション (`/new-session`, `/switch-session`, `/list-sessions`)
|
||||
|
||||
**長期:**
|
||||
- `Session.ParentKey` でサブエージェントセッションをグラフ化
|
||||
- クロスセッション検索 (MEMORY.md の補完として)
|
||||
|
||||
設計の哲学: `Session.Messages` (履歴中心) と `MEMORY.md` (知識中心) が現状共存している。どちらを主軸にするかで発展方向が変わる。
|
||||
|
||||
---
|
||||
|
||||
## Session DAG Migration Plan (Branch/Merge for Subagent Orchestration)
|
||||
|
||||
> Drafted 2026-03-04. Goal: make branch/fork/merge first-class in session history,
|
||||
> instead of injecting subagent completion as ad-hoc system text.
|
||||
|
||||
### 背景
|
||||
|
||||
thread の有無に依存せず、subagent 運用の本質は「会話コンテキストの分岐と合流」。
|
||||
現行の `sessionKey -> linear []Message` は分離には強いが、
|
||||
「どの branch から何を merge したか」を構造化して保持できない。
|
||||
|
||||
### 目標
|
||||
|
||||
1. `sessionKey` の value を head 参照 (`SessionRef`) に変更し、履歴実体を DAG ノード化する
|
||||
2. merge を「1回分の記憶」として会話ログに追加(git merge commit 相当)
|
||||
3. チャネル/プロバイダ差分は capability interface で吸収する
|
||||
4. 既存線形セッションとの後方互換を維持しながら段階移行する
|
||||
|
||||
### 提案データモデル(v1)
|
||||
|
||||
```go
|
||||
type SessionRef struct {
|
||||
HeadNodeID string
|
||||
GraphID string // optional shard namespace
|
||||
Version uint64 // optimistic concurrency (CAS)
|
||||
}
|
||||
|
||||
type ConversationNode struct {
|
||||
NodeID string
|
||||
SessionKey string
|
||||
Kind NodeKind // Message | Merge
|
||||
Parents []string // 1 parent = append, 2 parents = merge
|
||||
Events []LogEvent // one-turn memory bundle
|
||||
SummaryDelta string
|
||||
CreatedAt time.Time
|
||||
Author string
|
||||
Meta map[string]string
|
||||
}
|
||||
|
||||
type LogEvent struct {
|
||||
Role string // user|assistant|tool|system
|
||||
Content string
|
||||
ToolCallID string
|
||||
ToolCalls []providers.ToolCall
|
||||
Usage *providers.UsageInfo
|
||||
Meta map[string]string
|
||||
}
|
||||
```
|
||||
|
||||
### 提案インターフェース(v1)
|
||||
|
||||
```go
|
||||
type SessionGraphStore interface {
|
||||
Resolve(sessionKey string) (SessionRef, bool, error)
|
||||
CompareAndSwapHead(sessionKey string, expectVersion uint64, next SessionRef) error
|
||||
|
||||
AppendNode(node ConversationNode) error
|
||||
GetNode(nodeID string) (ConversationNode, bool, error)
|
||||
|
||||
ForkSession(parentKey, childKey string, atHead bool) error
|
||||
MergeSession(baseKey, branchKey string, policy MergePolicy) (mergeNodeID string, err error)
|
||||
}
|
||||
|
||||
type MergePolicy interface {
|
||||
BuildMergeEvents(basePath []ConversationNode, branchPath []ConversationNode) ([]LogEvent, error)
|
||||
}
|
||||
```
|
||||
|
||||
### Merge 方針
|
||||
|
||||
- subagent 結果は `processSystemMessage` で plain text 注入するのではなく、
|
||||
`NodeKind=Merge` のノードを base セッションに append する。
|
||||
- `Events` は「1回分の記憶」として扱う(例: merge summary, adopted decisions, artifacts)。
|
||||
- merge conflict は `MergePolicy` で deterministic ルール優先、必要に応じて LLM 補助。
|
||||
|
||||
### Capability 型の整理方針
|
||||
|
||||
既存の interface ベース設計を継続し、分岐/合流にも適用する:
|
||||
|
||||
- Channel capability (thread, draft, edit, status update)
|
||||
- Provider capability (streaming, structured output, tool-call fidelity)
|
||||
|
||||
capability は type assertion で解決し、非対応時は deterministic fallback。
|
||||
|
||||
### 互換移行ステップ
|
||||
|
||||
1. 新規 `session/graph` 実装を追加(in-memory + file persistence)
|
||||
2. adapter で既存 `GetHistory/SetHistory/AddMessage` を DAG replay に接続
|
||||
3. 新規 fork/merge API を miniapp/command 層へ段階公開
|
||||
4. 既存 linear JSON は lazy migration(read old -> write new on mutation)
|
||||
5. 安定後に old-only path を read-compat へ縮退
|
||||
|
||||
### 並列作業可能タスク
|
||||
|
||||
#### Track A: Core Data Model / Storage
|
||||
- `SessionRef`, `ConversationNode`, `LogEvent` 型追加
|
||||
- `SessionGraphStore` と CAS 更新実装
|
||||
- node persistence + compaction + GC (unreachable branch TTL)
|
||||
|
||||
#### Track B: Replay / Prompt Integration
|
||||
- DAG -> linear message replay 実装
|
||||
- merge node の replay 表現(1-turn memory bundle)
|
||||
- context overflow 時の compression 戦略を DAG 前提に再設計
|
||||
|
||||
#### Track C: Orchestration Integration
|
||||
- subagent completion を merge node 化
|
||||
- task metadata (`task_id`, `source_session`, `duration`, `tool_calls`) を node meta へ格納
|
||||
- reporter/broadcaster のイベントと node lifecycle の対応付け
|
||||
|
||||
#### Track D: Channel & Provider Capabilities
|
||||
- channel capability matrix を型化(thread/draft/edit/task-status)
|
||||
- provider capability matrix を型化(streaming/structured/tool fidelity)
|
||||
- merge policy の capability-aware fallback 追加
|
||||
|
||||
#### Track E: API / UI / Commands
|
||||
- fork/merge/list-branches API 追加
|
||||
- Mini App: branch graph 可視化、merge preview、conflict explanation
|
||||
- CLI/command: `/session fork`, `/session merge`, `/session graph`
|
||||
|
||||
#### Track F: Migration / Safety / Ops
|
||||
- old sessions から DAG への lazy migration
|
||||
- observability: merge success rate, conflict rate, replay latency
|
||||
- rollback switch(feature flag)とデータ整合性チェック
|
||||
|
||||
#### Track G: Tests / Benchmarks
|
||||
- replay determinism tests
|
||||
- merge policy golden tests
|
||||
- concurrency tests (CAS conflict / retry)
|
||||
- memory & latency benchmark(long-history / many-branch scenarios)
|
||||
|
||||
### 依存関係(高レベル)
|
||||
|
||||
- A は全 Track の前提
|
||||
- B/C は A 完了後に並列可能
|
||||
- D は B/C と並列可能(インターフェース先行)
|
||||
- E は B/C/D の API 固定後に進める
|
||||
- F/G は全フェーズ横断で継続実施
|
||||
|
||||
---
|
||||
|
||||
## Session DAG Design (Revised 2026-03-04)
|
||||
|
||||
> 上記 Session DAG Migration Plan を SBC制約と設計レビューを踏まえて改訂したもの。
|
||||
|
||||
### 設計原則
|
||||
|
||||
1. **セッション内は線形、セッション間がDAG** — per-message DAGは過剰。ターン間の因果は順序で十分。
|
||||
2. **SQLite single-file backend** — microSD書き込み最小化、WALモードでクラッシュ耐性。
|
||||
3. **サブエージェント報告は user role** — system roleの権威性バイアスを回避。conductorが評価・反論できる。
|
||||
4. **"merge" は特別な操作ではない** — 報告を受けて会話を続ける通常のターン。
|
||||
|
||||
### 初期提案からの変更点
|
||||
|
||||
| 初期提案 | 改訂 | 理由 |
|
||||
|---|---|---|
|
||||
| `ConversationNode` per-message | `Turn` per-turn (複数メッセージをバンドル) | ノード爆発回避、microSD保護 |
|
||||
| CAS + `Version uint64` | `sync.RWMutex` (single process) | SBCはシングルプロセス、CASは過剰 |
|
||||
| `NodeKind=Merge` + system role注入 | `TurnReport` + user role会話 | LLMの権威性バイアス回避 |
|
||||
| per-node JSON files | SQLite single file | 書き込み回数削減、インデックス付きクエリ |
|
||||
| LLM-assisted merge | deterministic (不要に) | mergeという概念自体が消えた |
|
||||
| 毎回DAG→linear replay | cached linear view + dirty flag | ARM CPU負荷軽減 |
|
||||
|
||||
### DAG構造
|
||||
|
||||
```
|
||||
Conductor session: turn1 → turn2 → turn3 → report(scout-1) → turn4 → report(coder-1) → turn5
|
||||
↓ fork ↑ report
|
||||
Scout-1 session: turn1 → turn2 → turn3 ──────────┘
|
||||
↓ fork
|
||||
Coder-1 session: turn1 → turn2 → turn3 ──────────┘
|
||||
```
|
||||
|
||||
セッション内は `seq INTEGER` で順序管理。DAGの辺はセッション間の `parent_key` / `origin_key`。
|
||||
|
||||
### SQLite Schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE sessions (
|
||||
key TEXT PRIMARY KEY,
|
||||
parent_key TEXT REFERENCES sessions(key),
|
||||
fork_turn_id TEXT, -- 親のどのturnで分岐したか
|
||||
status TEXT NOT NULL DEFAULT 'active', -- active|completed|archived
|
||||
label TEXT NOT NULL DEFAULT '', -- "scout-1", "heartbeat" etc.
|
||||
summary TEXT NOT NULL DEFAULT '',
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE turns (
|
||||
id TEXT PRIMARY KEY,
|
||||
session_key TEXT NOT NULL REFERENCES sessions(key) ON DELETE CASCADE,
|
||||
seq INTEGER NOT NULL,
|
||||
kind INTEGER NOT NULL DEFAULT 0, -- 0=normal 1=report 2=fork_point
|
||||
messages TEXT NOT NULL, -- JSON []providers.Message
|
||||
origin_key TEXT, -- report: どのセッションの報告か
|
||||
summary TEXT, -- compaction後。非NULLならmessagesは空
|
||||
author TEXT NOT NULL DEFAULT '',
|
||||
created_at INTEGER NOT NULL,
|
||||
meta TEXT, -- JSON object, nullable
|
||||
UNIQUE(session_key, seq)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_turns_session_seq ON turns(session_key, seq);
|
||||
CREATE INDEX idx_sessions_parent ON sessions(parent_key);
|
||||
```
|
||||
|
||||
### Go Interface
|
||||
|
||||
```go
|
||||
package session
|
||||
|
||||
type TurnKind int
|
||||
|
||||
const (
|
||||
TurnNormal TurnKind = iota // user↔assistant 通常ターン
|
||||
TurnReport // サブエージェント報告 + conductor評価
|
||||
TurnForkPoint // マーカー: ここで子セッションが分岐した
|
||||
)
|
||||
|
||||
type Turn struct {
|
||||
ID string
|
||||
Seq int
|
||||
Kind TurnKind
|
||||
Messages []providers.Message
|
||||
OriginKey string // TurnReport時: 報告元セッションkey
|
||||
Summary string // compaction済みなら非空、Messagesは空
|
||||
Author string
|
||||
CreatedAt time.Time
|
||||
Meta map[string]string
|
||||
}
|
||||
|
||||
type SessionInfo struct {
|
||||
Key string
|
||||
ParentKey string // "" = root
|
||||
ForkTurnID string
|
||||
Status string // active, completed, archived
|
||||
Label string
|
||||
Summary string
|
||||
TurnCount int
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type CreateOpts struct {
|
||||
ParentKey string
|
||||
ForkTurnID string
|
||||
Label string
|
||||
}
|
||||
|
||||
type ListFilter struct {
|
||||
ParentKey string
|
||||
Status string
|
||||
}
|
||||
|
||||
// SessionStore — SQLite実装が唯一の実装
|
||||
type SessionStore interface {
|
||||
// セッション
|
||||
Create(key string, opts *CreateOpts) error
|
||||
Get(key string) (*SessionInfo, error)
|
||||
List(filter *ListFilter) ([]*SessionInfo, error)
|
||||
SetStatus(key, status string) error
|
||||
SetSummary(key, summary string) error
|
||||
Delete(key string) error
|
||||
Children(key string) ([]*SessionInfo, error)
|
||||
|
||||
// ターン (セッション内は線形)
|
||||
Append(sessionKey string, turn *Turn) error
|
||||
Turns(sessionKey string, sinceSeq int) ([]*Turn, error)
|
||||
LastTurn(sessionKey string) (*Turn, error)
|
||||
TurnCount(sessionKey string) (int, error)
|
||||
|
||||
// compaction: seq以前のturnsをsummaryで置換
|
||||
Compact(sessionKey string, upToSeq int, summary string) error
|
||||
|
||||
// fork: 親セッションの現在headから子セッションを作る
|
||||
Fork(parentKey, childKey string, opts *CreateOpts) error
|
||||
|
||||
// 管理
|
||||
Prune(olderThan time.Duration) (int, error)
|
||||
Close() error
|
||||
}
|
||||
```
|
||||
|
||||
### 高レベルラッパー
|
||||
|
||||
```go
|
||||
// SessionGraph — AgentLoopが直接使う層
|
||||
// SessionStoreをラップし、ターンバッファとlinear viewキャッシュを持つ
|
||||
type SessionGraph struct {
|
||||
store SessionStore
|
||||
buffers sync.Map // sessionKey → *turnBuffer (書き込み中ターン)
|
||||
views sync.Map // sessionKey → *cachedView (LLM用メッセージ列)
|
||||
}
|
||||
|
||||
// LLM用: 全turnsをフラットな[]Messageに展開
|
||||
// compaction済みturnsはsummaryをsystem messageとして先頭に置く
|
||||
func (g *SessionGraph) Messages(sessionKey string) ([]providers.Message, error)
|
||||
|
||||
// ターン開始 (user message受信時)
|
||||
func (g *SessionGraph) BeginTurn(sessionKey string, kind TurnKind) *TurnWriter
|
||||
|
||||
// TurnWriter — 1ターン内でメッセージを逐次追加
|
||||
type TurnWriter struct { ... }
|
||||
func (tw *TurnWriter) Add(msg providers.Message)
|
||||
func (tw *TurnWriter) SetOrigin(sessionKey string) // report元を設定
|
||||
func (tw *TurnWriter) Commit() error // store.Appendして確定
|
||||
func (tw *TurnWriter) Discard() // 破棄 (エラー時)
|
||||
```
|
||||
|
||||
### サブエージェント報告フロー
|
||||
|
||||
```
|
||||
1. conductor が spawn → store.Fork(conductorSession, subagentSession)
|
||||
2. subagent 実行中 → store.Append(subagentSession, Turn{Kind: TurnNormal, ...})
|
||||
3. subagent 完了 → store.SetStatus(subagentSession, "completed")
|
||||
4. conductor 側に report ターン:
|
||||
tw := graph.BeginTurn(conductorSession, TurnReport)
|
||||
tw.SetOrigin(subagentSession)
|
||||
tw.Add(Message{Role: "user", Content: "[scout-1] 調査結果..."})
|
||||
// conductorのLLMループが応答を追加してから tw.Commit()
|
||||
5. conductor が応答:
|
||||
tw.Add(Message{Role: "assistant", Content: "なるほど、JWTで十分..."})
|
||||
tw.Commit()
|
||||
```
|
||||
|
||||
conductorは各報告を個別に評価・反論できる。system roleではないので鵜呑みにしにくい。
|
||||
|
||||
### 既存コードとの互換アダプター
|
||||
|
||||
```go
|
||||
// 移行期間中、既存の SessionManager interface を満たす
|
||||
type LegacyAdapter struct {
|
||||
graph *SessionGraph
|
||||
}
|
||||
|
||||
func (a *LegacyAdapter) GetHistory(key string) ([]providers.Message, error) {
|
||||
return a.graph.Messages(key)
|
||||
}
|
||||
func (a *LegacyAdapter) AddMessage(key, role, content string) error { ... }
|
||||
func (a *LegacyAdapter) SetHistory(key string, msgs []providers.Message) error { ... }
|
||||
```
|
||||
|
||||
### 移行フェーズ
|
||||
|
||||
1. **Phase 0**: SQLite SessionStore 実装 + LegacyAdapter。既存動作を維持したまま裏側を差し替え
|
||||
2. **Phase 1**: サブエージェントセッション永続化 + Fork/Report ターン導入
|
||||
3. **Phase 2**: AgentLoop を SessionGraph 直接呼び出しに移行。LegacyAdapter 廃止
|
||||
4. **Phase 3**: Mini App graph 可視化 + `/session` コマンド群
|
||||
| ファイル | 概要 |
|
||||
|---|---|
|
||||
| [`todo/TASKS-1.md`](todo/TASKS-1.md) | **Memory & Performance Optimization** — MemoryStore キャッシュ、FunctionCall/ToolDefinition 型整理、stats フラッシュ最適化 |
|
||||
| [`todo/TASKS-2.md`](todo/TASKS-2.md) | **Subagent Orchestration (Container Model)** — SubagentContainer、Orchestrator、Presets enforcement、Subagent Plan Mode |
|
||||
| [`todo/TASKS-3.md`](todo/TASKS-3.md) | **Session DAG (SQLite Store)** — セッション管理の SQLite 移行、Turn ベース線形+セッション間 DAG、Fork/Report フロー |
|
||||
| [`todo/TASKS-4.md`](todo/TASKS-4.md) | **Mini App & Static Serving** — 静的配信の汎用化、バンドラ導入、フロントエンドテスト追加 |
|
||||
| [`todo/TASKS-5.md`](todo/TASKS-5.md) | **Heartbeat Worktree Management** — worktree 一覧・点検・手動 merge/dispose の CLI/Mini App UI |
|
||||
|
|
|
|||
75
todo/TASKS-1.md
Normal file
75
todo/TASKS-1.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# TASKS-1: Memory & Performance Optimization
|
||||
|
||||
内部リファクタリング。外部APIの変更なし。他トラックへの依存なし。
|
||||
|
||||
## タスク一覧
|
||||
|
||||
### D-1. MemoryStore のパース済みキャッシュ導入
|
||||
|
||||
`GetMemoryContext()` 1回で `ReadLongTerm()` が5回以上呼ばれる連鎖を解消。
|
||||
|
||||
**方針 (いずれか):**
|
||||
- (a) content パススルー — 高レベルメソッドだけが1回 `ReadLongTerm()` を呼び、content を private ヘルパーに渡す
|
||||
- (b) `*ParsedPlan` 常駐 — MemoryStore にパース済み構造体を持たせ、`edit_file` 後に `InvalidateCache()` を呼ぶ
|
||||
|
||||
**対象ファイル:** `pkg/agent/memory.go`
|
||||
|
||||
---
|
||||
|
||||
### D-2. FunctionCall.Arguments の型整理
|
||||
|
||||
`FunctionCall.Arguments` が JSON 文字列のまま。ストリーミングループ内で重複 Unmarshal が発生。
|
||||
`ToolCall.Arguments map[string]any` のパース済みフィールドと中途半端に共存している。
|
||||
|
||||
**やること:** パース済みフィールドに一本化し、JSON 文字列フィールドを内部に隠蔽。
|
||||
|
||||
**対象ファイル:** `pkg/providers/protocoltypes/types.go`, ストリーミング処理周辺
|
||||
|
||||
---
|
||||
|
||||
### D-3. ToolFunctionDefinition.Parameters → json.RawMessage
|
||||
|
||||
`Parameters` が `map[string]any` のため、プロバイダーへ送るたびに Marshal が必要。
|
||||
`json.RawMessage` にすれば一度の marshal で済む。
|
||||
|
||||
**対象ファイル:** `pkg/providers/protocoltypes/types.go`, 各プロバイダー実装
|
||||
|
||||
---
|
||||
|
||||
### D-4. 検索プロバイダーの共通フォーマット抽象
|
||||
|
||||
`[]string + strings.Join` パターンが3箇所に複製。`Search()` 戻り値を構造体にすれば1箇所で済む。
|
||||
|
||||
**対象ファイル:** `pkg/search/` 配下
|
||||
|
||||
---
|
||||
|
||||
### D-6. MemoryStore メソッド境界の再設計
|
||||
|
||||
呼び出し側は複数の値が必要でも複数回 `ReadLongTerm()` を呼ぶしかない。
|
||||
D-1 の解決策と合わせて、メソッド境界を「必要な情報の単位」に再編成。
|
||||
|
||||
**対象ファイル:** `pkg/agent/memory.go`
|
||||
|
||||
---
|
||||
|
||||
### stats.Tracker 定期フラッシュ
|
||||
|
||||
`state/stats.json` が LLM 呼び出し毎に書き込まれる。定期フラッシュ (5分) に変更して microSD 寿命を保護。
|
||||
|
||||
**やること:**
|
||||
- `stats.Tracker` にインメモリバッファ + バックグラウンドフラッシャー goroutine を追加
|
||||
- `Close()` メソッド追加 (タイマー停止 + 最終 save)
|
||||
- SIGTERM/SIGINT でシャットダウンフック
|
||||
|
||||
**対象ファイル:** `pkg/state/state.go`
|
||||
|
||||
---
|
||||
|
||||
## 完了基準
|
||||
|
||||
- `go test ./...` 全パス
|
||||
- MEMORY.md 読み取りが1ターンあたり1回以下に削減 (D-1)
|
||||
- FunctionCall の Unmarshal が1回に集約 (D-2)
|
||||
- ToolFunctionDefinition.Parameters の Marshal がプロバイダー初期化時のみ (D-3)
|
||||
- stats.json の書き込み頻度が 98% 削減 (stats.Tracker)
|
||||
227
todo/TASKS-2.md
Normal file
227
todo/TASKS-2.md
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
# TASKS-2: Subagent Orchestration (Container Model)
|
||||
|
||||
SubagentManager を Container ベースの Orchestrator に進化させる。
|
||||
セッション管理の内部実装 (TASKS-3) には依存しない — 現行の SessionManager 上で動作させ、後から SessionStore に差し替える。
|
||||
|
||||
## 設計コンテキスト
|
||||
|
||||
### アーキテクチャ
|
||||
|
||||
```
|
||||
Conductor goroutine
|
||||
│ ContainerRequest (task, preset, environment)
|
||||
▼
|
||||
Container goroutine: provision → run → finalize
|
||||
│ ContainerMessage (question / result / status)
|
||||
▼
|
||||
Conductor goroutine
|
||||
│ answer (question への回答)
|
||||
▼ (わからなければ human に escalate)
|
||||
Container goroutine (再開)
|
||||
```
|
||||
|
||||
### escalation chain
|
||||
|
||||
```
|
||||
subagent (clarifying) → question → conductor
|
||||
→ conductor が答えられる: inCh に回答
|
||||
→ conductor もわからない: message tool で human に投げ、回答を転送
|
||||
```
|
||||
|
||||
### Presets (5種)
|
||||
|
||||
| preset | 性格 | write | exec | search | spawn |
|
||||
|---|---|---|---|---|---|
|
||||
| `scout` | Exploratory | x | x | o | x |
|
||||
| `analyst` | Exploratory | x | go test/vet, git log/diff, grep | o | x |
|
||||
| `coder` | Deliberate | o sandbox | test/lint/fmt 系 | o | x |
|
||||
| `worker` | Deliberate | o sandbox | build/package manager 系 | o | x |
|
||||
| `coordinator` | Deliberate | o sandbox | go/pnpm/bun/curl 系 | o | scout-worker のみ |
|
||||
|
||||
**性格の分類:**
|
||||
- **Exploratory** (scout/analyst): open-ended、見てきて報告。clarifying フェーズなし。
|
||||
- **Deliberate** (coder/worker/coordinator): 成果物を作る。clarifying フェーズあり。
|
||||
|
||||
**npm は全 preset で禁止** (git worktree に node_modules が生じるため)。pnpm/bun は可。
|
||||
**websearch/webfetch は全 preset で許可** (read-only のため)。
|
||||
|
||||
### exec allowlist regex
|
||||
|
||||
```go
|
||||
var presetExecPatterns = map[string]string{
|
||||
"scout": ``,
|
||||
"analyst": `^(go\s+(test|vet)|git\s+(log|diff|status)|curl|wget|grep|find)\b`,
|
||||
"coder": `^(` +
|
||||
`go\s+(test|vet|fmt)|gofmt|goimports|golangci-lint|` +
|
||||
`prettier|eslint|` +
|
||||
`black|ruff|` +
|
||||
`cargo\s+(test|fmt|clippy)|` +
|
||||
`pnpm\s+(test|run\s+(test|lint|format))|` +
|
||||
`bun\s+(test|run\s+(test|lint|format))|` +
|
||||
`uv\s+run\s+` +
|
||||
`)\b`,
|
||||
"worker": `^(` +
|
||||
`go\s+|` +
|
||||
`pnpm\s+(install|add|run|test|build)|` +
|
||||
`bun\s+(install|add|run|test|build)|` +
|
||||
`uv\s+(run|sync|add|pip\s+install)|` +
|
||||
`pip\s+install|` +
|
||||
`cargo\s+` +
|
||||
`)\b`,
|
||||
"coordinator": `^(go\s+|pnpm\s+|bun\s+|curl|wget)\b`,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## タスク一覧
|
||||
|
||||
### 1. SubagentContainer 実装
|
||||
|
||||
goroutine + channel でサブエージェントのライフサイクルを表現。
|
||||
|
||||
```go
|
||||
type ContainerMessage struct {
|
||||
Type string // "question" | "result" | "status"
|
||||
Content string
|
||||
}
|
||||
|
||||
type SubagentContainer struct {
|
||||
inCh chan string // conductor → subagent (回答)
|
||||
outCh chan ContainerMessage // subagent → conductor (質問・結果・進捗)
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
```
|
||||
|
||||
- spawn (async): outCh を返して即リターン
|
||||
- subagent (sync): その場で result を待つ
|
||||
- goroutine 終了時に `defer orchestrator.active.Delete(id)` + `defer close(outCh)` で自動 GC
|
||||
|
||||
**対象ファイル:** `pkg/tools/container.go` (新規)
|
||||
|
||||
---
|
||||
|
||||
### 2. Orchestrator 実装
|
||||
|
||||
SubagentManager を置き換える上位構造。
|
||||
|
||||
- Container の生成・管理
|
||||
- escalation chain の実装 (subagent → conductor → human)
|
||||
- spawn/subagent の使い分けロジック
|
||||
|
||||
**対象ファイル:** `pkg/tools/orchestrator.go` (新規)
|
||||
|
||||
---
|
||||
|
||||
### 3. SubagentEnvironment (Context Injection)
|
||||
|
||||
conductor が subagent に渡すコンテキストの構造化。
|
||||
|
||||
```go
|
||||
type SubagentEnvironment struct {
|
||||
Workspace string
|
||||
WorktreeDir string
|
||||
PlanTask string // MEMORY.md > Task: から自動抽出
|
||||
PlanContext string // MEMORY.md ## Context から自動抽出
|
||||
Commands string // MEMORY.md ## Commands から自動抽出
|
||||
CurrentPhase string
|
||||
Background string // conductor が明示的に追加
|
||||
Constraints string
|
||||
ContextFiles []string
|
||||
}
|
||||
```
|
||||
|
||||
`inject_plan_context: true` で MEMORY.md からの自動注入を有効化。
|
||||
|
||||
**対象ファイル:** `pkg/tools/container.go` または `pkg/tools/environment.go` (新規)
|
||||
|
||||
---
|
||||
|
||||
### 4. SandboxConfig enforcement
|
||||
|
||||
`ToolRegistry.Execute()` の入口で一括 enforcement。subagent は透過的に sandboxed になる。
|
||||
|
||||
```go
|
||||
type SandboxConfig struct {
|
||||
Preset string
|
||||
WriteRoot string
|
||||
AllowedTools map[string]bool
|
||||
ExecPolicy *ExecPolicy
|
||||
SpawnablePresets []string
|
||||
}
|
||||
```
|
||||
|
||||
workDir = worktreeDir として設定し、AI が隔離に気づかず振る舞う透過的隔離を実現。
|
||||
|
||||
**対象ファイル:** `pkg/tools/sandbox.go` (既存拡張), `pkg/tools/registry.go`
|
||||
|
||||
---
|
||||
|
||||
### 5. Preset enforcement
|
||||
|
||||
preset 定義と exec allowlist regex を SandboxConfig 経由で enforce。
|
||||
`pkg/tools/sandbox.go` に既存の定義があるが、ToolRegistry.Execute() での一括 enforcement がまだ。
|
||||
|
||||
---
|
||||
|
||||
### 6. Subagent Plan Mode (in-memory)
|
||||
|
||||
Deliberate preset 用のミニ plan mode。MEMORY.md には触れない。
|
||||
|
||||
```go
|
||||
type SubagentPlanState int
|
||||
|
||||
const (
|
||||
PlanStateNone SubagentPlanState = iota // Exploratory preset
|
||||
PlanStateClarifying // 目的・制約を確認中
|
||||
PlanStateReview // conductor の承認待ち
|
||||
PlanStateExecuting // 実行中
|
||||
)
|
||||
|
||||
type SubagentPlan struct {
|
||||
State SubagentPlanState
|
||||
Goal string
|
||||
Approach []string
|
||||
QA []QAItem
|
||||
mu sync.Mutex
|
||||
}
|
||||
```
|
||||
|
||||
SubagentContainer がフィールドとして保持。goroutine 終了とともに消える。
|
||||
|
||||
**System prompt 使い分け:**
|
||||
- Deliberate: clarifying → review → executing の3段階
|
||||
- Exploratory: 即座に探索開始、判断は自律的に
|
||||
|
||||
---
|
||||
|
||||
### 7. MEMORY.md Orchestration Section
|
||||
|
||||
conductor の guidance に Orchestration セクションの使い方を追記。
|
||||
|
||||
```markdown
|
||||
## Orchestration
|
||||
|
||||
### Delegated
|
||||
- coder-1 (coder): rate limiter 実装 → Phase 2 Step 1
|
||||
|
||||
### Findings
|
||||
- pkg/auth は middleware パターン (scout-1)
|
||||
|
||||
### Decisions
|
||||
- auth: JWT を選択 (外部依存なし、scout-2 推奨)
|
||||
```
|
||||
|
||||
conductor は spawn 後に Delegated に記録、結果受信後に Findings に追記、方向選択時に Decisions に記録。
|
||||
|
||||
**対象ファイル:** `pkg/agent/context.go` (guidance 追記)
|
||||
|
||||
---
|
||||
|
||||
## 完了基準
|
||||
|
||||
- `go test ./...` 全パス
|
||||
- scout/analyst/coder preset で spawn → 結果受信 → conductor 応答の E2E フロー動作
|
||||
- Deliberate preset の clarifying → review → executing フロー動作
|
||||
- SandboxConfig による exec 制限が全 preset で正しく enforcement
|
||||
- escalation chain (subagent → conductor → human) が動作
|
||||
230
todo/TASKS-3.md
Normal file
230
todo/TASKS-3.md
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
# TASKS-3: Session DAG (SQLite Store)
|
||||
|
||||
セッション管理を JSON ファイルベースの線形スライスから SQLite ベースの Turn DAG に移行する。
|
||||
他トラックへの依存なし — LegacyAdapter で既存コードとの後方互換を維持しながら段階移行。
|
||||
|
||||
## 設計原則
|
||||
|
||||
1. **セッション内は線形、セッション間が DAG** — per-message DAG は過剰。ターン間の因果は順序で十分。
|
||||
2. **SQLite single-file backend** — microSD 書き込み最小化、WAL モードでクラッシュ耐性。
|
||||
3. **サブエージェント報告は user role** — system role の権威性バイアスを回避。conductor が評価・反論できる。
|
||||
4. **"merge" は特別な操作ではない** — 報告を受けて会話を続ける通常のターン。
|
||||
|
||||
## DAG 構造
|
||||
|
||||
```
|
||||
Conductor session: turn1 → turn2 → turn3 → report(scout-1) → turn4 → report(coder-1) → turn5
|
||||
↓ fork ↑ report
|
||||
Scout-1 session: turn1 → turn2 → turn3 ──────────┘
|
||||
↓ fork
|
||||
Coder-1 session: turn1 → turn2 → turn3 ──────────┘
|
||||
```
|
||||
|
||||
## SQLite Schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE sessions (
|
||||
key TEXT PRIMARY KEY,
|
||||
parent_key TEXT REFERENCES sessions(key),
|
||||
fork_turn_id TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
label TEXT NOT NULL DEFAULT '',
|
||||
summary TEXT NOT NULL DEFAULT '',
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE turns (
|
||||
id TEXT PRIMARY KEY,
|
||||
session_key TEXT NOT NULL REFERENCES sessions(key) ON DELETE CASCADE,
|
||||
seq INTEGER NOT NULL,
|
||||
kind INTEGER NOT NULL DEFAULT 0,
|
||||
messages TEXT NOT NULL,
|
||||
origin_key TEXT,
|
||||
summary TEXT,
|
||||
author TEXT NOT NULL DEFAULT '',
|
||||
created_at INTEGER NOT NULL,
|
||||
meta TEXT,
|
||||
UNIQUE(session_key, seq)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_turns_session_seq ON turns(session_key, seq);
|
||||
CREATE INDEX idx_sessions_parent ON sessions(parent_key);
|
||||
```
|
||||
|
||||
## Go Interface
|
||||
|
||||
```go
|
||||
type TurnKind int
|
||||
|
||||
const (
|
||||
TurnNormal TurnKind = iota
|
||||
TurnReport
|
||||
TurnForkPoint
|
||||
)
|
||||
|
||||
type Turn struct {
|
||||
ID string
|
||||
Seq int
|
||||
Kind TurnKind
|
||||
Messages []providers.Message
|
||||
OriginKey string
|
||||
Summary string
|
||||
Author string
|
||||
CreatedAt time.Time
|
||||
Meta map[string]string
|
||||
}
|
||||
|
||||
type SessionInfo struct {
|
||||
Key string
|
||||
ParentKey string
|
||||
ForkTurnID string
|
||||
Status string
|
||||
Label string
|
||||
Summary string
|
||||
TurnCount int
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type SessionStore interface {
|
||||
Create(key string, opts *CreateOpts) error
|
||||
Get(key string) (*SessionInfo, error)
|
||||
List(filter *ListFilter) ([]*SessionInfo, error)
|
||||
SetStatus(key, status string) error
|
||||
SetSummary(key, summary string) error
|
||||
Delete(key string) error
|
||||
Children(key string) ([]*SessionInfo, error)
|
||||
|
||||
Append(sessionKey string, turn *Turn) error
|
||||
Turns(sessionKey string, sinceSeq int) ([]*Turn, error)
|
||||
LastTurn(sessionKey string) (*Turn, error)
|
||||
TurnCount(sessionKey string) (int, error)
|
||||
|
||||
Compact(sessionKey string, upToSeq int, summary string) error
|
||||
Fork(parentKey, childKey string, opts *CreateOpts) error
|
||||
|
||||
Prune(olderThan time.Duration) (int, error)
|
||||
Close() error
|
||||
}
|
||||
```
|
||||
|
||||
## 高レベルラッパー
|
||||
|
||||
```go
|
||||
type SessionGraph struct {
|
||||
store SessionStore
|
||||
buffers sync.Map // sessionKey → *turnBuffer
|
||||
views sync.Map // sessionKey → *cachedView
|
||||
}
|
||||
|
||||
func (g *SessionGraph) Messages(sessionKey string) ([]providers.Message, error)
|
||||
func (g *SessionGraph) BeginTurn(sessionKey string, kind TurnKind) *TurnWriter
|
||||
|
||||
type TurnWriter struct { ... }
|
||||
func (tw *TurnWriter) Add(msg providers.Message)
|
||||
func (tw *TurnWriter) SetOrigin(sessionKey string)
|
||||
func (tw *TurnWriter) Commit() error
|
||||
func (tw *TurnWriter) Discard()
|
||||
```
|
||||
|
||||
## サブエージェント報告フロー
|
||||
|
||||
```
|
||||
1. conductor が spawn → store.Fork(conductorSession, subagentSession)
|
||||
2. subagent 実行中 → store.Append(subagentSession, Turn{Kind: TurnNormal, ...})
|
||||
3. subagent 完了 → store.SetStatus(subagentSession, "completed")
|
||||
4. conductor 側に report ターン:
|
||||
tw := graph.BeginTurn(conductorSession, TurnReport)
|
||||
tw.SetOrigin(subagentSession)
|
||||
tw.Add(Message{Role: "user", Content: "[scout-1] 調査結果..."})
|
||||
5. conductor が応答:
|
||||
tw.Add(Message{Role: "assistant", Content: "なるほど、JWTで十分..."})
|
||||
tw.Commit()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## タスク一覧
|
||||
|
||||
### Phase 0: SQLite SessionStore + LegacyAdapter
|
||||
|
||||
既存動作を維持したまま裏側を差し替える。
|
||||
|
||||
1. **SQLite SessionStore 実装**
|
||||
- `pkg/session/sqlite.go` (新規): SessionStore interface の SQLite 実装
|
||||
- WAL モード、`modernc.org/sqlite` (CGO なし、ARM クロスコンパイル容易)
|
||||
- schema migration (CREATE TABLE IF NOT EXISTS)
|
||||
- `go:build` タグなしで常に利用可能
|
||||
|
||||
2. **LegacyAdapter 実装**
|
||||
- `pkg/session/legacy_adapter.go` (新規)
|
||||
- 既存の `GetHistory` / `SetHistory` / `AddMessage` / `MarkDirty` を SessionGraph 経由で実装
|
||||
- 既存テストが全パスすること
|
||||
|
||||
3. **JSON → SQLite lazy migration**
|
||||
- 起動時に `sessions/*.json` を検出 → SQLite に import → JSON ファイルをリネーム (.migrated)
|
||||
- エラー時は JSON にフォールバック
|
||||
|
||||
4. **AgentLoop 配線**
|
||||
- `AgentInstance` が `SessionStore` を保持、`LegacyAdapter` 経由で既存コードに注入
|
||||
- 既存の `SessionManager` は Phase 2 で廃止
|
||||
|
||||
---
|
||||
|
||||
### Phase 1: Fork/Report ターン導入
|
||||
|
||||
5. **Fork 操作**
|
||||
- `SessionStore.Fork()` 実装
|
||||
- 親セッションに `TurnForkPoint` を追記、子セッションを `parent_key` 付きで作成
|
||||
|
||||
6. **Report ターン**
|
||||
- `TurnReport` の Append/Turns/Messages 対応
|
||||
- `origin_key` でどのセッションの報告かを追跡
|
||||
- user role でメッセージ格納 (system role 禁止)
|
||||
|
||||
7. **サブエージェントセッション永続化**
|
||||
- サブエージェント実行中のターンを SQLite に記録
|
||||
- 完了後にセッション status を "completed" に変更
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: AgentLoop 直接移行
|
||||
|
||||
8. **SessionGraph 直接呼び出し**
|
||||
- `runAgentLoop()` を `SessionGraph.BeginTurn()` / `TurnWriter` 経由に変更
|
||||
- `processSystemMessage()` を Report ターン生成に変更
|
||||
- LegacyAdapter 廃止
|
||||
|
||||
9. **Compaction**
|
||||
- 古いターンの messages を空にして summary で置換
|
||||
- context window 管理と連動
|
||||
|
||||
10. **セッションライフサイクル**
|
||||
- `Delete(key)` + TTL エビクション (Prune)
|
||||
- `sessionLocks sync.Map` の GC
|
||||
- 起動時の遅延ロード (SQLite なので自然に実現)
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: UI & Commands
|
||||
|
||||
11. **Mini App セッショングラフ可視化**
|
||||
- WebSocket で session DAG 構造を配信
|
||||
- fork/report 関係をグラフとして描画
|
||||
|
||||
12. **CLI コマンド**
|
||||
- `/session list` — アクティブセッション一覧
|
||||
- `/session fork` — 現在のセッションを fork
|
||||
- `/session graph` — DAG 構造をテキスト表示
|
||||
|
||||
---
|
||||
|
||||
## 完了基準
|
||||
|
||||
- `go test ./...` 全パス
|
||||
- 既存の JSON セッションが SQLite に自動マイグレーション
|
||||
- Phase 0 完了時点で既存動作に変化なし (LegacyAdapter 透過)
|
||||
- サブエージェント報告が user role ターンとして記録
|
||||
- conductor が報告を個別に評価・応答するフロー動作
|
||||
- microSD 書き込み頻度が既存比で削減
|
||||
74
todo/TASKS-4.md
Normal file
74
todo/TASKS-4.md
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# TASKS-4: Mini App & Static Serving
|
||||
|
||||
Mini App のフロントエンド改善。バックエンドの変更に依存しない。
|
||||
|
||||
## 現状
|
||||
|
||||
```
|
||||
pkg/miniapp/static/
|
||||
index.html ← すべての JS/CSS をインライン
|
||||
map.js ← オーケストレーションルーム描画
|
||||
```
|
||||
|
||||
Go 側: `//go:embed static/index.html static/map.js` + 個別ルートで配信。
|
||||
|
||||
---
|
||||
|
||||
## タスク一覧
|
||||
|
||||
### 1. Static file serving の汎用化
|
||||
|
||||
個別ルート (`serveIndex`, `serveMapJS`) を `http.FileServer(http.FS(staticFS))` に統合。
|
||||
|
||||
**やること:**
|
||||
- `//go:embed static` でディレクトリごと embed
|
||||
- `/miniapp/` 以下を `http.FileServer` で一括配信
|
||||
- 新規ファイル追加時にルート登録が不要になる
|
||||
- `serveIndex` の `ORCH_ENABLED` 注入は `text/template` に移行
|
||||
|
||||
**対象ファイル:** `pkg/miniapp/handler.go`
|
||||
|
||||
---
|
||||
|
||||
### 2. バンドラ導入 (Vite / esbuild)
|
||||
|
||||
inline JS/CSS を外部ファイルに分離し、ビルドステップで bundle する。
|
||||
|
||||
**やること:**
|
||||
- `pkg/miniapp/frontend/` に source を配置
|
||||
- esbuild (軽量、Go 製) でビルド → `pkg/miniapp/static/dist/` に出力
|
||||
- Go 側は `//go:embed static/dist` で embed
|
||||
- `ORCH_ENABLED` はビルド時の環境変数注入 (`define`) で対応
|
||||
- `Makefile` / `go:generate` でビルドステップを統合
|
||||
|
||||
**検討事項:**
|
||||
- esbuild は Go 製でクロスコンパイル環境に影響しない
|
||||
- Vite は機能豊富だが Node.js 依存が増える
|
||||
|
||||
---
|
||||
|
||||
### 3. Log viewer のフロントエンドテスト
|
||||
|
||||
`renderLogs()` (index.html 内の inline JS) のテストが皆無。
|
||||
Fields 表示バグが検出されずに ship された前科あり。
|
||||
|
||||
**やること:**
|
||||
- index.html から JS を外部ファイルに抽出 (タスク 2 と連動)
|
||||
- `renderLogs()`, `renderFields()` 等の unit test を追加
|
||||
- DOM 操作のテストは jsdom (vitest) or happy-dom
|
||||
- CI で `pnpm test` を実行
|
||||
|
||||
**最低限のテストケース:**
|
||||
- メッセージの正常レンダリング
|
||||
- Fields の表示 (空/1件/複数件)
|
||||
- サニタイズ (XSS 防止)
|
||||
- ログのフィルタリング・ページネーション
|
||||
|
||||
---
|
||||
|
||||
## 完了基準
|
||||
|
||||
- 新規 JS/CSS ファイルの追加がルート登録なしで配信される
|
||||
- `ORCH_ENABLED` の注入がテンプレートまたはビルド時変数で動作
|
||||
- `renderLogs()` のユニットテストが CI で実行される
|
||||
- `go build ./...` が変わらず動作 (embed パスの整合性)
|
||||
62
todo/TASKS-5.md
Normal file
62
todo/TASKS-5.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# TASKS-5: Heartbeat Worktree Management
|
||||
|
||||
ハートビートで作られる git worktree の管理 CLI/UI。他トラックへの依存なし。
|
||||
|
||||
## 現状の問題
|
||||
|
||||
- ハートビートセッションが `.worktrees/heartbeat-YYYYMMDD/` を作るが、人手で管理する手段がない
|
||||
- `PruneOrphaned()` は起動時にディレクトリを削除するが、**未コミット変更は通知なく消失**する
|
||||
- active worktree の一覧・点検・手動 merge/dispose ができない
|
||||
|
||||
---
|
||||
|
||||
## タスク一覧
|
||||
|
||||
### 1. `/plan worktrees` コマンド
|
||||
|
||||
worktree の一覧表示と操作を提供する CLI コマンド。
|
||||
|
||||
**サブコマンド:**
|
||||
- `/plan worktrees list` — active worktree の一覧 (branch, last commit, status, uncommitted changes の有無)
|
||||
- `/plan worktrees inspect <name>` — 特定 worktree の詳細 (diff, log)
|
||||
- `/plan worktrees merge <name>` — main branch に merge を試行
|
||||
- `/plan worktrees dispose <name>` — worktree を削除 (未コミット変更がある場合は確認)
|
||||
|
||||
**対象ファイル:** `pkg/agent/loop.go` (コマンドハンドラ追加), `pkg/git/worktree.go`
|
||||
|
||||
---
|
||||
|
||||
### 2. PruneOrphaned の安全化
|
||||
|
||||
起動時の orphan prune で未コミット変更を保護する。
|
||||
|
||||
**やること:**
|
||||
- prune 前に `git status` で未コミット変更を検出
|
||||
- 未コミット変更がある場合: auto-commit (メッセージ: "auto-save before prune") してから dispose
|
||||
- auto-commit 結果をログに出力
|
||||
- commit 不可の場合 (conflict 等) はスキップしてログ警告
|
||||
|
||||
**対象ファイル:** `pkg/git/worktree.go`
|
||||
|
||||
---
|
||||
|
||||
### 3. Mini App worktree UI
|
||||
|
||||
Mini App から worktree を閲覧・操作できる画面。
|
||||
|
||||
**やること:**
|
||||
- `/api/worktrees` エンドポイント (GET: list, POST: merge/dispose)
|
||||
- Mini App に worktree 一覧パネルを追加
|
||||
- 各 worktree: branch 名、最終コミット日時、uncommitted changes badge
|
||||
- merge/dispose ボタン (確認ダイアログ付き)
|
||||
|
||||
**対象ファイル:** `pkg/miniapp/handler.go`, `pkg/miniapp/static/index.html`
|
||||
|
||||
---
|
||||
|
||||
## 完了基準
|
||||
|
||||
- `go test ./...` 全パス
|
||||
- `/plan worktrees list` で active worktree が表示される
|
||||
- orphan prune 時に未コミット変更が auto-commit で保護される
|
||||
- Mini App から worktree の一覧と dispose が操作できる
|
||||
Loading…
Add table
Reference in a new issue