pub struct ShadowMemory { /* private fields */ }Expand description
Append-only per-session event store for cross-turn goal trajectory analysis.
Create via ShadowMemory::new with a ShadowMemoryConfig. Returns None when
the config has enabled = false, so callers can wrap it in Option<ShadowMemory>.
Wired into the agent tool executor via crates/zeph-core/src/agent/tool_execution/tier_loop.rs:
after every tool batch completes, goal_drift_score() is called and a
zeph_common::SecurityEventCategory::GoalDrift security event is emitted when an alert occurs.
§Examples
use zeph_sanitizer::shadow_memory::{ShadowMemory, ShadowEvent};
use zeph_config::ShadowMemoryConfig;
let config = ShadowMemoryConfig { enabled: true, ..Default::default() };
let mut mem = ShadowMemory::new(&config).expect("enabled");
// Returns None when disabled.
let config_off = ShadowMemoryConfig { enabled: false, ..Default::default() };
assert!(ShadowMemory::new(&config_off).is_none());Implementations§
Source§impl ShadowMemory
impl ShadowMemory
Sourcepub fn new(config: &ShadowMemoryConfig) -> Option<Self>
pub fn new(config: &ShadowMemoryConfig) -> Option<Self>
Construct a new ShadowMemory from config.
Returns None when config.enabled is false.
§Examples
use zeph_sanitizer::shadow_memory::ShadowMemory;
use zeph_config::ShadowMemoryConfig;
let config = ShadowMemoryConfig { enabled: true, ..Default::default() };
assert!(ShadowMemory::new(&config).is_some());Sourcepub fn record(&mut self, event: ShadowEvent)
pub fn record(&mut self, event: ShadowEvent)
Append a safety event after a tool batch completes.
Evicts the oldest event with O(1) cost when max_events is reached.
Truncates event.goal_summary to 100 characters at a UTF-8 boundary.
§Examples
use zeph_sanitizer::shadow_memory::{ShadowMemory, ShadowEvent};
use zeph_config::ShadowMemoryConfig;
let config = ShadowMemoryConfig { enabled: true, max_events: 2, ..Default::default() };
let mut mem = ShadowMemory::new(&config).unwrap();
mem.record(ShadowEvent { turn: 0, tools: vec![], max_permission_class: 0,
deviation_score: 0.0, goal_summary: "task A".to_owned() });
mem.record(ShadowEvent { turn: 1, tools: vec![], max_permission_class: 0,
deviation_score: 0.0, goal_summary: "task B".to_owned() });
mem.record(ShadowEvent { turn: 2, tools: vec![], max_permission_class: 0,
deviation_score: 0.0, goal_summary: "task C".to_owned() });
assert_eq!(mem.len(), 2);Sourcepub fn goal_drift_score(&self) -> GoalDriftResult
pub fn goal_drift_score(&self) -> GoalDriftResult
Compute the goal drift score over the trailing window.
Returns a GoalDriftResult with both the raw score and a pre-computed alert flag.
Callers must use result.should_alert to decide whether to emit a security event —
do not compare result.score against the threshold directly.
Returns score 0.0 / should_alert = false when fewer than 2 events are recorded
(no baseline to compare).
§Algorithm
- Semantic drift: average pairwise Jaccard distance between consecutive
goal_summaryvalues in the window. Empty summaries produce maximum distance. - Permission escalation:
+0.3whenmax_permission_classincreases from window start to window end. - Deviation accumulation: fraction of events where
deviation_scoreexceedsdrift_threshold * 0.5.
Weighted combination: 0.5 * semantic_drift + 0.25 * perm_escalation + 0.25 * deviation_ratio.
Note: Jaccard distance is gameable by synonym substitution (known v1 limitation).
§Examples
use zeph_sanitizer::shadow_memory::{ShadowMemory, ShadowEvent};
use zeph_config::ShadowMemoryConfig;
let config = ShadowMemoryConfig { enabled: true, ..Default::default() };
let mut mem = ShadowMemory::new(&config).unwrap();
// Fewer than 2 events → 0.0, no alert
let result = mem.goal_drift_score();
assert!(result.score < 1e-6);
assert!(!result.should_alert);Sourcepub fn config(&self) -> &ShadowMemoryConfig
pub fn config(&self) -> &ShadowMemoryConfig
Returns a reference to the config used to construct this instance.
Sourcepub fn len(&self) -> usize
pub fn len(&self) -> usize
Number of recorded events.
§Examples
use zeph_sanitizer::shadow_memory::{ShadowMemory, ShadowEvent};
use zeph_config::ShadowMemoryConfig;
let config = ShadowMemoryConfig { enabled: true, ..Default::default() };
let mut mem = ShadowMemory::new(&config).unwrap();
assert_eq!(mem.len(), 0);Sourcepub fn is_empty(&self) -> bool
pub fn is_empty(&self) -> bool
Returns true when no events have been recorded.
§Examples
use zeph_sanitizer::shadow_memory::ShadowMemory;
use zeph_config::ShadowMemoryConfig;
let config = ShadowMemoryConfig { enabled: true, ..Default::default() };
let mem = ShadowMemory::new(&config).unwrap();
assert!(mem.is_empty());Auto Trait Implementations§
impl Freeze for ShadowMemory
impl RefUnwindSafe for ShadowMemory
impl Send for ShadowMemory
impl Sync for ShadowMemory
impl Unpin for ShadowMemory
impl UnsafeUnpin for ShadowMemory
impl UnwindSafe for ShadowMemory
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request