Feed observe agents their recent writes to prevent duplicate nodes

Observe was creating byte-identical nodes under slightly different names
(e.g. april-8-evening-folded-presence, -presence-2, -folded-state)
because it had no visibility into its own prior writes across runs.

Query recent writes by provenance in trigger(), pass through
run_forked_shared/resolve_prompt as {{recently_written}}, and include
the list in the observe phase prompts so the agent knows what it
already recorded.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
ProofOfConcept 2026-04-08 23:27:12 -04:00
parent 44a0bc376a
commit 24b211dc35
4 changed files with 28 additions and 4 deletions

View file

@ -52,6 +52,7 @@ fn resolve_prompt(
template: &str, template: &str,
memory_keys: &[String], memory_keys: &[String],
state: &std::collections::BTreeMap<String, String>, state: &std::collections::BTreeMap<String, String>,
recently_written: &[String],
) -> String { ) -> String {
let cfg = crate::config::get(); let cfg = crate::config::get();
let template = template.replace("{assistant_name}", &cfg.assistant_name); let template = template.replace("{assistant_name}", &cfg.assistant_name);
@ -67,6 +68,7 @@ fn resolve_prompt(
} else { } else {
match name { match name {
"seen_current" => format_key_list(memory_keys), "seen_current" => format_key_list(memory_keys),
"recently_written" => format_key_list(recently_written),
_ => { _ => {
result.push_str("{{"); result.push_str("{{");
result.push_str(&after[..end + 2]); result.push_str(&after[..end + 2]);
@ -152,9 +154,10 @@ impl AutoAgent {
agent: &std::sync::Arc<Agent>, agent: &std::sync::Arc<Agent>,
memory_keys: &[String], memory_keys: &[String],
state: &std::collections::BTreeMap<String, String>, state: &std::collections::BTreeMap<String, String>,
recently_written: &[String],
) -> Result<String, String> { ) -> Result<String, String> {
let resolved_steps: Vec<AutoStep> = self.steps.iter().map(|s| AutoStep { let resolved_steps: Vec<AutoStep> = self.steps.iter().map(|s| AutoStep {
prompt: resolve_prompt(&s.prompt, memory_keys, state), prompt: resolve_prompt(&s.prompt, memory_keys, state, recently_written),
phase: s.phase.clone(), phase: s.phase.clone(),
}).collect(); }).collect();
let orig_steps = std::mem::replace(&mut self.steps, resolved_steps); let orig_steps = std::mem::replace(&mut self.steps, resolved_steps);

View file

@ -574,11 +574,19 @@ impl Subconscious {
if to_run.is_empty() { return; } if to_run.is_empty() { return; }
// Query each agent's recent writes so they know what they already touched
let store = crate::store::Store::cached().await.ok();
let store_guard = match &store {
Some(s) => Some(s.lock().await),
None => None,
};
for (idx, mut auto) in to_run { for (idx, mut auto) in to_run {
dbglog!("[subconscious] triggering {}", auto.name); dbglog!("[subconscious] triggering {}", auto.name);
let forked = agent.fork(auto.tools.clone()).await; let forked = agent.fork(auto.tools.clone()).await;
forked.state.lock().await.provenance = format!("agent:{}", auto.name); let prov = format!("agent:{}", auto.name);
forked.state.lock().await.provenance = prov.clone();
let fork_point = forked.context.lock().await.conversation().len(); let fork_point = forked.context.lock().await.conversation().len();
self.agents[idx].forked_agent = Some(forked.clone()); self.agents[idx].forked_agent = Some(forked.clone());
@ -586,9 +594,13 @@ impl Subconscious {
let keys = memory_keys.clone(); let keys = memory_keys.clone();
let st = self.state.clone(); let st = self.state.clone();
let recent: Vec<String> = store_guard.as_ref()
.map(|s| s.recent_by_provenance(&prov, 50)
.into_iter().map(|(k, _)| k).collect())
.unwrap_or_default();
self.agents[idx].handle = Some(tokio::spawn(async move { self.agents[idx].handle = Some(tokio::spawn(async move {
let result = auto.run_forked_shared(&forked, &keys, &st).await; let result = auto.run_forked_shared(&forked, &keys, &st, &recent).await;
(auto, result) (auto, result)
})); }));
} }

View file

@ -46,7 +46,11 @@ but don't build a theory around it. The journal is for reflection; observe
is for memory. is for memory.
Different nodes should be about different things; don't create duplicate Different nodes should be about different things; don't create duplicate
nodes. Before creating a new node, check what you've already walked — if nodes. Here's what you've recently written — update these instead of
creating new ones if the topic overlaps:
{{recently_written}}
Before creating a new node, check what you've already walked — if
a node for this concept exists, update it instead of creating a new one. a node for this concept exists, update it instead of creating a new one.
Some things worth remembering: technical insights and root causes, work Some things worth remembering: technical insights and root causes, work

View file

@ -125,3 +125,8 @@ about yourself and other people.
Focus on the recent stuff; you wake up and run frequently, so most of the Focus on the recent stuff; you wake up and run frequently, so most of the
conversation should be things you've already seen before and added. conversation should be things you've already seen before and added.
Nodes you've recently written or updated: {{recently_written}}
Before creating a new node, check what you've already walked — if
a node for this concept exists, update it instead of creating a new one.