feat: remove active knowledge support

This commit is contained in:
2026-07-16 07:30:00 +09:00
parent f2106407be
commit f786e01997
54 changed files with 197 additions and 1591 deletions
+2 -19
View File
@@ -108,15 +108,6 @@ impl WorkerHandle {
is_dir: c.is_dir,
})
.collect(),
protocol::CompletionKind::Knowledge => self
.shared_state
.list_knowledge_completions(prefix)
.into_iter()
.map(|c| protocol::CompletionEntry {
value: c.slug,
is_dir: false,
})
.collect(),
}
}
@@ -331,13 +322,6 @@ impl WorkerController {
if let Some(fs_for_view) = fs_for_view {
shared_state.set_fs_view(crate::fs_view::WorkerFsView::new(fs_for_view));
}
shared_state.set_knowledge(
worker
.knowledge_completions()
.into_iter()
.map(|slug| crate::shared_state::KnowledgeCandidate { slug })
.collect(),
);
runtime_dir.write_manifest(&manifest_toml).await?;
runtime_dir.write_status(&shared_state).await?;
@@ -752,7 +736,7 @@ where
// Memory tools require both explicit feature exposure and memory storage
// configuration. This keeps resident-memory config separate from the
// model-visible Memory*/Knowledge* tool surface.
// model-visible Memory* tool surface.
if feature_config.memory.enabled {
let mem = memory_config.as_ref().ok_or_else(|| {
std::io::Error::new(
@@ -775,8 +759,7 @@ where
worker.register_tool(memory::tool::write_tool(layout.clone()));
worker.register_tool(memory::tool::edit_tool(layout.clone()));
worker.register_tool(memory::tool::delete_tool(layout.clone()));
worker.register_tool(memory::tool::memory_query_tool(layout.clone(), query_cfg));
worker.register_tool(memory::tool::knowledge_query_tool(layout, query_cfg));
worker.register_tool(memory::tool::memory_query_tool(layout, query_cfg));
}
// Worker-orchestration tools (SpawnWorker + the four comm tools) share
-28
View File
@@ -83,11 +83,6 @@ pub enum WorkerPrompt {
/// AGENTS.md section when memory is enabled, summary injection is enabled,
/// and `memory/summary.md` has a valid non-empty body.
ResidentMemorySummarySection,
/// Trailing `## Resident knowledge` section, appended after the
/// resident memory summary when memory is enabled, Knowledge resident
/// injection is enabled, and at least one `knowledge/*` record advertises
/// `model_invokation: true`.
ResidentKnowledgeSection,
/// Trailing Worker orchestration guidance, appended when registered tools
/// include Worker-management capabilities.
WorkerOrchestrationGuidanceSection,
@@ -110,7 +105,6 @@ impl WorkerPrompt {
Self::WorkingBoundariesSection => "working_boundaries_section",
Self::AgentsMdSection => "agents_md_section",
Self::ResidentMemorySummarySection => "resident_memory_summary_section",
Self::ResidentKnowledgeSection => "resident_knowledge_section",
Self::WorkerOrchestrationGuidanceSection => "worker_orchestration_guidance_section",
Self::TicketEventCompanionNotice => "ticket_event_companion_notice",
Self::SpawnWorkerToolDescription => "spawn_worker_tool_description",
@@ -130,7 +124,6 @@ impl WorkerPrompt {
WorkerPrompt::WorkingBoundariesSection,
WorkerPrompt::AgentsMdSection,
WorkerPrompt::ResidentMemorySummarySection,
WorkerPrompt::ResidentKnowledgeSection,
WorkerPrompt::WorkerOrchestrationGuidanceSection,
WorkerPrompt::TicketEventCompanionNotice,
WorkerPrompt::SpawnWorkerToolDescription,
@@ -146,7 +139,6 @@ impl WorkerPrompt {
"working_boundaries_section",
"agents_md_section",
"resident_memory_summary_section",
"resident_knowledge_section",
"worker_orchestration_guidance_section",
"ticket_event_companion_notice",
"spawn_worker_tool_description",
@@ -384,25 +376,6 @@ impl PromptCatalog {
)
}
/// Render `WorkerPrompt::ResidentKnowledgeSection` with `{{ entries }}`
/// (a pre-formatted list block authored by the caller).
pub fn resident_knowledge_section(
&self,
entries: &str,
knowledge_query_available: bool,
memory_read_available: bool,
) -> Result<String, CatalogError> {
use std::collections::BTreeMap;
let mut m: BTreeMap<&'static str, Value> = BTreeMap::new();
m.insert("entries", Value::from(entries));
m.insert(
"knowledge_query_available",
Value::from(knowledge_query_available),
);
m.insert("memory_read_available", Value::from(memory_read_available));
self.render(WorkerPrompt::ResidentKnowledgeSection, Value::from(m))
}
/// Render `WorkerPrompt::WorkerOrchestrationGuidanceSection` (no inputs).
pub fn worker_orchestration_guidance_section(&self) -> Result<String, CatalogError> {
self.render(
@@ -554,7 +527,6 @@ mod tests {
let extract = cat.memory_extract_system("Japanese").unwrap();
let consolidate = cat.memory_consolidation_system("Japanese").unwrap();
for rendered in [compact, extract, consolidate] {
assert!(!rendered.contains("### Memory and knowledge"));
assert!(!rendered.contains("Do not query memory every turn"));
assert!(!rendered.contains("Strong lookup triggers include"));
}
+6 -141
View File
@@ -21,7 +21,6 @@ use std::sync::Arc;
use chrono::{DateTime, SecondsFormat, Utc};
use manifest::Scope;
use memory::ResidentKnowledgeEntry;
use minijinja::value::Value;
use minijinja::{Environment, ErrorKind, UndefinedBehavior};
use thiserror::Error;
@@ -125,7 +124,6 @@ impl SystemPromptTemplate {
ctx.scope,
ctx.agents_md.as_deref(),
ctx.resident_summary,
ctx.resident_knowledge,
ToolCapabilities::from_tool_names(&ctx.tool_names),
)
}
@@ -159,11 +157,6 @@ pub struct SystemPromptContext<'a> {
/// frontmatter stripped. `None` disables the resident summary section;
/// empty strings are ignored by the trailing-section formatter.
pub resident_summary: Option<&'a str>,
/// Resident-injection candidates from `<workspace>/knowledge/*` whose
/// frontmatter has `model_invokation: true`. `None` disables the
/// section entirely (memory disabled, or a consolidation worker that opts
/// out); `Some(&[])` also yields no section.
pub resident_knowledge: Option<&'a [ResidentKnowledgeEntry]>,
/// Catalog used to render the fixed trailing section headers.
/// Passed by reference so callers do not give up ownership across
/// the short-lived render borrow.
@@ -208,7 +201,6 @@ impl<'a> SystemPromptContext<'a> {
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct ToolCapabilities {
memory_query: bool,
knowledge_query: bool,
memory_read: bool,
memory_write: bool,
memory_edit: bool,
@@ -227,7 +219,6 @@ impl ToolCapabilities {
for name in names {
match name.as_str() {
"MemoryQuery" => capabilities.memory_query = true,
"KnowledgeQuery" => capabilities.knowledge_query = true,
"MemoryRead" => capabilities.memory_read = true,
"MemoryWrite" => capabilities.memory_write = true,
"MemoryEdit" => capabilities.memory_edit = true,
@@ -253,7 +244,7 @@ impl ToolCapabilities {
}
fn memory_any(self) -> bool {
self.memory_records() || self.knowledge_query
self.memory_records()
}
fn memory_mutation(self) -> bool {
@@ -274,7 +265,6 @@ impl ToolCapabilities {
map.insert("memory_any", Value::from(self.memory_any()));
map.insert("memory_records", Value::from(self.memory_records()));
map.insert("memory_query", Value::from(self.memory_query));
map.insert("knowledge_query", Value::from(self.knowledge_query));
map.insert("memory_read", Value::from(self.memory_read));
map.insert("memory_write", Value::from(self.memory_write));
map.insert("memory_edit", Value::from(self.memory_edit));
@@ -297,7 +287,6 @@ fn append_trailing_section(
scope: &Scope,
agents_md: Option<&str>,
resident_summary: Option<&str>,
resident_knowledge: Option<&[ResidentKnowledgeEntry]>,
tool_capabilities: ToolCapabilities,
) -> Result<String, SystemPromptError> {
let mut out = String::with_capacity(body.len() + 256);
@@ -325,19 +314,6 @@ fn append_trailing_section(
out.push('\n');
}
}
if let Some(entries) = resident_knowledge {
if !entries.is_empty() {
out.push('\n');
let formatted = format_resident_knowledge_entries(entries);
let section = prompts.resident_knowledge_section(
&formatted,
tool_capabilities.knowledge_query,
tool_capabilities.memory_read,
)?;
out.push_str(section.trim_end_matches(&['\n', ' '][..]));
out.push('\n');
}
}
if tool_capabilities.worker_management() {
out.push('\n');
let section = prompts.worker_orchestration_guidance_section()?;
@@ -352,36 +328,6 @@ fn append_trailing_section(
Ok(out)
}
/// `- <slug>: <description>` per line. Description newlines are folded
/// to spaces so a single entry stays on one row in the rendered prompt.
fn format_resident_knowledge_entries(entries: &[ResidentKnowledgeEntry]) -> String {
format_resident_entries(
entries
.iter()
.map(|e| (e.slug.as_str(), e.description.as_str())),
)
}
fn format_resident_entries<'a>(entries: impl Iterator<Item = (&'a str, &'a str)>) -> String {
let mut out = String::new();
for (i, (slug, description)) in entries.enumerate() {
if i > 0 {
out.push('\n');
}
out.push_str("- ");
out.push_str(slug);
out.push_str(": ");
for ch in description.chars() {
if ch == '\n' || ch == '\r' {
out.push(' ');
} else {
out.push(ch);
}
}
}
out
}
/// Bridge used by [`Worker::ensure_system_prompt_materialized`] so tests
/// can construct a synthetic context without going through a full Worker.
#[doc(hidden)]
@@ -426,7 +372,6 @@ mod tests {
tool_names: tools,
agents_md,
resident_summary: None,
resident_knowledge: None,
prompts: test_prompts(),
}
}
@@ -444,16 +389,11 @@ mod tests {
tool_names: Vec::new(),
agents_md: None,
resident_summary: summary,
resident_knowledge: None,
prompts: test_prompts(),
}
}
fn ctx_with_resident<'a>(
cwd: &'a Path,
scope: &'a Scope,
resident: &'a [ResidentKnowledgeEntry],
) -> SystemPromptContext<'a> {
fn ctx_with_resident<'a>(cwd: &'a Path, scope: &'a Scope) -> SystemPromptContext<'a> {
SystemPromptContext {
now: fixed_now(),
cwd: cwd.display().to_string().into(),
@@ -462,7 +402,6 @@ mod tests {
tool_names: Vec::new(),
agents_md: None,
resident_summary: None,
resident_knowledge: Some(resident),
prompts: test_prompts(),
}
}
@@ -470,7 +409,6 @@ mod tests {
fn memory_tool_names() -> Vec<String> {
[
"MemoryQuery",
"KnowledgeQuery",
"MemoryRead",
"MemoryWrite",
"MemoryEdit",
@@ -522,8 +460,8 @@ mod tests {
.render(&ctx(dir.path(), &scope, memory_tool_names(), None))
.unwrap();
// Builtin default body must expose the tool and language policies.
assert!(rendered.contains("### Memory and knowledge"));
assert!(rendered.contains("small targeted `MemoryQuery` / `KnowledgeQuery`"));
assert!(rendered.contains("### Memory"));
assert!(rendered.contains("small targeted `MemoryQuery`"));
assert!(rendered.contains("Strong lookup triggers include"));
assert!(rendered.contains("MemoryRead(kind=summary)"));
assert!(rendered.contains("Do not query memory every turn"));
@@ -550,9 +488,8 @@ mod tests {
))
.unwrap();
assert!(!rendered.contains("### Memory and knowledge"));
assert!(!rendered.contains("### Memory"));
assert!(!rendered.contains("MemoryQuery"));
assert!(!rendered.contains("KnowledgeQuery"));
assert!(!rendered.contains("MemoryRead"));
assert!(!rendered.contains("MemoryWrite"));
assert!(!rendered.contains("MemoryEdit"));
@@ -576,10 +513,9 @@ mod tests {
))
.unwrap();
assert!(rendered.contains("### Memory and knowledge"));
assert!(rendered.contains("### Memory"));
assert!(rendered.contains("small targeted `MemoryQuery`"));
assert!(rendered.contains("MemoryRead(kind=summary)"));
assert!(!rendered.contains("KnowledgeQuery"));
assert!(!rendered.contains("MemoryWrite"));
assert!(!rendered.contains("MemoryEdit"));
assert!(!rendered.contains("MemoryDelete"));
@@ -838,75 +774,4 @@ mod tests {
.unwrap();
assert!(!rendered.contains("Resident memory summary"));
}
#[test]
fn trailing_section_omits_resident_knowledge_when_none() {
let (_tmp, loader) = user_loader_with("body.md", "BODY");
let tmpl = SystemPromptTemplate::parse("$user/body", loader).unwrap();
let dir = TempDir::new().unwrap();
let scope = build_scope(dir.path());
let rendered = tmpl.render(&ctx(dir.path(), &scope, vec![], None)).unwrap();
assert!(!rendered.contains("Resident knowledge"));
}
#[test]
fn trailing_section_omits_resident_knowledge_when_empty_slice() {
let (_tmp, loader) = user_loader_with("body.md", "BODY");
let tmpl = SystemPromptTemplate::parse("$user/body", loader).unwrap();
let dir = TempDir::new().unwrap();
let scope = build_scope(dir.path());
let rendered = tmpl
.render(&ctx_with_resident(dir.path(), &scope, &[]))
.unwrap();
assert!(!rendered.contains("Resident knowledge"));
}
#[test]
fn trailing_section_renders_resident_knowledge_entries() {
let (_tmp, loader) = user_loader_with("body.md", "BODY");
let tmpl = SystemPromptTemplate::parse("$user/body", loader).unwrap();
let dir = TempDir::new().unwrap();
let scope = build_scope(dir.path());
let entries = vec![
ResidentKnowledgeEntry {
slug: "alpha".into(),
description: "first record".into(),
},
ResidentKnowledgeEntry {
slug: "beta".into(),
description: "second record\nwith newline".into(),
},
];
let rendered = tmpl
.render(&ctx_with_resident(dir.path(), &scope, &entries))
.unwrap();
assert!(rendered.contains("## Resident knowledge"));
assert!(rendered.contains("- alpha: first record"));
// Newline in description is folded to a space (one entry per line).
assert!(rendered.contains("- beta: second record with newline"));
assert!(!rendered.contains("KnowledgeQuery"));
assert!(!rendered.contains("MemoryRead"));
// Resident section sits *after* the working-boundaries header.
let pos_boundaries = rendered.find("## Working boundaries").unwrap();
let pos_resident = rendered.find("## Resident knowledge").unwrap();
assert!(pos_resident > pos_boundaries);
}
#[test]
fn trailing_section_mentions_resident_knowledge_tools_when_available() {
let (_tmp, loader) = user_loader_with("body.md", "BODY");
let tmpl = SystemPromptTemplate::parse("$user/body", loader).unwrap();
let dir = TempDir::new().unwrap();
let scope = build_scope(dir.path());
let entries = [ResidentKnowledgeEntry {
slug: "alpha".into(),
description: "first record".into(),
}];
let mut context = ctx_with_resident(dir.path(), &scope, &entries);
context.tool_names = memory_tool_names();
let rendered = tmpl.render(&context).unwrap();
assert!(rendered.contains("## Resident knowledge"));
assert!(rendered.contains("KnowledgeQuery / MemoryRead"));
}
}
+1 -56
View File
@@ -6,11 +6,6 @@ use session_store::SegmentId;
use crate::fs_view::WorkerFsView;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KnowledgeCandidate {
pub slug: String,
}
/// Shared state between WorkerController and runtime directory.
///
/// Controller updates this in-memory; RuntimeDir writes the status
@@ -20,7 +15,7 @@ pub struct KnowledgeCandidate {
/// IPC layer could answer `Method::GetHistory`. Those reads now go
/// directly through the session-log sink (`Event::Snapshot` +
/// live events), so this struct holds only status, identity,
/// greeting, and completion lookup hubs.
/// greeting, and filesystem completion lookup hubs.
pub struct WorkerSharedState {
pub worker_name: String,
pub segment_id: SegmentId,
@@ -34,7 +29,6 @@ pub struct WorkerSharedState {
/// (only relevant for unit tests that build a `WorkerSharedState`
/// directly without spinning up a controller).
fs_view: OnceLock<WorkerFsView>,
knowledge: OnceLock<Vec<KnowledgeCandidate>>,
}
impl WorkerSharedState {
@@ -51,7 +45,6 @@ impl WorkerSharedState {
greeting,
status: RwLock::new(WorkerStatus::Idle),
fs_view: OnceLock::new(),
knowledge: OnceLock::new(),
}
}
@@ -67,23 +60,6 @@ impl WorkerSharedState {
self.fs_view.get()
}
pub fn set_knowledge(&self, knowledge: Vec<KnowledgeCandidate>) {
let _ = self.knowledge.set(knowledge);
}
pub fn list_knowledge_completions(&self, prefix: &str) -> Vec<KnowledgeCandidate> {
self.knowledge
.get()
.map(|items| {
items
.iter()
.filter(|candidate| candidate.slug.starts_with(prefix))
.cloned()
.collect()
})
.unwrap_or_default()
}
pub fn set_status(&self, status: WorkerStatus) {
if let Ok(mut s) = self.status.write() {
*s = status;
@@ -165,35 +141,4 @@ mod tests {
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["state"], "running");
}
#[test]
fn knowledge_completions_empty_when_unset() {
let state = test_state();
assert!(state.list_knowledge_completions("").is_empty());
assert!(state.list_knowledge_completions("foo").is_empty());
}
#[test]
fn knowledge_completions_filter_by_prefix() {
let state = test_state();
state.set_knowledge(vec![
KnowledgeCandidate {
slug: "alpha".into(),
},
KnowledgeCandidate {
slug: "alphabet".into(),
},
KnowledgeCandidate {
slug: "beta".into(),
},
]);
let all = state.list_knowledge_completions("");
assert_eq!(all.len(), 3);
let alpha = state.list_knowledge_completions("alpha");
assert_eq!(
alpha.iter().map(|c| c.slug.as_str()).collect::<Vec<_>>(),
vec!["alpha", "alphabet"]
);
assert!(state.list_knowledge_completions("zzz").is_empty());
}
}
+14 -253
View File
@@ -502,16 +502,15 @@ pub struct Worker<C: LlmClient, St: Store> {
/// [`Self::from_manifest`], or defaults to the builtin pack when a
/// Worker is constructed through lower-level paths that have no loader.
prompts: Arc<PromptCatalog>,
/// Memory workspace layout used for Memory/Knowledge record operations.
/// Memory workspace layout used for Memory record operations.
memory_layout: Option<memory::WorkspaceLayout>,
/// When true (default), the system-prompt assembler may append the
/// workspace memory summary (`memory/summary.md`). Internal disposable
/// workers disable this so resident memory exposure is opt-in per Worker.
inject_resident_summary: bool,
/// When true (default), the system-prompt assembler may append resident
/// Knowledge descriptions. This is intentionally independent from
/// resident context. This is intentionally independent from
/// summary residency: each section has its own gate.
inject_resident_knowledge: bool,
/// extract (memory.extract) reentry guard. `true` while an extract
/// worker is running; subsequent triggers are skipped per spec
/// (`docs/plan/memory.md` §Extract 並走防止). `Arc<AtomicBool>` so
@@ -617,7 +616,6 @@ impl<C: LlmClient + Clone + 'static, St: Store + Clone + 'static> Worker<C, St>
runtime_ticket_role: None,
prompts: self.prompts.clone(),
inject_resident_summary: self.inject_resident_summary,
inject_resident_knowledge: self.inject_resident_knowledge,
extract_in_flight: self.extract_in_flight.clone(),
consolidation_in_flight: self.consolidation_in_flight.clone(),
extract_pointer: self.extract_pointer.clone(),
@@ -806,7 +804,6 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
prompts,
memory_layout: None,
inject_resident_summary: true,
inject_resident_knowledge: true,
extract_in_flight: Arc::new(AtomicBool::new(false)),
consolidation_in_flight: Arc::new(AtomicBool::new(false)),
extract_pointer: Arc::new(Mutex::new(None)),
@@ -833,11 +830,9 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
///
/// Default `true`: normal Workers may expose each resident section according
/// to its own gate and manifest settings. Internal disposable workers set
/// this to `false` so summary and Knowledge residency are both
/// suppressed while explicit tools remain available.
pub fn set_resident_injection(&mut self, enabled: bool) {
pub fn set_resident_memory_injection(&mut self, enabled: bool) {
self.inject_resident_summary = enabled;
self.inject_resident_knowledge = enabled;
}
/// Toggle `memory/summary.md` resident injection in the system prompt.
@@ -845,14 +840,8 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
self.inject_resident_summary = enabled;
}
/// Toggle resident Knowledge injection in the system prompt.
pub fn set_resident_knowledge_injection(&mut self, enabled: bool) {
self.inject_resident_knowledge = enabled;
}
/// Shared handle to the prompt catalog. Cheap to clone (`Arc`).
pub fn prompts(&self) -> &Arc<PromptCatalog> {
&self.prompts
pub fn prompts(&self) -> Arc<PromptCatalog> {
Arc::clone(&self.prompts)
}
/// The current segment ID. Read lock-free from the shared session
@@ -1450,9 +1439,6 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
let alerter = self.alerter.clone();
let tool_names: Vec<String> = {
let worker = self.engine.as_mut().expect("worker present");
// Materialise any pending tool factories so the template sees the
// full list of tool names. Redundant with the flush inside
// `Engine::lock()`; safe because `flush_pending` is idempotent.
worker.tool_server_handle().flush_pending();
worker
.tool_server_handle()
@@ -1472,11 +1458,6 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
}
}
}
// Resident-injection collection. Each resident section has its own
// gate so summary and Knowledge residency remain conceptually independent.
// Internal workers can still opt out of both resident sections.
// Owned values live for the duration of `render` below; the
// context borrows from them.
let memory_layout = self.memory_layout.as_ref();
let inject_summary = self.inject_resident_summary
&& memory_layout.is_some()
@@ -1491,21 +1472,6 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
} else {
None
};
let inject_resident_knowledge = self.inject_resident_knowledge && memory_layout.is_some();
let resident: Vec<memory::ResidentKnowledgeEntry> = if inject_resident_knowledge {
memory_layout
.map(memory::collect_resident_knowledge)
.unwrap_or_default()
} else {
Vec::new()
};
let resident_slice: Option<&[memory::ResidentKnowledgeEntry]> = if inject_resident_knowledge
{
Some(&resident)
} else {
None
};
let resident_exposure_snapshots = self.resident_exposure_snapshots(&resident);
let worker_language = worker_language(&self.manifest.engine);
let scope_snapshot = self.scope.snapshot();
let cwd_for_prompt = self
@@ -1520,7 +1486,6 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
tool_names,
agents_md: agents_md_read.and_then(|read| read.body),
resident_summary: resident_summary.as_deref(),
resident_knowledge: resident_slice,
prompts: &self.prompts,
};
let rendered = template
@@ -1530,7 +1495,6 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
.as_mut()
.expect("worker present")
.set_system_prompt(rendered);
self.append_resident_exposure_event(resident_exposure_snapshots);
Ok(())
}
@@ -1700,11 +1664,10 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
})?;
self.user_segments.push(input.clone());
// Resolve `@<path>` file refs and `#<slug>` Knowledge refs to system
// messages stashed for the WorkerInterceptor to attach right after the
// user message. Resolution failures are non-fatal alerts.
let mut attachments = self.resolve_file_refs(&input);
attachments.extend(self.resolve_knowledge_refs(&input));
// Resolve `@<path>` file refs to system messages stashed for the
// WorkerInterceptor to attach right after the user message. Resolution
// failures are non-fatal alerts.
let attachments = self.resolve_file_refs(&input);
let flattened = self.flatten_segments(&input);
if !attachments.is_empty() {
*self
@@ -1783,113 +1746,6 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
out
}
fn resolve_knowledge_refs(&self, segments: &[Segment]) -> Vec<SystemItem> {
let Some(layout) = self.memory_layout.as_ref() else {
return Vec::new();
};
let mut out = Vec::new();
for seg in segments {
let Segment::KnowledgeRef { slug } = seg else {
continue;
};
let parsed = match memory::Slug::parse(slug.clone()) {
Ok(slug) => slug,
Err(e) => {
self.alert(
AlertLevel::Warn,
AlertSource::Worker,
format!("knowledge ref #{slug} has invalid slug: {e}"),
);
continue;
}
};
let path = layout.knowledge_path(&parsed);
let bytes = match std::fs::read(&path) {
Ok(bytes) => bytes,
Err(e) => {
self.alert(
AlertLevel::Warn,
AlertSource::Worker,
format!("knowledge ref #{slug} could not be read: {e}"),
);
continue;
}
};
let raw = String::from_utf8_lossy(&bytes).into_owned();
let body_text = match memory::schema::split_frontmatter(&raw) {
Ok((_yaml, body)) => body,
Err(e) => {
self.alert(
AlertLevel::Warn,
AlertSource::Worker,
format!("knowledge ref #{slug} has invalid frontmatter: {e}"),
);
continue;
}
};
let snapshot = memory::snapshot_record_from_bytes(
memory::workspace::RecordKind::Knowledge,
slug.clone(),
&bytes,
);
self.append_memory_use_event(memory::UsageSource::KnowledgeRef, vec![snapshot]);
let body = format!("[Knowledge #{}]\n{}", slug, body_text.trim_end());
out.push(SystemItem::Knowledge {
slug: slug.clone(),
body,
});
}
out
}
fn resident_exposure_snapshots(
&self,
knowledge: &[memory::ResidentKnowledgeEntry],
) -> Vec<memory::UsageRecordSnapshot> {
let Some(layout) = self.memory_layout.as_ref() else {
return Vec::new();
};
let mut snapshots = Vec::new();
for entry in knowledge {
match memory::snapshot_record_from_layout(
layout,
memory::workspace::RecordKind::Knowledge,
&entry.slug,
) {
Ok(snapshot) => snapshots.push(snapshot),
Err(err) => {
warn!(knowledge = %entry.slug, error = %err, "failed to snapshot resident knowledge exposure")
}
}
}
snapshots
}
fn append_memory_use_event(
&self,
source: memory::UsageSource,
records: Vec<memory::UsageRecordSnapshot>,
) {
let Some(layout) = self.memory_layout.as_ref() else {
return;
};
if let Err(err) =
memory::append_use_event(layout, self.segment_id().to_string(), source, records)
{
warn!(error = %err, "failed to append memory usage event");
}
}
fn append_resident_exposure_event(&self, records: Vec<memory::UsageRecordSnapshot>) {
let Some(layout) = self.memory_layout.as_ref() else {
return;
};
if let Err(err) =
memory::append_resident_exposure_event(layout, self.segment_id().to_string(), records)
{
warn!(error = %err, "failed to append resident exposure event");
}
}
/// Stage the post-interruption cleanup at the front of worker
/// history: close every unanswered `Item::ToolCall` with a synthetic
/// `Item::ToolResult` (Anthropic wire-validity), then append a
@@ -1947,16 +1803,9 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
Ok(())
}
pub fn knowledge_completions(&self) -> Vec<String> {
self.memory_layout
.as_ref()
.map(memory::list_knowledge_slugs)
.unwrap_or_default()
}
/// Flatten a typed segment list into the single string the Engine
/// receives as the user message, and emit user-facing alerts for
/// segments that fall through to placeholder (Knowledge refs without a resolver, or unknown variants from a newer client).
/// segments that fall through to placeholder (unknown variants from a newer client).
/// `FileRef` is handled separately by `resolve_file_refs`. The text
/// reconstruction itself comes from `Segment::flatten_to_text`,
/// shared with replay paths that should not re-alert.
@@ -1964,18 +1813,6 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
for seg in segments {
match seg {
Segment::Text { .. } | Segment::Paste { .. } | Segment::FileRef { .. } => {}
Segment::KnowledgeRef { slug } => {
if self.memory_layout.is_none() {
self.alert(
AlertLevel::Warn,
AlertSource::Worker,
format!(
"knowledge ref #{slug} cannot be resolved \
because memory is disabled; passed to LLM as placeholder"
),
);
}
}
Segment::Unknown => {
self.alert(
AlertLevel::Warn,
@@ -3678,8 +3515,6 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
// directly under the workspace via WorkspaceLayout. Resident section
// injection is a Worker-level concern; this disposable Engine is built
// without it by construction, in keeping with `docs/plan/memory.md`
// §Consolidation のKnowledgeアクセス (agent pulls knowledge through
// the search tool instead of via system-prompt residency).
let query_cfg = memory::tool::QueryConfig::from(memory_cfg);
worker.register_tool(memory::tool::read_tool_with_usage(
layout.clone(),
@@ -3689,10 +3524,6 @@ impl<C: LlmClient, St: Store> Worker<C, St> {
worker.register_tool(memory::tool::edit_tool(layout.clone()));
worker.register_tool(memory::tool::delete_tool(layout.clone()));
worker.register_tool(memory::tool::memory_query_tool(layout.clone(), query_cfg));
worker.register_tool(memory::tool::knowledge_query_tool(
layout.clone(),
query_cfg,
));
let tidy = consolidate::collect_tidy_hints(&layout);
let usage_report = match memory::build_usage_report(&layout) {
@@ -4020,7 +3851,6 @@ where
prompts: common.prompts,
memory_layout: common.memory_layout,
inject_resident_summary: true,
inject_resident_knowledge: true,
extract_in_flight: Arc::new(AtomicBool::new(false)),
consolidation_in_flight: Arc::new(AtomicBool::new(false)),
extract_pointer: Arc::new(Mutex::new(None)),
@@ -4127,7 +3957,6 @@ where
prompts: common.prompts,
memory_layout: common.memory_layout,
inject_resident_summary: true,
inject_resident_knowledge: true,
extract_in_flight: Arc::new(AtomicBool::new(false)),
consolidation_in_flight: Arc::new(AtomicBool::new(false)),
extract_pointer: Arc::new(Mutex::new(None)),
@@ -4360,7 +4189,6 @@ where
prompts: common.prompts,
memory_layout: common.memory_layout,
inject_resident_summary: true,
inject_resident_knowledge: true,
extract_in_flight: Arc::new(AtomicBool::new(false)),
consolidation_in_flight: Arc::new(AtomicBool::new(false)),
extract_pointer: Arc::new(Mutex::new(extract_pointer)),
@@ -4876,10 +4704,6 @@ fn preview_segments(segments: &[Segment]) -> String {
preview.push('@');
preview.push_str(path);
}
Segment::KnowledgeRef { slug } => {
preview.push('#');
preview.push_str(slug);
}
Segment::Unknown => preview.push_str("[unknown input segment]"),
}
}
@@ -5972,15 +5796,11 @@ mod build_summary_prompt_tests {
#[derive(Clone, Copy)]
struct ResidentInjectionGates {
summary: bool,
knowledge: bool,
}
impl ResidentInjectionGates {
fn all(enabled: bool) -> Self {
Self {
summary: enabled,
knowledge: enabled,
}
Self { summary: enabled }
}
}
@@ -6002,7 +5822,7 @@ mod build_summary_prompt_tests {
summary_doc: Option<&str>,
memory_config: Option<manifest::MemoryConfig>,
gates: ResidentInjectionGates,
include_knowledge: bool,
_unused: bool,
) -> String {
let dir = tempfile::tempdir().unwrap();
let store = session_store::FsStore::new(dir.path().join("sessions")).unwrap();
@@ -6012,14 +5832,6 @@ mod build_summary_prompt_tests {
std::fs::create_dir_all(cwd.join(".yoi/memory")).unwrap();
std::fs::write(cwd.join(".yoi/memory/summary.md"), doc).unwrap();
}
if include_knowledge {
std::fs::create_dir_all(cwd.join(".yoi/knowledge")).unwrap();
std::fs::write(
cwd.join(".yoi/knowledge/resident-policy.md"),
knowledge_doc("knowledge resident desc"),
)
.unwrap();
}
let mut manifest = minimal_manifest();
manifest.memory = memory_config;
let scope = Scope::writable(&cwd).unwrap();
@@ -6039,12 +5851,7 @@ mod build_summary_prompt_tests {
.memory
.as_ref()
.map(|mem| memory::WorkspaceLayout::resolve(mem, &cwd));
if gates.summary == gates.knowledge {
worker.set_resident_injection(gates.summary);
} else {
worker.set_resident_summary_injection(gates.summary);
worker.set_resident_knowledge_injection(gates.knowledge);
}
worker.set_resident_memory_injection(gates.summary);
let template = SystemPromptTemplate::parse(
"$yoi/default",
crate::prompt::loader::PromptLoader::builtins_only(),
@@ -6059,12 +5866,6 @@ mod build_summary_prompt_tests {
format!("---\nupdated_at: 2026-01-01T00:00:00Z\n---\n{body}")
}
fn knowledge_doc(description: &str) -> String {
format!(
"---\ncreated_at: 2026-01-01T00:00:00Z\nupdated_at: 2026-01-01T00:00:00Z\nkind: policy\ndescription: \"{description}\"\nmodel_invokation: true\nuser_invocable: true\nlast_sources: []\n---\nbody\n",
)
}
#[tokio::test]
async fn resident_summary_body_is_injected_without_frontmatter() {
let rendered = render_system_prompt_with_summary(
@@ -6129,53 +5930,13 @@ mod build_summary_prompt_tests {
let prompt = render_system_prompt_with_resident_sections(
Some(&summary_doc("resident summary marker")),
Some(manifest::MemoryConfig::default()),
ResidentInjectionGates {
summary: false,
knowledge: true,
},
ResidentInjectionGates { summary: false },
true,
)
.await;
assert!(!prompt.contains("Resident memory summary"));
assert!(!prompt.contains("resident summary marker"));
assert!(prompt.contains("Resident knowledge"));
assert!(prompt.contains("knowledge resident desc"));
}
#[tokio::test]
async fn knowledge_gate_false_keeps_resident_summary() {
let prompt = render_system_prompt_with_resident_sections(
Some(&summary_doc("resident summary marker")),
Some(manifest::MemoryConfig::default()),
ResidentInjectionGates {
summary: true,
knowledge: false,
},
true,
)
.await;
assert!(prompt.contains("Resident memory summary"));
assert!(prompt.contains("resident summary marker"));
assert!(!prompt.contains("Resident knowledge"));
assert!(!prompt.contains("knowledge resident desc"));
}
#[tokio::test]
async fn resident_injection_opt_out_omits_all_resident_sections() {
let prompt = render_system_prompt_with_resident_sections(
Some(&summary_doc("resident summary marker")),
Some(manifest::MemoryConfig::default()),
ResidentInjectionGates::all(false),
true,
)
.await;
assert!(!prompt.contains("Resident memory summary"));
assert!(!prompt.contains("resident summary marker"));
assert!(!prompt.contains("Resident knowledge"));
assert!(!prompt.contains("knowledge resident desc"));
}
fn minimal_manifest() -> WorkerManifest {