Chio/Docs

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

A policy-selected rule can extend 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:

KnobTypeDefaultPurpose
enabledbooltrueMaster switch.
store_allowlistVec<String>emptyStores the agent may write or read. Layered on top of Constraint::MemoryStoreAllowlist.
max_memory_entriesOption<u64>NonePer-(agent, capability) write cap.
max_retention_ttl_secsOption<u64>NoneHard ceiling on the retention TTL declared on a write.
max_content_size_bytesOption<u64>NoneByte ceiling on the content payload of a single write.
deny_patternsVec<String>emptyRegex patterns that deny writes whose content text matches.

Two policy sources

The effective store allowlist is the union of 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.

  1. Store allowlist. Capability + config. Wildcards: * matches any store; prefix* matches by prefix; otherwise exact match.
  2. Retention TTL ceiling. When max_retention_ttl_secs is set, the arguments must include a parseable TTL via one of retention_ttl, retentionTtl, retention_ttl_secs, retentionTtlSecs, ttl, ttl_secs, expires_in, or expiresIn. Missing TTL with a configured ceiling is treated as a request for indefinite retention and denied.
  3. Content size. Readscontent_size / contentSize / content_bytes / size from the arguments. Falls back to .len() on the content text. Over-size: Deny.
  4. 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.
  5. 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 to KernelError::GuardDenied via the fail-closed wrapper. A poisoned counter denies every subsequent write under the same guard instance.
  • A regex in deny_patterns that does not compile fails construction with MemoryGovernanceError::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 store to the tool name itself via unwrap_or_else(|| self.tool.clone()) after probing collection, index, namespace, and store. A bare vector_upsert with no store argument therefore produces store == "vector_upsert", which store_matches evaluates 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.

rust
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