From e6703ed18a8b98b3cec053ba2d52426ce701d282 Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 7 Aug 2026 02:15:00 +0900 Subject: [PATCH] worker: preserve prompt overrides in internal children --- crates/worker/src/prompt/catalog.rs | 7 ++++++- crates/worker/src/spawn/tool.rs | 29 +++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/crates/worker/src/prompt/catalog.rs b/crates/worker/src/prompt/catalog.rs index 3c780a0c..12c54458 100644 --- a/crates/worker/src/prompt/catalog.rs +++ b/crates/worker/src/prompt/catalog.rs @@ -251,6 +251,7 @@ struct PackFile { /// `$yoi` / `$user` / `$workspace`. pub struct PromptCatalog { env: Environment<'static>, + loader: PromptLoader, } impl std::fmt::Debug for PromptCatalog { @@ -260,6 +261,10 @@ impl std::fmt::Debug for PromptCatalog { } impl PromptCatalog { + pub(crate) fn loader(&self) -> PromptLoader { + self.loader.clone() + } + /// Builtin-only catalog. All `{% include %}` references must resolve /// through `$yoi` (user/workspace prefixes are unavailable). pub fn builtins_only() -> Result, CatalogError> { @@ -483,7 +488,7 @@ fn build_catalog( })?; } - Ok(PromptCatalog { env }) + Ok(PromptCatalog { env, loader }) } #[cfg(test)] diff --git a/crates/worker/src/spawn/tool.rs b/crates/worker/src/spawn/tool.rs index b697117b..9b6d1ad0 100644 --- a/crates/worker/src/spawn/tool.rs +++ b/crates/worker/src/spawn/tool.rs @@ -226,6 +226,7 @@ pub struct SubWorkerSpawnTool { /// child config from reusable fields here, and selected profiles are /// merged into the same internal handoff shape before launch. spawner_manifest: WorkerManifest, + prompt_loader: PromptLoader, /// Compact selector list shared by tool description and diagnostics. available_profiles: AvailableProfiles, /// Spawner's runtime scope. After a successful spawn, the @@ -259,6 +260,7 @@ impl SubWorkerSpawnTool { spawner_cwd: PathBuf, registry: Arc, spawner_manifest: WorkerManifest, + prompt_loader: PromptLoader, available_profiles: AvailableProfiles, spawner_scope: SharedScope, delegation_scope: DelegationScope, @@ -272,6 +274,7 @@ impl SubWorkerSpawnTool { spawner_cwd, registry, spawner_manifest, + prompt_loader, available_profiles, spawner_scope, delegation_scope, @@ -340,7 +343,7 @@ impl Tool for SubWorkerSpawnTool { let mut child = Worker::, EphemeralSessionStore>::from_internal_manifest_with_context( child_manifest, store.clone(), - PromptLoader::builtins_only(), + self.prompt_loader.clone(), self.workspace_context.clone(), filesystem_authority, self.internal_client_override @@ -803,6 +806,7 @@ fn sub_worker_spawn_tool_impl( spawner_cwd.clone(), registry.clone(), spawner_manifest.clone(), + prompts.loader(), available_profiles, spawner_scope.clone(), DelegationScope::from_config(&spawner_manifest.delegation_scope) @@ -858,6 +862,15 @@ mod tests { ); let calls = Arc::new(AtomicUsize::new(0)); let observed_parent_write_revoked = Arc::new(std::sync::atomic::AtomicBool::new(false)); + let observed_instruction_override = Arc::new(std::sync::atomic::AtomicBool::new(false)); + let workspace_prompts = runtime.path().join("workspace-prompts"); + std::fs::create_dir_all(&workspace_prompts).unwrap(); + std::fs::write( + workspace_prompts.join("custom-reviewer.md"), + "WORKSPACE REVIEWER OVERRIDE", + ) + .unwrap(); + let prompt_loader = PromptLoader::new(None, Some(workspace_prompts)); let parent_notifies = crate::ipc::notify_buffer::NotifyBuffer::new(); let tool = SubWorkerSpawnTool::new( "parent".into(), @@ -868,6 +881,7 @@ mod tests { workspace_root.clone(), registry.clone(), manifest.clone(), + prompt_loader, AvailableProfiles::discover(&workspace_root), spawner_scope.clone(), DelegationScope::from_config(&manifest.delegation_scope).unwrap(), @@ -877,10 +891,12 @@ mod tests { parent_scope: spawner_scope.clone(), delegated_path: workspace_root.clone(), observed_parent_write_revoked: observed_parent_write_revoked.clone(), + observed_instruction_override: observed_instruction_override.clone(), })); let input = serde_json::json!({ "name": "reviewer-child", "profile": "builtin:reviewer", + "instruction": "$workspace/custom-reviewer", "task": "review immutable commit", "scope": [{ "target": workspace_root.clone(), @@ -909,6 +925,7 @@ mod tests { ); assert_eq!(calls.load(Ordering::SeqCst), 1); assert!(observed_parent_write_revoked.load(Ordering::SeqCst)); + assert!(observed_instruction_override.load(Ordering::SeqCst)); assert_eq!(parent_notifies.len(), 1); assert!(!runtime.path().join("reviewer-child/sock").exists()); @@ -1080,6 +1097,7 @@ mod tests { parent_scope: SharedScope, delegated_path: PathBuf, observed_parent_write_revoked: Arc, + observed_instruction_override: Arc, } #[async_trait] @@ -1090,7 +1108,7 @@ mod tests { async fn stream( &self, - _request: Request, + request: Request, ) -> Result> + Send>>, ClientError> { self.calls.fetch_add(1, Ordering::SeqCst); @@ -1101,6 +1119,13 @@ mod tests { .is_writable(&self.delegated_path), Ordering::SeqCst, ); + self.observed_instruction_override.store( + request + .system_prompt + .as_deref() + .is_some_and(|prompt| prompt.contains("WORKSPACE REVIEWER OVERRIDE")), + Ordering::SeqCst, + ); Ok(Box::pin(futures::stream::iter(vec![ Ok(LlmEvent::text_block_start(0)), Ok(LlmEvent::text_delta(0, "reviewed")),