docs: add revised Session DAG design with SBC-friendly constraints
Append refined design below the initial DAG Migration Plan, based on SBC performance constraints and design review: - Session-internal linearity, inter-session DAG (no per-message nodes) - SQLite single-file backend instead of per-node JSON files - Subagent reports as user-role turns instead of system-role merge nodes - Turn-granularity segments with cached linear view - Concrete Go interfaces (SessionStore, SessionGraph, TurnWriter) - 4-phase migration path with LegacyAdapter for backward compatibility Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c6d08938f2
commit
1122ccde71
1 changed files with 211 additions and 0 deletions
211
CLAUDE.md
211
CLAUDE.md
|
|
@ -669,3 +669,214 @@ capability は type assertion で解決し、非対応時は deterministic fallb
|
|||
- 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` コマンド群
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue