PlatformGuard Catalog
Memory Governance
MemoryGovernanceGuard limits agent memory writes and reads by store, size, lifetime, and per-session entry count.
Supported Actions
The guard claims two action shapes from extract_action:
ToolAction::MemoryWrite { store, .. }runs the full enforcement chain.ToolAction::MemoryRead { store, .. }runs the store allowlist only. A read against a denied store fails closed even when the write path is also blocked.
Anything else passes with GuardDecision::allow() (evaluate returns Result<GuardDecision, KernelError>). The guard does not enforce process resource limits (RSS, CPU time) directly; WASM tool servers cap those at the host through fuel metering. See Kernel Architecture for where the responsibility line sits.
Opt-in retraction quarantine
MemoryGovernanceGuard from an annotate-only observation to an enforced deny: a read whose provenance traces to a retracted finding is denied outright, not merely flagged, so a swarm stops acting on cognition that was later invalidated. This is opt-in read-side quarantine, not automatic invalidation of already-derived data: the guard still does not walk memory and rewrite entries derived from a finding before the retraction landed. See The Cognition Market for the purchase path and Status & Retraction for the retraction and quarantine mechanics.Config
MemoryGovernanceConfig knobs:
| Knob | Type | Default | Purpose |
|---|---|---|---|
enabled | bool | true | Master switch. |
store_allowlist | Vec<String> | empty | Stores the agent may write or read. Layered on top of Constraint::MemoryStoreAllowlist. |
max_memory_entries | Option<u64> | None | Per-(agent, capability) write cap. |
max_retention_ttl_secs | Option<u64> | None | Hard ceiling on the retention TTL declared on a write. |
max_content_size_bytes | Option<u64> | None | Byte ceiling on the content payload of a single write. |
deny_patterns | Vec<String> | empty | Regex patterns that deny writes whose content text matches. |
Two policy sources
MemoryGovernanceConfig::store_allowlist and Constraint::MemoryStoreAllowlist on the matched grant. An empty union means "no store allowlist" and the check is skipped. Operators that want to forbid every write set the config to a non-empty list that excludes the offending store.Enforcement order
For a memory write the guard runs five gates in this order. The first gate that fails returns Deny; later gates do not consume quota.
- Store allowlist. Capability + config. Wildcards:
*matches any store;prefix*matches by prefix; otherwise exact match. - Retention TTL ceiling. When
max_retention_ttl_secsis set, the arguments must include a parseable TTL via one ofretention_ttl,retentionTtl,retention_ttl_secs,retentionTtlSecs,ttl,ttl_secs,expires_in, orexpiresIn. Missing TTL with a configured ceiling is treated as a request for indefinite retention and denied. - Content size. Reads
content_size/contentSize/content_bytes/sizefrom the arguments. Falls back to.len()on the content text. Over-size: Deny. - Deny patterns. Each regex is compiled at construction time. The text body of the write (
content,text,value,vector_text,payload) is scanned. Any match: Deny. - Per-session entry limit. Bumped only after the four prior gates pass. Counter key is
(agent_id, capability_id). Crossing the limit: Deny.
Failure modes
- Counter mutex poisoning produces
KernelError::Internal, which the kernel converts toKernelError::GuardDeniedvia the fail-closed wrapper. A poisoned counter denies every subsequent write under the same guard instance. - A regex in
deny_patternsthat does not compile fails construction withMemoryGovernanceError::InvalidPattern. Policy load rejects the config; runtime traffic never sees the guard. - A memory write whose arguments carry no store key does not fall back to an empty string. The action extractor sets
storeto the tool name itself viaunwrap_or_else(|| self.tool.clone())after probingcollection,index,namespace, andstore. A barevector_upsertwith no store argument therefore producesstore == "vector_upsert", whichstore_matchesevaluates against the configured patterns. If a pattern happens to match that tool name, the write is allowed, not denied. The extractor is the source of truth for what counts as a store key.
Evidence
Denials currently carry no evidence. Every gate in evaluate_write and evaluate_read returns GuardDecision::deny(Vec::new()), and the Vec::new() is the evidence array itself. The guard attaches no structured reason label and records neither the matched store, the requested TTL, the byte size, nor the per-session counter value on the decision. A caller that needs to know which gate tripped reads it from the kernel's fail-closed Deny reason, not from a per-guard evidence block.
Resource model and WASM fuel
The memory guard is an application-level control: it caps the number, size, and lifetime of logical memory entries. It does not cap process RSS, CPU time, or syscall budget. Those caps live one layer down in the WASM host, where the kernel meters fuel per invocation and aborts on overrun. The guard pipeline does not see the abort directly; it sees the resulting KernelError::GuardDenied on the next call, or a tool-server error on the failing call.
Cross-link: Kernel Architecture covers how application-level guards compose with host-level sandboxing.
Per-instance counters do not survive a restart
MemoryGovernanceGuard keeps the counter map inside the guard struct. A kernel restart resets every counter. Operators that need durable per-session caps enforce them in the memory store instead of relying on the guard's in-process state.Wiring
HushSpec does not configure memory governance: the Rules type has no memory field. Configure the guard configured through two mechanisms: a guard-level MemoryGovernanceConfig (wired directly or through chio.yaml's guard configuration) and the per-grant capability constraint Constraint::MemoryStoreAllowlist, which is minted onto a ToolGrant as part of ChioScope, a and is separate from policy YAML.
use chio_guards::{MemoryGovernanceConfig, MemoryGovernanceGuard};
use chio_core::capability::scope::Constraint;
// Guard-level config: deployment-wide ceilings and deny patterns.
let guard = MemoryGovernanceGuard::with_config(MemoryGovernanceConfig {
enabled: true,
store_allowlist: vec!["agent-notes".to_string(), "vector-*".to_string()],
max_memory_entries: Some(500),
max_retention_ttl_secs: Some(86_400), // 24h
max_content_size_bytes: Some(64 * 1024),
deny_patterns: vec![
r"(?i)\bssn\b".to_string(),
r"AKIA[0-9A-Z]{16}".to_string(),
],
})?;
// Capability-level allowlist: rides on the matched grant's constraints,
// a surface distinct from the guard config above. The effective allowlist
// is the union of the two.
grant.constraints.push(Constraint::MemoryStoreAllowlist(vec![
"agent-notes".to_string(),
]));With this config, a write to agent-notes with {ttl: 3600, content: "..."} passes if it stays under 500 entries per session, the content fits in 64 KiB, and the body does not match either deny pattern. A write to incident-log denies (not in the union of allowlists). A write without an explicit TTL denies (indefinite retention requested under a 24h ceiling).
Performance class
Hot path: pattern scan over the action's arguments, HashMap bump on the counter, optional regex sweep on content text. Empty config short-circuits inside the match on action shape. Regex cost is bounded by the operator-supplied pattern set; the guard does not run a built-in PII detector. For a deployment that needs cross-cutting PII redaction on results, see Data-Layer Guards and the QueryResultGuard.
Next steps
- Data-Layer Guards for vector and SQL stores that the memory guard delegates to.
- Default Pipeline to see where memory governance sits in the conjunction.