SpawnPodツールが落ちる問題の発見
This commit is contained in:
parent
ddd7327290
commit
25df7a79c1
1
TODO.md
1
TODO.md
|
|
@ -5,6 +5,7 @@
|
|||
- [ ] パーミッション: パターンベースのツール実行制御 → [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)
|
||||
|
|
|
|||
|
|
@ -194,7 +194,9 @@ impl Tool for SpawnPodTool {
|
|||
}
|
||||
};
|
||||
|
||||
let start_outcome = self.exec_child(&overlay_toml, &predicted_socket).await;
|
||||
let start_outcome = self
|
||||
.exec_child(&input.name, &overlay_toml, &predicted_socket)
|
||||
.await;
|
||||
if let Err(e) = start_outcome {
|
||||
self.release_reservation(&lock_path, &input.name);
|
||||
return Err(e);
|
||||
|
|
@ -242,11 +244,31 @@ impl Tool for SpawnPodTool {
|
|||
impl SpawnPodTool {
|
||||
async fn exec_child(
|
||||
&self,
|
||||
pod_name: &str,
|
||||
overlay_toml: &str,
|
||||
predicted_socket: &Path,
|
||||
) -> Result<(), ToolError> {
|
||||
let pod_command = std::env::var("INSOMNIA_POD_COMMAND").unwrap_or_else(|_| "pod".into());
|
||||
|
||||
// Pre-create the child's runtime dir so we have a stable place to
|
||||
// capture its stderr before it has had a chance to bind anything.
|
||||
// The child's own `RuntimeDir::create` will `create_dir_all` the
|
||||
// same path again — that's idempotent. On clean exit the child's
|
||||
// RuntimeDir Drop tears the dir (and this log) down with it.
|
||||
let pod_runtime_dir = self.runtime_base.join(pod_name);
|
||||
tokio::fs::create_dir_all(&pod_runtime_dir)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ToolError::ExecutionFailed(format!(
|
||||
"create runtime dir {}: {e}",
|
||||
pod_runtime_dir.display()
|
||||
))
|
||||
})?;
|
||||
let stderr_path = pod_runtime_dir.join("stderr.log");
|
||||
let stderr_file = std::fs::File::create(&stderr_path).map_err(|e| {
|
||||
ToolError::ExecutionFailed(format!("open {}: {e}", stderr_path.display()))
|
||||
})?;
|
||||
|
||||
let mut cmd = Command::new(&pod_command);
|
||||
cmd.arg("--adopt")
|
||||
.arg("--callback")
|
||||
|
|
@ -256,7 +278,7 @@ impl SpawnPodTool {
|
|||
.current_dir(&self.spawner_pwd)
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.stderr(Stdio::from(stderr_file))
|
||||
.process_group(0);
|
||||
|
||||
let child = cmd
|
||||
|
|
@ -269,7 +291,10 @@ impl SpawnPodTool {
|
|||
// orphans. Lifecycle tracking lives in `spawned_pods.json`.
|
||||
drop(child);
|
||||
|
||||
wait_for_socket(predicted_socket, SOCKET_WAIT_TIMEOUT).await
|
||||
match wait_for_socket(predicted_socket, SOCKET_WAIT_TIMEOUT).await {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => Err(annotate_with_stderr(e, &stderr_path).await),
|
||||
}
|
||||
}
|
||||
|
||||
fn release_reservation(&self, lock_path: &Path, pod_name: &str) {
|
||||
|
|
@ -330,6 +355,33 @@ fn build_overlay_toml(
|
|||
toml::to_string(&overlay)
|
||||
}
|
||||
|
||||
/// Tail of the spawned child's `stderr.log` to splice into a startup
|
||||
/// failure message. Capped so a chatty child can't blow up the LLM's
|
||||
/// tool-result budget — debugging beyond this should read the file
|
||||
/// directly.
|
||||
const STDERR_TAIL_BYTES: usize = 4 * 1024;
|
||||
|
||||
async fn annotate_with_stderr(err: ToolError, stderr_path: &Path) -> ToolError {
|
||||
let tail = match tokio::fs::read(stderr_path).await {
|
||||
Ok(bytes) => {
|
||||
let start = bytes.len().saturating_sub(STDERR_TAIL_BYTES);
|
||||
String::from_utf8_lossy(&bytes[start..]).into_owned()
|
||||
}
|
||||
Err(_) => return err,
|
||||
};
|
||||
let trimmed = tail.trim();
|
||||
if trimmed.is_empty() {
|
||||
return err;
|
||||
}
|
||||
match err {
|
||||
ToolError::ExecutionFailed(msg) => ToolError::ExecutionFailed(format!(
|
||||
"{msg}\n--- child stderr ({}) ---\n{trimmed}",
|
||||
stderr_path.display()
|
||||
)),
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_socket(path: &Path, timeout: Duration) -> Result<(), ToolError> {
|
||||
let deadline = tokio::time::Instant::now() + timeout;
|
||||
loop {
|
||||
|
|
|
|||
28
tickets/spawn-inherit-provider.md
Normal file
28
tickets/spawn-inherit-provider.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# 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