feat: refresh prompts at operation boundaries

This commit is contained in:
2026-08-19 06:00:57 +09:00
parent 44b3c78761
commit d24d50cac9
3 changed files with 143 additions and 30 deletions
+64 -19
View File
@@ -11,6 +11,7 @@ use std::borrow::Cow;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use arc_swap::ArcSwap;
use async_trait::async_trait; use async_trait::async_trait;
use llm_engine::Item; use llm_engine::Item;
use llm_engine::UsageRecord; use llm_engine::UsageRecord;
@@ -64,7 +65,7 @@ pub(crate) struct WorkerInterceptor {
pending_attachments: Arc<Mutex<Vec<SystemItem>>>, pending_attachments: Arc<Mutex<Vec<SystemItem>>>,
/// Prompt catalog used to render pending notification entries into the /// Prompt catalog used to render pending notification entries into the
/// same system-message text that will be persisted in history. /// same system-message text that will be persisted in history.
prompts: Arc<PromptCatalog>, prompts: Arc<ArcSwap<PromptCatalog>>,
/// Type-erased commit handle. The interceptor uses it to commit /// Type-erased commit handle. The interceptor uses it to commit
/// `LogEntry::SystemItem` entries directly (sync) before /// `LogEntry::SystemItem` entries directly (sync) before
/// returning the corresponding `Item::system_message`s up to the /// returning the corresponding `Item::system_message`s up to the
@@ -84,7 +85,7 @@ impl WorkerInterceptor {
usage_history: Option<Arc<Mutex<Vec<UsageRecord>>>>, usage_history: Option<Arc<Mutex<Vec<UsageRecord>>>>,
pending_notifies: NotifyBuffer, pending_notifies: NotifyBuffer,
pending_attachments: Arc<Mutex<Vec<SystemItem>>>, pending_attachments: Arc<Mutex<Vec<SystemItem>>>,
prompts: Arc<PromptCatalog>, prompts: Arc<ArcSwap<PromptCatalog>>,
log_writer: Option<Arc<dyn SystemItemCommitter>>, log_writer: Option<Arc<dyn SystemItemCommitter>>,
) -> Self { ) -> Self {
Self { Self {
@@ -208,10 +209,11 @@ impl Interceptor for WorkerInterceptor {
return Ok(Vec::new()); return Ok(Vec::new());
} }
let prompts = self.prompts.load_full();
let mut system_items: Vec<SystemItem> = Vec::with_capacity(drained.len()); let mut system_items: Vec<SystemItem> = Vec::with_capacity(drained.len());
let mut items: Vec<Item> = Vec::with_capacity(drained.len()); let mut items: Vec<Item> = Vec::with_capacity(drained.len());
for entry in drained { for entry in drained {
match build_system_item(&entry, &self.prompts) { match build_system_item(&entry, &prompts) {
Ok(system_item) => { Ok(system_item) => {
items.push(system_item.to_history_item()); items.push(system_item.to_history_item());
system_items.push(system_item); system_items.push(system_item);
@@ -440,6 +442,10 @@ mod tests {
HookTurnEndAction, OnTurnEnd, PostToolCall, PreLlmRequest, PreToolCall, HookTurnEndAction, OnTurnEnd, PostToolCall, PreLlmRequest, PreToolCall,
}; };
fn test_prompts() -> Arc<ArcSwap<PromptCatalog>> {
Arc::new(ArcSwap::from(PromptCatalog::builtins_only().unwrap()))
}
struct CountingHook(Arc<AtomicUsize>); struct CountingHook(Arc<AtomicUsize>);
#[async_trait] #[async_trait]
@@ -541,7 +547,7 @@ mod tests {
Some(history), Some(history),
NotifyBuffer::new(), NotifyBuffer::new(),
Arc::new(Mutex::new(Vec::new())), Arc::new(Mutex::new(Vec::new())),
PromptCatalog::builtins_only().unwrap(), test_prompts(),
None, None,
); );
let mut ctx = ctx_items; let mut ctx = ctx_items;
@@ -571,7 +577,7 @@ mod tests {
Some(history), Some(history),
NotifyBuffer::new(), NotifyBuffer::new(),
Arc::new(Mutex::new(Vec::new())), Arc::new(Mutex::new(Vec::new())),
PromptCatalog::builtins_only().unwrap(), test_prompts(),
Some(Arc::new(RecordingSystemItemCommitter { Some(Arc::new(RecordingSystemItemCommitter {
committed: Arc::clone(&committed), committed: Arc::clone(&committed),
})), })),
@@ -609,7 +615,7 @@ mod tests {
Some(history), Some(history),
NotifyBuffer::new(), NotifyBuffer::new(),
Arc::new(Mutex::new(Vec::new())), Arc::new(Mutex::new(Vec::new())),
PromptCatalog::builtins_only().unwrap(), test_prompts(),
None, None,
) )
.with_usage_tracker(usage_tracker); .with_usage_tracker(usage_tracker);
@@ -634,7 +640,7 @@ mod tests {
Some(history), Some(history),
NotifyBuffer::new(), NotifyBuffer::new(),
Arc::new(Mutex::new(Vec::new())), Arc::new(Mutex::new(Vec::new())),
PromptCatalog::builtins_only().unwrap(), test_prompts(),
None, None,
); );
let mut ctx = ctx_items; let mut ctx = ctx_items;
@@ -675,7 +681,7 @@ mod tests {
Some(history), Some(history),
NotifyBuffer::new(), NotifyBuffer::new(),
Arc::new(Mutex::new(Vec::new())), Arc::new(Mutex::new(Vec::new())),
PromptCatalog::builtins_only().unwrap(), test_prompts(),
None, None,
); );
let mut ctx = ctx_items; let mut ctx = ctx_items;
@@ -702,7 +708,7 @@ mod tests {
Some(history), Some(history),
NotifyBuffer::new(), NotifyBuffer::new(),
Arc::new(Mutex::new(Vec::new())), Arc::new(Mutex::new(Vec::new())),
PromptCatalog::builtins_only().unwrap(), test_prompts(),
None, None,
); );
let mut ctx = ctx_items; let mut ctx = ctx_items;
@@ -723,7 +729,7 @@ mod tests {
None, None,
NotifyBuffer::new(), NotifyBuffer::new(),
Arc::new(Mutex::new(Vec::new())), Arc::new(Mutex::new(Vec::new())),
PromptCatalog::builtins_only().unwrap(), test_prompts(),
None, None,
); );
let mut ctx: Vec<Item> = Vec::new(); let mut ctx: Vec<Item> = Vec::new();
@@ -751,7 +757,7 @@ mod tests {
None, None,
NotifyBuffer::new(), NotifyBuffer::new(),
Arc::new(Mutex::new(Vec::new())), Arc::new(Mutex::new(Vec::new())),
PromptCatalog::builtins_only().unwrap(), test_prompts(),
Some(committer), Some(committer),
); );
@@ -798,7 +804,7 @@ mod tests {
None, None,
NotifyBuffer::new(), NotifyBuffer::new(),
Arc::new(Mutex::new(Vec::new())), Arc::new(Mutex::new(Vec::new())),
PromptCatalog::builtins_only().unwrap(), test_prompts(),
None, None,
); );
@@ -855,7 +861,7 @@ mod tests {
None, None,
NotifyBuffer::new(), NotifyBuffer::new(),
Arc::new(Mutex::new(Vec::new())), Arc::new(Mutex::new(Vec::new())),
PromptCatalog::builtins_only().unwrap(), test_prompts(),
None, None,
); );
let mut info = task_tool_call_info("TaskList", serde_json::json!({"scope": "all"})); let mut info = task_tool_call_info("TaskList", serde_json::json!({"scope": "all"}));
@@ -902,7 +908,7 @@ mod tests {
None, None,
NotifyBuffer::new(), NotifyBuffer::new(),
Arc::new(Mutex::new(Vec::new())), Arc::new(Mutex::new(Vec::new())),
PromptCatalog::builtins_only().unwrap(), test_prompts(),
None, None,
); );
let info = task_tool_call_info("TaskList", serde_json::json!({})); let info = task_tool_call_info("TaskList", serde_json::json!({}));
@@ -953,7 +959,7 @@ mod tests {
None, None,
NotifyBuffer::new(), NotifyBuffer::new(),
Arc::new(Mutex::new(Vec::new())), Arc::new(Mutex::new(Vec::new())),
PromptCatalog::builtins_only().unwrap(), test_prompts(),
None, None,
); );
let history = vec![Item::user_message("hi"), Item::assistant_message("done")]; let history = vec![Item::user_message("hi"), Item::assistant_message("done")];
@@ -985,7 +991,7 @@ mod tests {
None, None,
NotifyBuffer::new(), NotifyBuffer::new(),
Arc::new(Mutex::new(Vec::new())), Arc::new(Mutex::new(Vec::new())),
PromptCatalog::builtins_only().unwrap(), test_prompts(),
Some(Arc::new(RecordingSystemItemCommitter { Some(Arc::new(RecordingSystemItemCommitter {
committed: Arc::clone(&committed), committed: Arc::clone(&committed),
})), })),
@@ -1033,6 +1039,45 @@ mod tests {
assert!(body.contains("track active work")); assert!(body.contains("track active work"));
} }
#[tokio::test]
async fn pending_notifications_use_the_latest_prompt_projection() {
let prompts = test_prompts();
let buffer = NotifyBuffer::new();
let interceptor = WorkerInterceptor::new(
Arc::new(HookRegistryBuilder::new().build()),
None,
None,
buffer.clone(),
Arc::new(Mutex::new(Vec::new())),
prompts.clone(),
None,
);
let current = prompts.load_full();
let projection = current.projection();
let mut templates = projection.templates.clone();
templates.insert(
"internal.notify_wrapper".to_string(),
"CURRENT-PROJECTION {{ message }}".to_string(),
);
let mut projection = crate::prompt::catalog::EffectivePromptCatalog::new(
templates,
2,
projection.schema_fingerprint.clone(),
projection.toolchain_fingerprint.clone(),
)
.unwrap();
projection.source_digest = "source-2".to_string();
prompts.store(Arc::new(
PromptCatalog::from_projection(projection).unwrap(),
));
buffer.push_notify("updated".to_string(), false);
let appends = interceptor.pending_history_appends().await.unwrap();
assert_eq!(appends.len(), 1);
assert!(format!("{:?}", appends[0]).contains("CURRENT-PROJECTION updated"));
}
#[tokio::test] #[tokio::test]
async fn pending_history_appends_drains_buffer_into_items() { async fn pending_history_appends_drains_buffer_into_items() {
let registry = Arc::new(HookRegistryBuilder::new().build()); let registry = Arc::new(HookRegistryBuilder::new().build());
@@ -1046,7 +1091,7 @@ mod tests {
None, None,
buffer.clone(), buffer.clone(),
Arc::new(Mutex::new(Vec::new())), Arc::new(Mutex::new(Vec::new())),
PromptCatalog::builtins_only().unwrap(), test_prompts(),
None, None,
); );
@@ -1083,7 +1128,7 @@ mod tests {
None, None,
buffer.clone(), buffer.clone(),
Arc::new(Mutex::new(Vec::new())), Arc::new(Mutex::new(Vec::new())),
PromptCatalog::builtins_only().unwrap(), test_prompts(),
None, None,
); );
let mut ctx: Vec<Item> = vec![Item::user_message("hi")]; let mut ctx: Vec<Item> = vec![Item::user_message("hi")];
@@ -1113,7 +1158,7 @@ mod tests {
None, None,
NotifyBuffer::new(), NotifyBuffer::new(),
Arc::new(Mutex::new(Vec::new())), Arc::new(Mutex::new(Vec::new())),
PromptCatalog::builtins_only().unwrap(), test_prompts(),
None, None,
); );
let mut ctx: Vec<Item> = Vec::new(); let mut ctx: Vec<Item> = Vec::new();
+5 -3
View File
@@ -8,6 +8,7 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use arc_swap::ArcSwap;
use async_trait::async_trait; use async_trait::async_trait;
use llm_engine::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput}; use llm_engine::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
use manifest::{ use manifest::{
@@ -946,7 +947,7 @@ pub(crate) fn sub_worker_spawn_tool(
registry: Arc<SpawnedWorkerRegistry>, registry: Arc<SpawnedWorkerRegistry>,
spawner_manifest: WorkerManifest, spawner_manifest: WorkerManifest,
spawner_scope: SharedScope, spawner_scope: SharedScope,
prompts: Arc<PromptCatalog>, prompts: Arc<ArcSwap<PromptCatalog>>,
) -> ToolDefinition { ) -> ToolDefinition {
sub_worker_spawn_tool_impl( sub_worker_spawn_tool_impl(
spawner_name, spawner_name,
@@ -972,13 +973,14 @@ fn sub_worker_spawn_tool_impl(
registry: Arc<SpawnedWorkerRegistry>, registry: Arc<SpawnedWorkerRegistry>,
spawner_manifest: WorkerManifest, spawner_manifest: WorkerManifest,
spawner_scope: SharedScope, spawner_scope: SharedScope,
prompts: Arc<PromptCatalog>, prompts: Arc<ArcSwap<PromptCatalog>>,
) -> ToolDefinition { ) -> ToolDefinition {
Arc::new(move || { Arc::new(move || {
let schema = schemars::schema_for!(SubWorkerSpawnInput); let schema = schemars::schema_for!(SubWorkerSpawnInput);
let schema_value = serde_json::to_value(schema).unwrap_or(serde_json::json!({})); let schema_value = serde_json::to_value(schema).unwrap_or(serde_json::json!({}));
let available_profiles = AvailableProfiles::discover(&workspace_root); let available_profiles = AvailableProfiles::discover(&workspace_root);
let description = prompts let description = prompts
.load_full()
.sub_worker_spawn_tool_description( .sub_worker_spawn_tool_description(
&available_profiles.compact_list(), &available_profiles.compact_list(),
&available_profiles.default_label(), &available_profiles.default_label(),
@@ -1002,7 +1004,7 @@ fn sub_worker_spawn_tool_impl(
spawner_cwd.clone(), spawner_cwd.clone(),
registry.clone(), registry.clone(),
spawner_manifest.clone(), spawner_manifest.clone(),
prompts.source(), prompts.load_full().source(),
available_profiles, available_profiles,
spawner_scope.clone(), spawner_scope.clone(),
DelegationScope::from_config(&spawner_manifest.delegation_scope) DelegationScope::from_config(&spawner_manifest.delegation_scope)
+74 -8
View File
@@ -67,7 +67,7 @@ use crate::ipc::alerter::Alerter;
use crate::ipc::interceptor::WorkerInterceptor; use crate::ipc::interceptor::WorkerInterceptor;
use crate::ipc::notify_buffer::NotifyBuffer; use crate::ipc::notify_buffer::NotifyBuffer;
use crate::prompt::agents_md::read_agents_md; use crate::prompt::agents_md::read_agents_md;
use crate::prompt::catalog::{CatalogError, PromptCatalog}; use crate::prompt::catalog::{CatalogError, PromptCatalog, WorkspacePromptProjection};
use crate::prompt::source::PromptCatalogSource; use crate::prompt::source::PromptCatalogSource;
use crate::prompt::system::{SystemPromptContext, SystemPromptError, SystemPromptTemplate}; use crate::prompt::system::{SystemPromptContext, SystemPromptError, SystemPromptTemplate};
use crate::runtime::dir; use crate::runtime::dir;
@@ -224,6 +224,15 @@ pub trait WorkspaceClient: std::fmt::Debug + Send + Sync {
fn execute(&self, request: WorkspaceRequest) fn execute(&self, request: WorkspaceRequest)
-> Result<WorkspaceResponse, WorkspaceClientError>; -> Result<WorkspaceResponse, WorkspaceClientError>;
/// Resolve the Workspace's current immutable Prompt projection for future
/// operation boundaries. Creation and restore continue to use persisted
/// launch/session state; this hook never reconstructs historical prompts.
fn current_prompt_projection(
&self,
) -> Result<Option<WorkspacePromptProjection>, WorkspaceClientError> {
Ok(None)
}
/// Executes the destructive WorkerRemove operation through Runtime-owned source proof. /// Executes the destructive WorkerRemove operation through Runtime-owned source proof.
/// Target identity is operation data; source identity and permission are never caller inputs. /// Target identity is operation data; source identity and permission are never caller inputs.
fn execute_worker_remove( fn execute_worker_remove(
@@ -285,6 +294,12 @@ impl WorkspaceClient for ReviewerChildWorkspaceClient {
Some(&self.context) Some(&self.context)
} }
fn current_prompt_projection(
&self,
) -> Result<Option<WorkspacePromptProjection>, WorkspaceClientError> {
self.inner.current_prompt_projection()
}
fn execute( fn execute(
&self, &self,
mut request: WorkspaceRequest, mut request: WorkspaceRequest,
@@ -872,7 +887,7 @@ pub struct Worker<C: LlmClient, St: Store> {
/// sections, ...). Built from the 4-layer overlay in /// sections, ...). Built from the 4-layer overlay in
/// [`Self::from_manifest`], or defaults to the builtin pack when a /// [`Self::from_manifest`], or defaults to the builtin pack when a
/// Worker is constructed through lower-level paths that have no loader. /// Worker is constructed through lower-level paths that have no loader.
prompts: Arc<PromptCatalog>, prompts: Arc<ArcSwap<PromptCatalog>>,
/// When true (default), the system-prompt assembler may append resident /// When true (default), the system-prompt assembler may append resident
/// context from the workspace Memory document. Internal disposable /// context from the workspace Memory document. Internal disposable
/// workers disable this so resident memory exposure is opt-in per Worker. /// workers disable this so resident memory exposure is opt-in per Worker.
@@ -1146,7 +1161,7 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
// `set_system_prompt_template`) can be captured by `SegmentStart`. // `set_system_prompt_template`) can be captured by `SegmentStart`.
let session_id = session_store::new_session_id(); let session_id = session_store::new_session_id();
let segment_id = session_store::new_segment_id(); let segment_id = session_store::new_segment_id();
let prompts = PromptCatalog::builtins_only()?; let prompts = Arc::new(ArcSwap::from(PromptCatalog::builtins_only()?));
let delegation_scope = let delegation_scope =
DelegationScope::from_config(&manifest.delegation_scope).map_err(WorkerError::Scope)?; DelegationScope::from_config(&manifest.delegation_scope).map_err(WorkerError::Scope)?;
let scope = SharedScope::new(scope); let scope = SharedScope::new(scope);
@@ -1232,10 +1247,49 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
self.inject_resident_summary = enabled; self.inject_resident_summary = enabled;
} }
pub fn prompts(&self) -> Arc<PromptCatalog> { pub fn prompts(&self) -> Arc<ArcSwap<PromptCatalog>> {
Arc::clone(&self.prompts) Arc::clone(&self.prompts)
} }
fn refresh_prompt_projection_for_future_operations(&self) -> Result<(), WorkerError> {
// The launch catalog remains authoritative until the initial system
// Prompt has been rendered and committed. Later operation boundaries
// may adopt the Workspace's current immutable projection.
if self.system_prompt_template.is_some() {
return Ok(());
}
let Some(projection) = self
.workspace_context
.client()
.current_prompt_projection()
.map_err(|source| WorkerError::WorkspacePromptProjection {
message: source.to_string(),
})?
else {
return Ok(());
};
projection
.validate()
.map_err(|source| WorkerError::WorkspacePromptProjection {
message: source.to_string(),
})?;
let current = self.prompts.load();
if current.projection().config_revision == projection.config_revision
&& current.projection().source_digest == projection.source_digest
&& current.projection().catalog_digest == projection.projection_digest
{
return Ok(());
}
let catalog = PromptCatalog::load(
&PromptCatalogSource::builtins_only().with_effective_catalog(projection.catalog),
)
.map_err(|source| WorkerError::WorkspacePromptProjection {
message: source.to_string(),
})?;
self.prompts.store(catalog);
Ok(())
}
/// The current segment ID. Read lock-free from the shared session /// The current segment ID. Read lock-free from the shared session
/// pointer so fork-time swaps are observed immediately. /// pointer so fork-time swaps are observed immediately.
pub fn segment_id(&self) -> SegmentId { pub fn segment_id(&self) -> SegmentId {
@@ -2020,6 +2074,7 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
.local_working_directory() .local_working_directory()
.map(|local| local.cwd.display().to_string()) .map(|local| local.cwd.display().to_string())
.unwrap_or_else(|| "no local working directory".to_string()); .unwrap_or_else(|| "no local working directory".to_string());
let prompt_catalog = self.prompts.load_full();
let ctx = SystemPromptContext { let ctx = SystemPromptContext {
now: chrono::Utc::now(), now: chrono::Utc::now(),
cwd: cwd_for_prompt.into(), cwd: cwd_for_prompt.into(),
@@ -2029,7 +2084,7 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
feature_instructions: &self.feature_instructions, feature_instructions: &self.feature_instructions,
agents_md: agents_md_read.and_then(|read| read.body), agents_md: agents_md_read.and_then(|read| read.body),
resident_summary: resident_summary.as_deref(), resident_summary: resident_summary.as_deref(),
prompts: &self.prompts, prompts: &prompt_catalog,
}; };
let rendered = template let rendered = template
.render(&ctx) .render(&ctx)
@@ -2085,6 +2140,7 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
/// store, and runs pre-run compact (joining any in-flight memory task /// store, and runs pre-run compact (joining any in-flight memory task
/// first so extract sees a stable history range). /// first so extract sees a stable history range).
async fn prepare_for_run(&mut self) -> Result<(), WorkerError> { async fn prepare_for_run(&mut self) -> Result<(), WorkerError> {
self.refresh_prompt_projection_for_future_operations()?;
self.ensure_interceptor_installed(); self.ensure_interceptor_installed();
self.ensure_system_prompt_materialized().await?; self.ensure_system_prompt_materialized().await?;
self.cleanup_finished_memory_task(); self.cleanup_finished_memory_task();
@@ -2430,10 +2486,12 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
fn apply_interrupt_prep(&mut self) -> Result<(), WorkerError> { fn apply_interrupt_prep(&mut self) -> Result<(), WorkerError> {
let tool_result_summary = self let tool_result_summary = self
.prompts() .prompts()
.load_full()
.interrupt_tool_result_summary() .interrupt_tool_result_summary()
.map_err(WorkerError::from)?; .map_err(WorkerError::from)?;
let system_note = self let system_note = self
.prompts() .prompts()
.load_full()
.interrupt_system_note() .interrupt_system_note()
.map_err(WorkerError::from)?; .map_err(WorkerError::from)?;
@@ -3173,6 +3231,7 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
let summary_client: Box<dyn LlmClient> = self.build_compactor_client()?; let summary_client: Box<dyn LlmClient> = self.build_compactor_client()?;
let summary_system_prompt = self let summary_system_prompt = self
.prompts .prompts
.load_full()
.compact_system() .compact_system()
.map_err(WorkerError::PromptCatalog)?; .map_err(WorkerError::PromptCatalog)?;
let mut summary_worker = Engine::new(summary_client).system_prompt(summary_system_prompt); let mut summary_worker = Engine::new(summary_client).system_prompt(summary_system_prompt);
@@ -3802,7 +3861,11 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
} }
}; };
let memory_language = memory_language(memory_cfg); let memory_language = memory_language(memory_cfg);
let extract_system_prompt = match self.prompts.memory_extract_system(memory_language) { let extract_system_prompt = match self
.prompts
.load_full()
.memory_extract_system(memory_language)
{
Ok(prompt) => prompt, Ok(prompt) => prompt,
Err(err) => { Err(err) => {
audit audit
@@ -5446,6 +5509,9 @@ pub enum WorkerError {
#[error(transparent)] #[error(transparent)]
PromptCatalog(#[from] CatalogError), PromptCatalog(#[from] CatalogError),
#[error("failed to resolve current Workspace Prompt projection: {message}")]
WorkspacePromptProjection { message: String },
#[error(transparent)] #[error(transparent)]
Skill(#[from] SkillClientError), Skill(#[from] SkillClientError),
@@ -5513,7 +5579,7 @@ struct WorkerCommon {
scope: Scope, scope: Scope,
delegation_scope: DelegationScope, delegation_scope: DelegationScope,
client: Box<dyn LlmClient>, client: Box<dyn LlmClient>,
prompts: Arc<PromptCatalog>, prompts: Arc<ArcSwap<PromptCatalog>>,
system_prompt_template: Option<SystemPromptTemplate>, system_prompt_template: Option<SystemPromptTemplate>,
feature_instructions: Vec<FeatureInstructionDeclaration>, feature_instructions: Vec<FeatureInstructionDeclaration>,
} }
@@ -5655,7 +5721,7 @@ fn prepare_worker_common_from_scope(
DelegationScope::from_config(&manifest.delegation_scope).map_err(WorkerError::Scope)?; DelegationScope::from_config(&manifest.delegation_scope).map_err(WorkerError::Scope)?;
let client = crate::model_client::build_client(&manifest.model)?; let client = crate::model_client::build_client(&manifest.model)?;
let prompts = PromptCatalog::load(loader)?; let prompts = Arc::new(ArcSwap::from(PromptCatalog::load(loader)?));
let system_prompt_template = if parse_template { let system_prompt_template = if parse_template {
Some( Some(
SystemPromptTemplate::parse(&manifest.engine.instruction, loader.clone()) SystemPromptTemplate::parse(&manifest.engine.instruction, loader.clone())