feat: refresh prompts at operation boundaries
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
Reference in New Issue
Block a user