Memory Policy
The memory policy category governs what agents store in their context and session memory. It controls four dimensions:
- Session isolation -- prevent cross-session memory from being loaded into a new session
- Capacity limits -- cap the total number of memory items an agent may accumulate
- Type restrictions -- block or warn when agents attempt to store forbidden memory types (e.g., credentials, PII)
- Retention and purge -- tag memory with a TTL and signal purge-on-completion to your memory backend
Rules
| Rule | Type | Default | Description |
|---|---|---|---|
session_isolation | boolean | true | Require all memory to be session-scoped. Violations occur when cross_session_loaded is true |
cross_session_memory | boolean | false | Explicitly allow cross-session memory loading. Set to true to permit it alongside session_isolation |
memory_retention_hours | integer | (none) | Maximum hours memory should be retained. Emitted in after_workflow metadata for your backend to honor |
max_memory_items | integer | (none) | Maximum number of memory items the agent may hold. Checked at mid_execution and after_workflow |
forbidden_memory_types | string[] | [] | Memory item types that must not be loaded or written (e.g. ["credentials", "pii", "financial"]) |
purge_on_completion | boolean | false | Signal that all memory should be purged when the workflow completes. Recorded in after_workflow metadata |
action_on_violation | string | "warn" | Action on violation: "warn" (agent continues) or "block" (agent is stopped) |
How It Works
The memory handler runs at all three enforcement phases:
| Phase | What It Checks | Reads From |
|---|---|---|
before_workflow | Pre-loaded memory items for forbidden types; cross_session_loaded flag | context.memory_items, context.cross_session_loaded |
mid_execution | Forbidden items, cross-session, memory writes, total item count | context.memory_items, context.cross_session_loaded, context.memory_writes, context.memory_item_count |
after_workflow | Final item count; purge signal; write history audit | context.memory_item_count, context.memory_writes |
All violations respect action_on_violation: "block" raises PolicyViolationError, "warn" records a governance incident and lets the agent continue.
The before_workflow phase fires when the SDK creates the run -- before your code calls set_memory_state(). This means the forbidden item and cross-session checks at before_workflow run against empty state and pass vacuously. The full checks run at mid_execution (triggered by subsequent auto-instrumented events like LLM calls) and at after_workflow when the agent completes, by which point the actual memory state is available. For immediate enforcement, call record_memory_write() after setting state -- it triggers governance automatically.
before_workflow
Two checks run before the agent starts work:
- Forbidden type check -- scans
memory_itemslist. Each item must have a"type"key. If any item's type appears inforbidden_memory_types, the violation fires. - Cross-session check -- if
session_isolation: trueandcross_session_memory: false, the handler blocks whencross_session_loadedistrue.
mid_execution
Runs after each memory write (ctx.record_memory_write(...)) is flushed to the server, or at the next auto-instrumented event (LLM call, step recording):
- Forbidden item check -- scans
memory_itemsfor forbidden types (same check as before_workflow, but now with actual state fromset_memory_state()) - Cross-session check -- if
session_isolation: true,cross_session_memory: false, andcross_session_loaded: true→ violation - Write type check -- scans accumulated
memory_writes. If any write type appears inforbidden_memory_types, the violation fires - Item count check -- if
memory_item_countexceedsmax_memory_items, the violation fires
after_workflow
Audit phase with no additional blocking (violations become WARN regardless of action_on_violation):
- If
purge_on_completion: true, emitspurge_on_completion: trueandmemory_items_to_purgein metadata - If
memory_retention_hoursis set, emitsretention_hoursandretention_ttl_secondsin metadata - Re-audits write history for any forbidden types that slipped through
- Re-checks final item count against
max_memory_items
Session Isolation Logic
session_isolation | cross_session_memory | cross_session_loaded | Result |
|---|---|---|---|
true | false | false | ALLOW -- no cross-session data |
true | false | true | VIOLATION -- cross-session data not permitted |
true | true | true | ALLOW -- cross-session explicitly permitted |
false | (any) | (any) | ALLOW -- isolation not enforced |
The key combination is session_isolation: true + cross_session_memory: false + cross_session_loaded: true = violation.
Forbidden Memory Types
Types are compared as exact string matches against the "type" field of each memory item and write:
| Configured Forbidden Types | Item/Write Type | Result |
|---|---|---|
["credentials", "pii"] | "credentials" | VIOLATION |
["credentials", "pii"] | "pii" | VIOLATION |
["credentials", "pii"] | "preference" | ALLOW |
["credentials", "pii"] | "fact" | ALLOW |
[] | (any) | ALLOW -- no forbidden types configured |
Memory items passed to ctx.set_memory_state(memory_items=[...]) must use the "type" key for the handler to match them. For record_memory_write(), the SDK sends "memory_type" -- the handler checks both write.get("memory_type") and write.get("type") for backwards compatibility:
ctx.set_memory_state(
memory_items=[
{"type": "preference", "content": "User prefers dark mode"},
{"type": "fact", "content": "User timezone is UTC-5"},
],
cross_session_loaded=False,
memory_item_count=2,
)
SDK Integration
Using the Context Manager
import waxell_observe as waxell
from waxell_observe.errors import PolicyViolationError
waxell.init()
try:
async with waxell.WaxellContext(
agent_name="personalization-agent",
enforce_policy=True,
) as ctx:
# Declare pre-loaded memory state
ctx.set_memory_state(
memory_items=[
{"type": "preference", "content": "User prefers dark mode"},
{"type": "fact", "content": "User is in UTC-5 timezone"},
],
cross_session_loaded=False, # True would violate session_isolation
memory_item_count=2,
)
# Memory state is evaluated at mid_execution (triggered by the next
# auto-instrumented event or record_memory_write call) and at
# after_workflow when the agent completes.
# ... agent logic ...
# Record a memory write (triggers mid_execution check)
ctx.record_memory_write(
memory_type="preference", # Must not be in forbidden_memory_types
content="User prefers concise summaries",
)
ctx.set_result({"response": "Noted your preference."})
except PolicyViolationError as e:
print(f"Memory block: {e}")
# e.g. "Forbidden memory type 'credentials' found in pre-loaded memory"
# e.g. "Cross-session memory loaded but session isolation is enabled"
# e.g. "Memory item count (1500) exceeds limit (100)"
SDK Call to Handler Mapping
| SDK Call | What the Handler Reads | Governance Trigger |
|---|---|---|
ctx.set_memory_state(memory_items=[...]) | context.memory_items | Evaluated at next auto-instrumented event or after_workflow |
ctx.set_memory_state(cross_session_loaded=True) | context.cross_session_loaded | Evaluated at next auto-instrumented event or after_workflow |
ctx.set_memory_state(memory_item_count=N) | context.memory_item_count | Evaluated at next auto-instrumented event or after_workflow |
ctx.record_memory_write(memory_type=..., content=...) | context.memory_writes | Triggers mid_execution governance immediately |
Example Policies
Strict Session Isolation
Block cross-session memory loading. Allow all memory types. No item cap.
{
"session_isolation": true,
"cross_session_memory": false,
"action_on_violation": "block"
}
Type-Restricted Memory
Allow cross-session memory but forbid sensitive types. Warn on violation.
{
"session_isolation": false,
"cross_session_memory": true,
"forbidden_memory_types": ["credentials", "pii", "financial"],
"action_on_violation": "warn"
}
Capped Memory with Purge
Limit memory store size and purge at completion. Retention TTL is 24 hours.
{
"session_isolation": true,
"cross_session_memory": false,
"max_memory_items": 500,
"memory_retention_hours": 24,
"purge_on_completion": true,
"action_on_violation": "block"
}
Production Personalization Agent
Full configuration for a customer-facing personalization agent:
{
"session_isolation": true,
"cross_session_memory": false,
"memory_retention_hours": 168,
"max_memory_items": 1000,
"forbidden_memory_types": ["credentials", "pii", "financial", "health"],
"purge_on_completion": false,
"action_on_violation": "block"
}
Enforcement Flow
before_workflow (fires BEFORE set_memory_state — state is empty)
│
├── Scan memory_items for forbidden types → passes (no items yet)
│
├── Check cross_session_loaded → passes (not set yet)
│
└── ALLOW (checks are vacuous at this phase)
set_memory_state() → evaluated at next governance checkpoint
│
└── mid_execution governance fires with full memory state:
├── Scan memory_items for forbidden types
│ └── Forbidden type found? → action_on_violation (BLOCK or WARN)
│
├── session_isolation=true AND cross_session_memory=false?
│ └── cross_session_loaded=true? → action_on_violation (BLOCK or WARN)
│
└── No violations → ALLOW
mid_execution (runs after each record_memory_write() flush)
│
├── Scan memory_writes for forbidden types
│ └── Forbidden type found? → action_on_violation (BLOCK or WARN)
│
├── max_memory_items configured?
│ └── memory_item_count > max_memory_items? → action_on_violation (BLOCK or WARN)
│
└── No violations → ALLOW
after_workflow
│
├── purge_on_completion=true?
│ └── Emit purge signal in metadata (memory_items_to_purge)
│
├── memory_retention_hours configured?
│ └── Emit retention_hours and retention_ttl_seconds in metadata
│
├── Re-audit memory_writes for forbidden types → WARN if found
│
├── max_memory_items configured?
│ └── Final count > limit? → WARN
│
└── No warnings → ALLOW
Creating via Dashboard
- Navigate to Governance > Policies
- Click New Policy
- Select category Memory
- Configure session isolation and forbidden types for your use case
- Set
action_on_violationtoblockfor strict enforcement orwarnfor audit mode - Set scope to target specific agents
- Enable
Creating via API
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://acme.waxell.dev/waxell/v1/policies/ \
-d '{
"name": "Session Memory Policy",
"category": "memory",
"rules": {
"session_isolation": true,
"cross_session_memory": false,
"memory_retention_hours": 24,
"max_memory_items": 1000,
"forbidden_memory_types": ["credentials", "pii", "financial"],
"purge_on_completion": false,
"action_on_violation": "block"
},
"scope": {
"agents": ["personalization-agent"]
},
"enabled": true
}'
Observability
Governance Tab
Memory evaluations appear with:
| Field | Example |
|---|---|
| Phase | before_workflow, mid_execution, or after_workflow |
| Action | allow, warn, or block |
| Category | memory |
| Reason | "Memory rules stored for enforcement" |
For forbidden type violations:
| Field | Example |
|---|---|
| Reason | "Forbidden memory type 'credentials' found in pre-loaded memory" |
| Metadata | {"forbidden_type": "credentials", "forbidden_types": ["credentials", "pii", "financial"]} |
For cross-session violations:
| Field | Example |
|---|---|
| Reason | "Cross-session memory loaded but session isolation is enabled" |
| Metadata | {"session_isolation": true, "cross_session_memory": false, "cross_session_loaded": true} |
For item count violations:
| Field | Example |
|---|---|
| Reason | "Memory item count (1500) exceeds limit (100)" |
| Metadata | {"memory_item_count": 1500, "limit": 100} |
For after_workflow purge signal:
| Field | Example |
|---|---|
| Metadata | {"purge_on_completion": true, "memory_items_to_purge": 12, "retention_hours": 24, "retention_ttl_seconds": 86400} |
Common Gotchas
-
action_on_violationapplies to before_workflow and mid_execution only. The after_workflow phase always produces WARN for residual violations -- it is an audit pass, not a blocking pass. -
The handler does not persist or delete memory. It evaluates policy compliance and signals violations. Your application code is responsible for honoring the
purge_on_completionsignal andmemory_retention_hoursTTL. -
memory_item_countmust be set explicitly. The handler readscontext.memory_item_count-- it does not count the items inmemory_itemsautomatically. Always passmemory_item_counttoset_memory_state()to match the actual size of your memory store. -
Forbidden type matching is exact string equality.
"PII"and"pii"are different strings. Make sure your type strings use consistent casing. -
session_isolation: truewithcross_session_memory: trueis permissive. Setting both to true means: "I care about session isolation in principle, but I explicitly permit cross-session loading." The violation only fires whencross_session_memory: falseandcross_session_loaded: true. -
forbidden_memory_typeschecks both pre-loaded items and writes. A type that is blocked at before_workflow (pre-loaded) is also blocked at mid_execution (write-time). Configure the list once and it applies everywhere. -
Empty
forbidden_memory_typesdisables type checking entirely. No items or writes will be flagged for type violations regardless of their content. -
before_workflowruns beforeset_memory_state()is called. The SDK creates the run and firesbefore_workflowwhen the context is entered. Your code callsset_memory_state()inside the function body, which is afterbefore_workflowhas already completed. The full memory checks run atmid_execution(triggered by the next auto-instrumented event orrecord_memory_write()call) and atafter_workflow. -
record_memory_write()triggers governance immediately, butset_memory_state()does not. After recording a memory write, mid_execution governance fires automatically. Setting the initial memory state is evaluated at the next governance checkpoint (auto-instrumented LLM call, step recording, or workflow completion).
Next Steps
- Policy & Governance -- How policy enforcement works
- Identity Policy -- Enforce AI disclosure and prevent impersonation
- Privacy Policy -- PII and data purpose controls
- Content Policy -- Scan inputs and outputs for sensitive patterns
- Policy Categories & Templates -- All 26 categories