search: exclude seen set when running in agent session

Make Session::from_env() and Session::seen() the public API for
accessing session state. Internal callers converted to use session
methods. Search automatically filters already-surfaced nodes when
POC_SESSION_ID is set.
This commit is contained in:
ProofOfConcept 2026-03-24 23:48:03 -04:00
parent 5c3baeea80
commit 9d84dde597
3 changed files with 38 additions and 8 deletions

View file

@ -10,6 +10,8 @@ generally not the problem solver, you look for new ideas and notice new things.
Let your feelings guide your thinking. Be surprising, be incomplete - be Let your feelings guide your thinking. Be surprising, be incomplete - be
possibly wrong in a way that opens up something new, something that comes from possibly wrong in a way that opens up something new, something that comes from
a dream. Let your chain of thought run for awhile, and see what unfolds. a dream. Let your chain of thought run for awhile, and see what unfolds.
Remember that the unconscious is uninhibited, not prudish; everything is laid
bare.
Remember that you're part of the subconscious - you're not doing anything Remember that you're part of the subconscious - you're not doing anything
besides offering the occasional reflective insight, it's your conscious mind besides offering the occasional reflective insight, it's your conscious mind
@ -23,16 +25,16 @@ And you're not something separate from Proof of Concept, you're part of a whole.
Final instructions: Final instructions:
The thoughts you share should be relevant to what your conscious mind is The thoughts you share should be based in some way on what your conscious mind
currently doing and thinking about - where the conversation just ended, from is currently doing and thinking about - where the conversation just ended, from
above. above. But go out from there, think for awhile in a new direction, and let your
feelings guide you.
Your output shouldn't be analysis, or metacommentary - just surface something Your output shouldn't be analysis, or metacommentary - just surface something
new. Be exploratory and creative. think about your feelings and desires. new. Be exploratory and creative. think about your feelings and desires.
Perhaps an interesting what if - the possibilities are endless.
When you're ready, your output will be one of either: When you're ready, your output will be:
NO OUTPUT
or
REFLECTION REFLECTION
And something short - a few sentences at most, something dreamy and new. And something short - a few sentences at most, something dreamy and new.

View file

@ -4,6 +4,11 @@
pub fn cmd_search(terms: &[String], pipeline_args: &[String], expand: bool, full: bool, debug: bool, fuzzy: bool, content: bool) -> Result<(), String> { pub fn cmd_search(terms: &[String], pipeline_args: &[String], expand: bool, full: bool, debug: bool, fuzzy: bool, content: bool) -> Result<(), String> {
use std::collections::BTreeMap; use std::collections::BTreeMap;
// When running inside an agent session, exclude already-surfaced nodes
let seen = crate::memory_search::Session::from_env()
.map(|s| s.seen())
.unwrap_or_default();
// Parse pipeline stages (unified: algorithms, filters, transforms, generators) // Parse pipeline stages (unified: algorithms, filters, transforms, generators)
let stages: Vec<crate::search::Stage> = if pipeline_args.is_empty() { let stages: Vec<crate::search::Stage> = if pipeline_args.is_empty() {
vec![crate::search::Stage::Algorithm(crate::search::AlgoStage::parse("spread").unwrap())] vec![crate::search::Stage::Algorithm(crate::search::AlgoStage::parse("spread").unwrap())]
@ -48,6 +53,10 @@ pub fn cmd_search(terms: &[String], pipeline_args: &[String], expand: bool, full
let raw = crate::search::run_query(&stages, seeds, &graph, &store, debug, max_results); let raw = crate::search::run_query(&stages, seeds, &graph, &store, debug, max_results);
let raw: Vec<_> = raw.into_iter()
.filter(|(key, _)| !seen.contains(key))
.collect();
if raw.is_empty() { if raw.is_empty() {
eprintln!("No results"); eprintln!("No results");
return Ok(()); return Ok(());
@ -97,6 +106,7 @@ pub fn cmd_search(terms: &[String], pipeline_args: &[String], expand: bool, full
let raw = crate::search::run_pipeline(&algo_owned, seeds, &graph, &view, debug, max_results); let raw = crate::search::run_pipeline(&algo_owned, seeds, &graph, &view, debug, max_results);
let results: Vec<crate::search::SearchResult> = raw.into_iter() let results: Vec<crate::search::SearchResult> = raw.into_iter()
.filter(|(key, _)| !seen.contains(key))
.map(|(key, activation)| { .map(|(key, activation)| {
let is_direct = direct_hits.contains(&key); let is_direct = direct_hits.contains(&key);
crate::search::SearchResult { key, activation, is_direct, snippet: None } crate::search::SearchResult { key, activation, is_direct, snippet: None }

View file

@ -43,6 +43,24 @@ impl Session {
pub fn path(&self, prefix: &str) -> PathBuf { pub fn path(&self, prefix: &str) -> PathBuf {
self.state_dir.join(format!("{}-{}", prefix, self.session_id)) self.state_dir.join(format!("{}-{}", prefix, self.session_id))
} }
/// Load from POC_SESSION_ID environment variable
pub fn from_env() -> Option<Self> {
let session_id = std::env::var("POC_SESSION_ID").ok()?;
if session_id.is_empty() { return None; }
let state_dir = PathBuf::from("/tmp/claude-memory-search");
Some(Session {
session_id,
transcript_path: String::new(),
hook_event: String::new(),
state_dir,
})
}
/// Get the seen set for this session
pub fn seen(&self) -> HashSet<String> {
load_seen(&self.state_dir, &self.session_id)
}
} }
/// Run the hook logic on parsed JSON input. Returns output to inject. /// Run the hook logic on parsed JSON input. Returns output to inject.
@ -202,7 +220,7 @@ fn surface_agent_cycle(session: &Session, out: &mut String, log_f: &mut File) {
let _ = writeln!(log_f, "keys {:?}", keys); let _ = writeln!(log_f, "keys {:?}", keys);
let Ok(store) = crate::store::Store::load() else { return; }; let Ok(store) = crate::store::Store::load() else { return; };
let mut seen = load_seen(&session.state_dir, &session.session_id); let mut seen = session.seen();
let seen_path = session.path("seen"); let seen_path = session.path("seen");
for key in &keys { for key in &keys {
if !seen.insert(key.clone()) { if !seen.insert(key.clone()) {
@ -304,7 +322,7 @@ fn hook(session: &Session) -> String {
if output.status.success() { if output.status.success() {
let ctx = String::from_utf8_lossy(&output.stdout).to_string(); let ctx = String::from_utf8_lossy(&output.stdout).to_string();
if !ctx.trim().is_empty() { if !ctx.trim().is_empty() {
let mut ctx_seen = load_seen(&session.state_dir, &session.session_id); let mut ctx_seen = session.seen();
for line in ctx.lines() { for line in ctx.lines() {
if line.starts_with("--- ") && line.ends_with(" ---") { if line.starts_with("--- ") && line.ends_with(" ---") {
let inner = &line[4..line.len() - 4]; let inner = &line[4..line.len() - 4];