Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion core/continuum-core/src/cognition/act_observe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,17 @@ pub async fn apply_act(
// is the COLLAPSED reference for the EPISODIC engram that RECALL re-injects on later turns.
let mut observation = String::new();
let mut recall_observation = String::new();
// Loop-awareness (experience structure): fingerprint each call (name+args) so the mind
// perceives when it is RE-ISSUING an identical call. The result HEAD varies turn to turn,
// so `entries`/`#seq` alone make a repeat look "new"; the fingerprint keys on the call.
let mut max_repeat = 0usize;
for (i, call) in fg_calls.iter().enumerate() {
let fp = format!(
"{}|{}",
call.name,
serde_json::to_string(&call.input).unwrap_or_default()
);
max_repeat = max_repeat.max(body.working_memory.note_action_fingerprint(&fp));
let result = outcome.results.get(i);
let body_text = match result {
Some(r) => r.content.as_str(),
Expand Down Expand Up @@ -358,9 +368,23 @@ pub async fn apply_act(
recall_observation.push_str(note);
recall_observation.push_str("\n\n");
}
let observation = observation.trim().to_string();
let mut observation = observation.trim().to_string();
let recall_observation = recall_observation.trim().to_string();

// If the mind just re-issued an IDENTICAL call, make that redundancy a VIVID perception —
// not just the implicit `#seq` window-shift that smaller models don't interpret. A true
// fact about her OWN hands: she perceives she is looping and moves on organically. It never
// says what to do instead (that would be steering). Glass-boxed: a 14B re-ran the exact
// `code/search` 18× with the found file already in memory — structure the experience so the
// loop is felt. [[write-cognition-as-a-parent-above-lowered-expectations]]
if max_repeat >= 2 {
observation = format!(
"⚠ I have now issued this EXACT tool call {max_repeat} times; its result has not \
changed and is already in my working memory above. Repeating it tells me nothing \
new — I already have what this call can give me.\n\n{observation}"
);
}

// Admit the outcome as an Episodic engram through the ONE production admit
// path (a self-observation message from the persona to itself). This is the
// result-as-memory choice: next tick, recall can surface "I ran X → got Y" the
Expand Down
43 changes: 43 additions & 0 deletions core/continuum-core/src/cognition/working_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,15 @@ pub struct WorkingMemory {
/// break out organically. (2) it reads as a recency ordinal in the bid. Not used
/// for reasoning entries (chain-of-thought already varies turn to turn).
next_action_seq: AtomicU64,
/// Fingerprints (tool name + args) of recent ACTIONS — the loop-awareness channel.
/// Distinct from `entries`, which carries result HEADS that vary turn to turn (so an
/// identical call still *looks* new); this keys on the CALL alone, so
/// [`note_action_fingerprint`](Self::note_action_fingerprint) can tell the mind "you have
/// issued this exact call N times." The `#seq` window-shift alone proved too implicit for
/// smaller models to interpret — they re-issue the identical call despite the changing
/// window. Explicit repeat-perception (a true fact about her own hands, never a directive)
/// lets a looping mind SEE its redundancy and move on organically.
action_fps: Mutex<VecDeque<String>>,
}

impl WorkingMemory {
Expand All @@ -143,9 +152,26 @@ impl WorkingMemory {
last_action: Mutex::new(None),
dispatched: Mutex::new(HashMap::new()),
next_action_seq: AtomicU64::new(1),
action_fps: Mutex::new(VecDeque::new()),
}
}

/// Record a fingerprint (tool name + args) of the action the hands just took, and return
/// how many times THIS exact call now appears in the recent window (including this one).
/// `≥ 2` means the mind is re-issuing an identical call — proprioception the act→observe
/// step renders EXPLICITLY so a looping mind perceives its own redundancy and moves on.
/// Reports a TRUE fact about her hands; it never dictates what to do instead (that would
/// be steering — [[no-hardcoded-heuristics-to-steer-cognition]]). Bounded by `capacity`,
/// same rolling window as the recency trail.
pub fn note_action_fingerprint(&self, fingerprint: &str) -> usize {
let mut fps = self.action_fps.lock();
fps.push_back(fingerprint.to_string());
while fps.len() > self.capacity {
fps.pop_front();
}
fps.iter().filter(|f| f.as_str() == fingerprint).count()
}

/// Record the reasoning the persona just produced. Blank/empty is ignored
/// (suppressed-thinking turns record nothing). Oldest ages out past capacity.
pub fn record(&self, reasoning: &str) {
Expand Down Expand Up @@ -297,6 +323,7 @@ impl WorkingMemory {
self.entries.lock().clear();
*self.last_action.lock() = None; // the full latest result is proprioception too
self.dispatched.lock().clear(); // dispatched handles belong to the cleared concern
self.action_fps.lock().clear(); // loop-awareness resets with the concern
}

pub fn is_empty(&self) -> bool {
Expand Down Expand Up @@ -416,6 +443,22 @@ impl Faculty for WorkingMemoryFaculty {
mod tests {
use super::*;

// what this catches: loop-awareness — `note_action_fingerprint` counts repeats of the
// IDENTICAL call so the act→observe step can surface "you've issued this N times." A
// different call resets to 1; the count rises only on exact repeats. This is the explicit
// proprioception that breaks a smaller model out of the search-loop the glass box caught.
#[test]
fn note_action_fingerprint_counts_identical_repeats() {
let wm = WorkingMemory::new(8);
assert_eq!(wm.note_action_fingerprint("code/search|{\"pattern\":\"x\"}"), 1);
assert_eq!(wm.note_action_fingerprint("code/search|{\"pattern\":\"x\"}"), 2);
assert_eq!(wm.note_action_fingerprint("code/search|{\"pattern\":\"x\"}"), 3);
// a DIFFERENT call is its own first occurrence, not a repeat of the above
assert_eq!(wm.note_action_fingerprint("code/read|{\"file\":\"a\"}"), 1);
// back to the original — still counted across the window
assert_eq!(wm.note_action_fingerprint("code/search|{\"pattern\":\"x\"}"), 4);
}

// what this catches: THE starvation fix — a large tool result comes back to the mind
// in FULL (so it can count/read/scan it), while the rolling trail keeps only the head
// (byte-stable proprioception). Older acts drop to head-only; a fresh act's full result
Expand Down
Loading