Agent Framework — 設計詳細 (仮)

[仮版・concept design] agent-framework-scope でスコープが「realtime agent framework + Rails 流モジュラー」に固まったのを受けて、その下層の 5 つの設計詳細(package 粒度 / state model API / realtime API / command protocol 型表現 / 名前選定基準)を概念設計する。実装はしない。各節は推奨案 + open questions を残す形で書く。

1. パッケージ分割の粒度

Rails の参考

Rails は meta-gem rails の下に概ね次の component gem を持つ:

activesupport / activemodel / activerecord / actionpack / actionview /
actionmailer / activejob / actioncable / activestorage / actiontext / railties

10+ の sub-gem。理由:

Almide 版での候補

almide-agent-framework の 7 抽象(L0–L7、L1 は外部)+ realtime をどう束ねるか。3 つの粒度案:

案構成評価
α 粗(3)core (L0/L2/L3/L4/L7 + realtime) / permit / observe✗ realtime が core に張り付き、外せない
β 中(5)core (L0/L2/L3/L4/L7) / realtime / permit / memory-persistent / observe + meta✓ Rails 流、各 layer 独立
γ 細(8+)L0/L2/L3/L4 を更に分割✗ 過剰分割、単独使用ニーズが薄い層が多い

推奨: β(5 sub-package + meta)

?-core               L0 message + L2 loop + L3 dispatch + L4 state(in-memory) + L7 testing
?-realtime           push driver / timing / backpressure / protocol contract
?-permit             L5 permission resolver + 危険パターン検出
?-memory-persistent  L4 拡張(DB / Redis / file 等のアダプタ)
?-observe            L6 trace events + token usage + latency
?                    meta package — 全部 bundle(Rails 相当)

almai                framework に取り込まず外部依存(L1 provider)

判断軸:

Open questions

2. L4 State Model の API

Almide の素材

設計選択肢

A. 単一 record 全部入り

type AgentState = {
  history: List[Message],
  memory: Memory,
  scene: Option[SceneState],
  ...
}

B. extensible / generic record

type CoreAgentState[T] = {
  history: List[Message],
  memory: Memory,
  domain: T,
}

C. composable separate records(推奨)

type CoreState     = { history: List[Message], memory: Memory }
type RealtimeState = { tick: Int, last_event_ms: Int }
type SceneState    = { avatar: Avatar, camera: Camera, expression: Expression }

// AITubeStudio が組み立てる:
type StudioState = {
  core: CoreState,
  realtime: RealtimeState,
  scene: SceneState,
}

// homullus が組み立てる:
type HomulluState = {
  core: CoreState,
  repl: ReplState,
}

推奨: C + ヘルパーアクセサ

C を採用しつつ、framework が core_history(state) のような top-level アクセサを提供して nested の冗長を緩和する。Almide の lambda + パイプとも相性が良い:

state |> core_history |> list.last

Open questions

3. Realtime Primitives の API

必要なもの

Almide の素材

設計選択肢

event-driven loop:

effect fn handle_event(state: State, event: Event) -> Result[State, RtError]

realtime.run(source, handle_event, opts)
// source: イベントの出元(WebSocket / frame timer / etc.)
// opts: backpressure policy, deadline, trace hook

timing 契約の表現:

annotation(@deadline(16))は Almide にない。代替案:

// 戻り値型に deadline 違反を含める
type FrameResult[S] = | OnTime(S) | Late(S, lag_ms: Int) | Timeout

戻り値で表現するほうが型に乗り、effect fn と整合する。

backpressure:

type Backpressure =
  | DropOldest
  | DropNewest
  | Coalesce(merge_fn: (Event, Event) -> Event)
  | Block

policy として渡す。

non-blocking 保証:

Almide が effect fn に “blocking” マーカーを持つかどうか次第。現時点では型に出ない可能性が高いので、規約 + lint で縛るのが現実的(“?-realtime 内では fs.read_text を呼ばない” 等)。

推奨: event-driven + 戻り値型での timing 表現 + policy 引数 backpressure

ただし non-blocking 保証は型では完全には縛れない。lint と doc で誘導する形を取る。

Open questions

4. Command Protocol の型表現

openaituber の WebSocket commands(setEmotion / lookAt / speak / playFbx / setCamera / setBackground)と homullus の tools(Bash / Read / Write / …)を、framework の dispatch surface でどう型付けするか。

設計選択肢

A. variant 型で閉じた集合

type Command =
  | SetEmotion(name: String)
  | LookAt(yaw: Float, pitch: Float)
  | Speak(url: String)
  ...

effect fn dispatch(state: State, cmd: Command) -> Result[State, String]

B. dispatch table with effect fn references

type Handler = (State, Value) -> Result[State, String]
let commands: Map[String, Handler] = ["setEmotion": ..., ...]

C. typed registration(推奨)

type CommandSpec[A] = {
  name: String,
  decode: (Value) -> Result[A, String],
  handler: (State, A) -> Result[State, String],
}

// 各 command を typed で登録
let set_emotion = CommandSpec[SetEmotionArgs](
  name: "setEmotion",
  decode: SetEmotionArgs.decode,
  handler: ...,
)

// dispatch surface に bundle
let surface = dispatch.surface([set_emotion, look_at, ...])

推奨: C(typed registration)

Almide の Type.decode(value) / Type.encode(value) codec convention をそのまま活かせる。CommandSpec を framework の dispatch surface の中核型として置く。

Open questions

5. Framework 名 — 選定基準と候補レジスタ

framework name は意図的に未決のまま据え置く(almide-agent-framework の方針)。本節はあくまで 選定基準 と 候補のレジスタ(傾向) を整理するもので、命名は AITubeStudio v0 が動いた段階に持ち越す。

選定基準

#基準根拠
1短い(1–3 音節)Rails(1 音節)、Ruby(2 音節)の伝統
2専用 GitHub org が空いているalmide-agent-framework org 構造の前提
3agent / runtime / direction を喚起framework のドメインを示唆
4Almide エコシステム命名と整合bonsai / homullus / lumen / prism / syschord の poetic / Latin 系
5特定ドメインに閉じないAITuber 専用に聞こえないこと
6意味が狭すぎない”Reins” の反省(“制御” に寄りすぎた)
7AI 時代の構造的断絶を示唆Rails が “線路” だったように、何か別の motif

候補のレジスタ(方向性のみ、特定の名前は決めない)

早すぎる命名のリスク

推奨タイミング

それまでは “Almide エージェントフレームワーク” / “framework” / ”?” の placeholder で planning を進める。

5 項目の関係構造

flowchart TB
    Scope[scope: realtime agent FW<br/>Rails 流モジュラー]
    Scope --> Pkg[1. パッケージ粒度<br/>core / realtime / permit / memory / observe + meta]
    Scope --> State[2. State model API<br/>composable separate records]
    Scope --> RT[3. Realtime API<br/>event-driven + 戻り値型 timing]
    Scope --> Cmd[4. Command 型表現<br/>typed registration]
    Pkg --> Name[5. 名前選定基準<br/>命名は v0 後に持ち越す]
    State --> Name
    RT --> Name
    Cmd --> Name

5 つは独立ではなく、1〜4 が固まって初めて 5 の名前が決められる 構造。1〜4 のいずれかが大きく動けば、framework の輪郭が変わり、ふさわしい名前のレジスタも変わる。

押さえどころ(カード化候補)

Graph