Skip to main content

Memory Policy

The memory policy category governs what agents store in their context and session memory. It controls four dimensions:

  1. Session isolation -- prevent cross-session memory from being loaded into a new session
  2. Capacity limits -- cap the total number of memory items an agent may accumulate
  3. Type restrictions -- block or warn when agents attempt to store forbidden memory types (e.g., credentials, PII)
  4. Retention and purge -- tag memory with a TTL and signal purge-on-completion to your memory backend

Rules

RuleTypeDefaultDescription
session_isolationbooleantrueRequire all memory to be session-scoped. Violations occur when cross_session_loaded is true
cross_session_memorybooleanfalseExplicitly allow cross-session memory loading. Set to true to permit it alongside session_isolation
memory_retention_hoursinteger(none)Maximum hours memory should be retained. Emitted in after_workflow metadata for your backend to honor
max_memory_itemsinteger(none)Maximum number of memory items the agent may hold. Checked at mid_execution and after_workflow
forbidden_memory_typesstring[][]Memory item types that must not be loaded or written (e.g. ["credentials", "pii", "financial"])
purge_on_completionbooleanfalseSignal that all memory should be purged when the workflow completes. Recorded in after_workflow metadata
action_on_violationstring"warn"Action on violation: "warn" (agent continues) or "block" (agent is stopped)

How It Works

The memory handler runs at all three enforcement phases:

PhaseWhat It ChecksReads From
before_workflowPre-loaded memory items for forbidden types; cross_session_loaded flagcontext.memory_items, context.cross_session_loaded
mid_executionForbidden items, cross-session, memory writes, total item countcontext.memory_items, context.cross_session_loaded, context.memory_writes, context.memory_item_count
after_workflowFinal item count; purge signal; write history auditcontext.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.

before_workflow Fires Before set_memory_state()

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:

  1. Forbidden type check -- scans memory_items list. Each item must have a "type" key. If any item's type appears in forbidden_memory_types, the violation fires.
  2. Cross-session check -- if session_isolation: true and cross_session_memory: false, the handler blocks when cross_session_loaded is true.

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):

  1. Forbidden item check -- scans memory_items for forbidden types (same check as before_workflow, but now with actual state from set_memory_state())
  2. Cross-session check -- if session_isolation: true, cross_session_memory: false, and cross_session_loaded: true → violation
  3. Write type check -- scans accumulated memory_writes. If any write type appears in forbidden_memory_types, the violation fires
  4. Item count check -- if memory_item_count exceeds max_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, emits purge_on_completion: true and memory_items_to_purge in metadata
  • If memory_retention_hours is set, emits retention_hours and retention_ttl_seconds in 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_isolationcross_session_memorycross_session_loadedResult
truefalsefalseALLOW -- no cross-session data
truefalsetrueVIOLATION -- cross-session data not permitted
truetruetrueALLOW -- 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 TypesItem/Write TypeResult
["credentials", "pii"]"credentials"VIOLATION
["credentials", "pii"]"pii"VIOLATION
["credentials", "pii"]"preference"ALLOW
["credentials", "pii"]"fact"ALLOW
[](any)ALLOW -- no forbidden types configured
Memory Item Schema and Key Mapping

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 CallWhat the Handler ReadsGovernance Trigger
ctx.set_memory_state(memory_items=[...])context.memory_itemsEvaluated at next auto-instrumented event or after_workflow
ctx.set_memory_state(cross_session_loaded=True)context.cross_session_loadedEvaluated at next auto-instrumented event or after_workflow
ctx.set_memory_state(memory_item_count=N)context.memory_item_countEvaluated at next auto-instrumented event or after_workflow
ctx.record_memory_write(memory_type=..., content=...)context.memory_writesTriggers 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

  1. Navigate to Governance > Policies
  2. Click New Policy
  3. Select category Memory
  4. Configure session isolation and forbidden types for your use case
  5. Set action_on_violation to block for strict enforcement or warn for audit mode
  6. Set scope to target specific agents
  7. 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:

FieldExample
Phasebefore_workflow, mid_execution, or after_workflow
Actionallow, warn, or block
Categorymemory
Reason"Memory rules stored for enforcement"

For forbidden type violations:

FieldExample
Reason"Forbidden memory type 'credentials' found in pre-loaded memory"
Metadata{"forbidden_type": "credentials", "forbidden_types": ["credentials", "pii", "financial"]}

For cross-session violations:

FieldExample
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:

FieldExample
Reason"Memory item count (1500) exceeds limit (100)"
Metadata{"memory_item_count": 1500, "limit": 100}

For after_workflow purge signal:

FieldExample
Metadata{"purge_on_completion": true, "memory_items_to_purge": 12, "retention_hours": 24, "retention_ttl_seconds": 86400}

Common Gotchas

  1. action_on_violation applies 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.

  2. 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_completion signal and memory_retention_hours TTL.

  3. memory_item_count must be set explicitly. The handler reads context.memory_item_count -- it does not count the items in memory_items automatically. Always pass memory_item_count to set_memory_state() to match the actual size of your memory store.

  4. Forbidden type matching is exact string equality. "PII" and "pii" are different strings. Make sure your type strings use consistent casing.

  5. session_isolation: true with cross_session_memory: true is permissive. Setting both to true means: "I care about session isolation in principle, but I explicitly permit cross-session loading." The violation only fires when cross_session_memory: false and cross_session_loaded: true.

  6. forbidden_memory_types checks 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.

  7. Empty forbidden_memory_types disables type checking entirely. No items or writes will be flagged for type violations regardless of their content.

  8. before_workflow runs before set_memory_state() is called. The SDK creates the run and fires before_workflow when the context is entered. Your code calls set_memory_state() inside the function body, which is after before_workflow has already completed. The full memory checks run at mid_execution (triggered by the next auto-instrumented event or record_memory_write() call) and at after_workflow.

  9. record_memory_write() triggers governance immediately, but set_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