マニフェストを継承してPodをスポーンさせる
This commit is contained in:
parent
25df7a79c1
commit
a89701bc43
1
TODO.md
1
TODO.md
|
|
@ -5,7 +5,6 @@
|
|||
- [ ] パーミッション: パターンベースのツール実行制御 → [tickets/permission-extension-point.md](tickets/permission-extension-point.md)
|
||||
- [ ] Pod オーケストレーション
|
||||
- [ ] 動的 Scope 変更 → [tickets/dynamic-scope.md](tickets/dynamic-scope.md)
|
||||
- [ ] SpawnPod が親の provider 設定を引き継ぐ → [tickets/spawn-inherit-provider.md](tickets/spawn-inherit-provider.md)
|
||||
- [ ] ネイティブ GUI クライアント MVP → [tickets/native-gui-mvp.md](tickets/native-gui-mvp.md)
|
||||
- [ ] TUI 拡充
|
||||
- [ ] 新しい Pod を spawn する UI の設計 → [tickets/tui-pod-spawn-ui.md](tickets/tui-pod-spawn-ui.md)
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ impl PodController {
|
|||
let scope_for_tools = pod.scope().clone();
|
||||
let pwd_for_tools = pod.pwd().to_path_buf();
|
||||
let spawner_name = pod.manifest().pod.name.clone();
|
||||
let spawner_provider = pod.manifest().provider.clone();
|
||||
|
||||
// Parent callback socket (this Pod's own parent, used for
|
||||
// `PodEvent` upward reports). `None` for top-level Pods.
|
||||
|
|
@ -229,6 +230,7 @@ impl PodController {
|
|||
pwd_for_tools,
|
||||
spawned_registry.clone(),
|
||||
self_parent_socket.clone(),
|
||||
spawner_provider.clone(),
|
||||
));
|
||||
worker.register_tool(send_to_pod_tool(spawned_registry.clone()));
|
||||
worker.register_tool(read_pod_output_tool(spawned_registry.clone()));
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ use std::time::Duration;
|
|||
use async_trait::async_trait;
|
||||
use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
|
||||
use manifest::{
|
||||
Permission, PodManifestConfig, PodMetaConfig, ScopeConfig, ScopeRule, WorkerManifestConfig,
|
||||
Permission, PodManifestConfig, PodMetaConfig, ProviderConfig, ProviderConfigPartial,
|
||||
ScopeConfig, ScopeRule, WorkerManifestConfig,
|
||||
};
|
||||
use protocol::Method;
|
||||
use protocol::stream::JsonLineWriter;
|
||||
|
|
@ -113,6 +114,11 @@ pub struct SpawnPodTool {
|
|||
/// `None` for top-level Pods — in that case the re-emission is a
|
||||
/// no-op.
|
||||
parent_socket: Option<PathBuf>,
|
||||
/// Spawner's resolved provider config — copied into every spawned
|
||||
/// Pod's overlay TOML so the child does not need its own provider
|
||||
/// configuration in the manifest cascade. Per-spawn override is
|
||||
/// out of scope here (see `tickets/spawn-inherit-provider.md`).
|
||||
spawner_provider: ProviderConfig,
|
||||
}
|
||||
|
||||
impl SpawnPodTool {
|
||||
|
|
@ -123,6 +129,7 @@ impl SpawnPodTool {
|
|||
spawner_pwd: PathBuf,
|
||||
registry: Arc<SpawnedPodRegistry>,
|
||||
parent_socket: Option<PathBuf>,
|
||||
spawner_provider: ProviderConfig,
|
||||
) -> Self {
|
||||
Self {
|
||||
spawner_name,
|
||||
|
|
@ -131,6 +138,7 @@ impl SpawnPodTool {
|
|||
spawner_pwd,
|
||||
registry,
|
||||
parent_socket,
|
||||
spawner_provider,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -184,7 +192,12 @@ impl Tool for SpawnPodTool {
|
|||
// it back — even if later steps (Method::Run delivery, record
|
||||
// write) fail, the child is running and will release its own
|
||||
// entry on exit.
|
||||
let overlay_toml = match build_overlay_toml(&input.name, &instruction, &scope_allow) {
|
||||
let overlay_toml = match build_overlay_toml(
|
||||
&input.name,
|
||||
&instruction,
|
||||
&scope_allow,
|
||||
&self.spawner_provider,
|
||||
) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
self.release_reservation(&lock_path, &input.name);
|
||||
|
|
@ -337,11 +350,18 @@ fn build_overlay_toml(
|
|||
name: &str,
|
||||
instruction: &str,
|
||||
scope_allow: &[ScopeRule],
|
||||
provider: &ProviderConfig,
|
||||
) -> Result<String, toml::ser::Error> {
|
||||
let overlay = PodManifestConfig {
|
||||
pod: PodMetaConfig {
|
||||
name: Some(name.to_string()),
|
||||
},
|
||||
provider: ProviderConfigPartial {
|
||||
kind: Some(provider.kind),
|
||||
model: Some(provider.model.clone()),
|
||||
api_key_file: provider.api_key_file.clone(),
|
||||
base_url: provider.base_url.clone(),
|
||||
},
|
||||
worker: WorkerManifestConfig {
|
||||
instruction: Some(instruction.to_string()),
|
||||
..Default::default()
|
||||
|
|
@ -438,6 +458,7 @@ pub fn spawn_pod_tool(
|
|||
spawner_pwd: PathBuf,
|
||||
registry: Arc<SpawnedPodRegistry>,
|
||||
parent_socket: Option<PathBuf>,
|
||||
spawner_provider: ProviderConfig,
|
||||
) -> ToolDefinition {
|
||||
Arc::new(move || {
|
||||
let schema = schemars::schema_for!(SpawnPodInput);
|
||||
|
|
@ -452,7 +473,38 @@ pub fn spawn_pod_tool(
|
|||
spawner_pwd.clone(),
|
||||
registry.clone(),
|
||||
parent_socket.clone(),
|
||||
spawner_provider.clone(),
|
||||
));
|
||||
(meta, tool)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use manifest::ProviderKind;
|
||||
|
||||
#[test]
|
||||
fn overlay_inherits_spawner_provider() {
|
||||
let provider = ProviderConfig {
|
||||
kind: ProviderKind::Anthropic,
|
||||
model: "claude-sonnet-4".into(),
|
||||
api_key_file: Some(PathBuf::from("/etc/keys/anthropic")),
|
||||
base_url: Some("https://example.test".into()),
|
||||
};
|
||||
|
||||
let toml_str = build_overlay_toml("child", "$insomnia/default", &[], &provider).unwrap();
|
||||
let parsed = PodManifestConfig::from_toml(&toml_str).unwrap();
|
||||
|
||||
assert_eq!(parsed.provider.kind, Some(ProviderKind::Anthropic));
|
||||
assert_eq!(parsed.provider.model.as_deref(), Some("claude-sonnet-4"));
|
||||
assert_eq!(
|
||||
parsed.provider.api_key_file.as_deref(),
|
||||
Some(Path::new("/etc/keys/anthropic"))
|
||||
);
|
||||
assert_eq!(
|
||||
parsed.provider.base_url.as_deref(),
|
||||
Some("https://example.test")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use std::path::{Path, PathBuf};
|
|||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
use llm_worker::tool::{ToolError, ToolOutput};
|
||||
use manifest::{Permission, ScopeRule};
|
||||
use manifest::{Permission, ProviderConfig, ProviderKind, ScopeRule};
|
||||
use pod::runtime_dir::{RuntimeDir, SpawnedPodRecord};
|
||||
use pod::scope_lock::{self, LockFileGuard};
|
||||
use pod::spawn_pod::spawn_pod_tool;
|
||||
|
|
@ -132,6 +132,18 @@ fn which_true() -> String {
|
|||
"/bin/true".into()
|
||||
}
|
||||
|
||||
/// Tests don't exercise the provider — they intercept the spawned
|
||||
/// child via a mock socket — but `spawn_pod_tool` needs a value to
|
||||
/// embed in the overlay TOML. Any well-formed `ProviderConfig` works.
|
||||
fn dummy_provider() -> ProviderConfig {
|
||||
ProviderConfig {
|
||||
kind: ProviderKind::Anthropic,
|
||||
model: "claude-test".into(),
|
||||
api_key_file: None,
|
||||
base_url: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_env() {
|
||||
unsafe {
|
||||
std::env::remove_var("INSOMNIA_SCOPE_LOCK");
|
||||
|
|
@ -159,6 +171,7 @@ async fn spawn_pod_delegates_scope_and_sends_run() {
|
|||
allow_root.path().to_path_buf(),
|
||||
registry,
|
||||
None,
|
||||
dummy_provider(),
|
||||
);
|
||||
let (_meta, tool) = def();
|
||||
|
||||
|
|
@ -221,6 +234,7 @@ async fn spawn_pod_rejects_scope_outside_spawner() {
|
|||
allow_root.path().to_path_buf(),
|
||||
registry,
|
||||
None,
|
||||
dummy_provider(),
|
||||
);
|
||||
let (_meta, tool) = def();
|
||||
|
||||
|
|
@ -279,6 +293,7 @@ async fn spawn_pod_rolls_back_reservation_when_socket_never_appears() {
|
|||
allow_root.path().to_path_buf(),
|
||||
registry,
|
||||
None,
|
||||
dummy_provider(),
|
||||
);
|
||||
let (_meta, tool) = def();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
# SpawnPod が親の provider 設定を引き継ぐ
|
||||
|
||||
## 背景
|
||||
|
||||
`SpawnPod` ツールは子 Pod に渡す overlay TOML に `pod.name` / `worker.instruction` / `scope` のみを含め、`provider` を含めない(`crates/pod/src/spawn_pod.rs::build_overlay_toml`)。
|
||||
|
||||
子 Pod は起動時に manifest cascade を一からやり直すため、デフォルトのインストラクション (`$insomnia/default`) を使うと `provider.kind` が見つからず `failed to resolve manifest cascade: missing required field: provider.kind` で起動失敗する。結果として `SpawnPod` ツールがデフォルト引数では事実上使えない状態になっている。
|
||||
|
||||
## ゴール
|
||||
|
||||
`SpawnPod` で起動した子 Pod が、特に指定なしで親 Pod と同じ provider 設定(kind, model, api_key 等)を使って起動できる。
|
||||
|
||||
## 必要な変更
|
||||
|
||||
- 親 Pod の resolved provider を `SpawnPodTool` から参照できるようにする(`SpawnPodTool::new` の引数追加 等)
|
||||
- `build_overlay_toml` が overlay に provider を含める
|
||||
- ツール入力 (`SpawnPodInput`) からの provider 指定は今回は不要(範囲外を参照)
|
||||
|
||||
## 完了条件
|
||||
|
||||
- `SpawnPod` を `provider` 指定なしで呼び出すと、子 Pod が親と同じ provider 設定で起動し、`Method::Run` を受けて応答できる
|
||||
- 既存の spawn_pod 統合テスト (`crates/pod/tests/spawn_pod_test.rs`) が引き続きパス
|
||||
- 親が provider を持たないケース(あり得るなら)の挙動が定まっている
|
||||
|
||||
## 範囲外
|
||||
|
||||
- ツール入力からの provider override(モデルだけ差し替えたい等のマルチエージェント用途)。将来別チケットで `SpawnPodInput` に optional provider override を追加する形で扱う
|
||||
- インストラクションファイル側で provider を指定する仕組み
|
||||
Loading…
Reference in New Issue
Block a user