update: memoryシステム周りのプロンプトの整理
This commit is contained in:
+13
-3
@@ -1549,7 +1549,11 @@ impl<C: LlmClient, St: Store> Pod<C, St> {
|
||||
.unwrap_or(manifest::defaults::MEMORY_EXTRACT_WORKER_MAX_INPUT_TOKENS);
|
||||
|
||||
let client = self.build_extractor_client(memory_cfg)?;
|
||||
let mut extract_worker = Worker::new(client).system_prompt(extract::EXTRACT_SYSTEM_PROMPT);
|
||||
let extract_system_prompt = self
|
||||
.prompts
|
||||
.memory_extract_system()
|
||||
.map_err(PodError::PromptCatalog)?;
|
||||
let mut extract_worker = Worker::new(client).system_prompt(extract_system_prompt);
|
||||
extract_worker.set_cache_key(Some(self.session_id.to_string()));
|
||||
|
||||
// Cumulative input-token meter + interceptor (mirror of
|
||||
@@ -1742,8 +1746,14 @@ impl<C: LlmClient, St: Store> Pod<C, St> {
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
let mut worker =
|
||||
Worker::new(client).system_prompt(consolidate::CONSOLIDATION_SYSTEM_PROMPT);
|
||||
let consolidation_system_prompt = match self.prompts.memory_consolidation_system() {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
lock.release_only();
|
||||
return Err(PodError::PromptCatalog(e));
|
||||
}
|
||||
};
|
||||
let mut worker = Worker::new(client).system_prompt(consolidation_system_prompt);
|
||||
worker.set_cache_key(Some(self.session_id.to_string()));
|
||||
|
||||
// Memory tools are self-contained — they bypass ScopedFs and write
|
||||
|
||||
@@ -61,6 +61,10 @@ const INTERNAL_TOML: &str = include_str!("../../../../resources/prompts/internal
|
||||
pub enum PodPrompt {
|
||||
/// System prompt of the compaction (summary) Worker.
|
||||
CompactSystem,
|
||||
/// System prompt of the memory Phase 1 (extract) Worker.
|
||||
MemoryExtractSystem,
|
||||
/// System prompt of the memory Phase 2 (consolidation + tidy) Worker.
|
||||
MemoryConsolidationSystem,
|
||||
/// Wrapper around an incoming `Method::Notify` message injected into
|
||||
/// the next LLM request context as a transient system message.
|
||||
NotifyWrapper,
|
||||
@@ -89,6 +93,8 @@ impl PodPrompt {
|
||||
pub fn key(self) -> &'static str {
|
||||
match self {
|
||||
Self::CompactSystem => "compact_system",
|
||||
Self::MemoryExtractSystem => "memory_extract_system",
|
||||
Self::MemoryConsolidationSystem => "memory_consolidation_system",
|
||||
Self::NotifyWrapper => "notify_wrapper",
|
||||
Self::InterruptToolResultSummary => "interrupt_tool_result_summary",
|
||||
Self::InterruptSystemNote => "interrupt_system_note",
|
||||
@@ -104,6 +110,8 @@ impl PodPrompt {
|
||||
/// `INTERNAL_KEYS` (generated by `build.rs`).
|
||||
pub const ALL: &'static [PodPrompt] = &[
|
||||
PodPrompt::CompactSystem,
|
||||
PodPrompt::MemoryExtractSystem,
|
||||
PodPrompt::MemoryConsolidationSystem,
|
||||
PodPrompt::NotifyWrapper,
|
||||
PodPrompt::InterruptToolResultSummary,
|
||||
PodPrompt::InterruptSystemNote,
|
||||
@@ -115,6 +123,8 @@ impl PodPrompt {
|
||||
|
||||
pub const KEYS: &'static [&'static str] = &[
|
||||
"compact_system",
|
||||
"memory_extract_system",
|
||||
"memory_consolidation_system",
|
||||
"notify_wrapper",
|
||||
"interrupt_tool_result_summary",
|
||||
"interrupt_system_note",
|
||||
@@ -301,6 +311,16 @@ impl PromptCatalog {
|
||||
self.render(PodPrompt::CompactSystem, Value::UNDEFINED)
|
||||
}
|
||||
|
||||
/// Render `PodPrompt::MemoryExtractSystem` (no inputs).
|
||||
pub fn memory_extract_system(&self) -> Result<String, CatalogError> {
|
||||
self.render(PodPrompt::MemoryExtractSystem, Value::UNDEFINED)
|
||||
}
|
||||
|
||||
/// Render `PodPrompt::MemoryConsolidationSystem` (no inputs).
|
||||
pub fn memory_consolidation_system(&self) -> Result<String, CatalogError> {
|
||||
self.render(PodPrompt::MemoryConsolidationSystem, Value::UNDEFINED)
|
||||
}
|
||||
|
||||
/// Render `PodPrompt::NotifyWrapper` with `{{ message }}`.
|
||||
pub fn notify_wrapper(&self, message: &str) -> Result<String, CatalogError> {
|
||||
self.render(PodPrompt::NotifyWrapper, single("message", message))
|
||||
|
||||
@@ -570,6 +570,64 @@ async fn notify_while_idle_auto_starts_turn_and_injects_system_message() {
|
||||
assert!(last_item_text.contains("not a blocking request"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pod_event_turn_ended_while_idle_auto_starts_turn_and_injects_system_message() {
|
||||
let client = MockClient::new(simple_text_events());
|
||||
let client_for_assert = client.clone();
|
||||
let pod = make_pod(client).await;
|
||||
let handle = spawn_controller(pod).await;
|
||||
let mut rx = handle.subscribe();
|
||||
|
||||
handle
|
||||
.send(Method::PodEvent(protocol::PodEvent::TurnEnded {
|
||||
pod_name: "child".into(),
|
||||
}))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut saw_turn_end = false;
|
||||
let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(2);
|
||||
loop {
|
||||
tokio::select! {
|
||||
event = rx.recv() => {
|
||||
match event {
|
||||
Ok(Event::TurnEnd { .. }) => { saw_turn_end = true; break; }
|
||||
Err(_) => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ = tokio::time::sleep_until(deadline) => break,
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
saw_turn_end,
|
||||
"PodEvent::TurnEnded on idle Pod should auto-start a turn"
|
||||
);
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
assert_eq!(handle.shared_state.get_status(), PodStatus::Idle);
|
||||
|
||||
let requests = client_for_assert.captured_requests();
|
||||
assert_eq!(
|
||||
requests.len(),
|
||||
1,
|
||||
"auto-kick should issue exactly one LLM request"
|
||||
);
|
||||
let last_item_text = requests[0]
|
||||
.items
|
||||
.last()
|
||||
.and_then(|i| i.as_text())
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
assert!(
|
||||
last_item_text.contains("[Notification]"),
|
||||
"injected system message missing, got: {last_item_text:?}"
|
||||
);
|
||||
assert!(
|
||||
last_item_text.contains("child") && last_item_text.contains("finished a turn"),
|
||||
"rendered TurnEnded text missing, got: {last_item_text:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn notify_while_running_does_not_emit_already_running_error() {
|
||||
let client = MockClient::new(simple_text_events());
|
||||
@@ -669,6 +727,61 @@ async fn socket_run_receives_events() {
|
||||
assert!(saw_turn_end, "should see turn_end via socket");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn socket_pod_event_turn_ended_while_idle_auto_starts_turn() {
|
||||
use protocol::stream::{JsonLineReader, JsonLineWriter};
|
||||
use tokio::net::UnixStream;
|
||||
|
||||
let client = MockClient::new(simple_text_events());
|
||||
let pod = make_pod(client).await;
|
||||
let handle = spawn_controller(pod).await;
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
|
||||
let sock_path = handle.runtime_dir.socket_path();
|
||||
let stream = UnixStream::connect(&sock_path).await.unwrap();
|
||||
let (reader, writer) = stream.into_split();
|
||||
let mut reader = JsonLineReader::new(reader);
|
||||
let mut writer = JsonLineWriter::new(writer);
|
||||
|
||||
writer
|
||||
.write(&Method::PodEvent(protocol::PodEvent::TurnEnded {
|
||||
pod_name: "child".into(),
|
||||
}))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut saw_turn_start = false;
|
||||
let mut saw_turn_end = false;
|
||||
|
||||
let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(2);
|
||||
loop {
|
||||
tokio::select! {
|
||||
event = reader.next::<Event>() => {
|
||||
match event {
|
||||
Ok(Some(Event::TurnStart { .. })) => saw_turn_start = true,
|
||||
Ok(Some(Event::TurnEnd { .. })) => {
|
||||
saw_turn_end = true;
|
||||
break;
|
||||
}
|
||||
Ok(None) | Err(_) => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ = tokio::time::sleep_until(deadline) => break,
|
||||
}
|
||||
}
|
||||
|
||||
assert!(
|
||||
saw_turn_start,
|
||||
"PodEvent::TurnEnded via socket should auto-start a turn"
|
||||
);
|
||||
assert!(
|
||||
saw_turn_end,
|
||||
"auto-triggered turn should reach turn_end via socket"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn socket_invalid_method_returns_error() {
|
||||
use protocol::stream::JsonLineReader;
|
||||
|
||||
Reference in New Issue
Block a user