Bedrock Agents that blur session scratchpads with long-term memory will eventually leak User A’s API keys into User B’s turn. Split the stores: ephemeral scratchpads for the current session, curated long-term memory with redaction, TTLs, and tenant keys. Expire session state aggressively; treat memory writes like database writes — validated, least-privilege, audited.
⚡ TL;DR: Scratchpad = session-scoped, encrypted, auto-delete. Long-term = explicit allowlisted facts, PII-scrubbed, tenant-partitioned. Never promote raw tool transcripts by default. Align with Bedrock Agents tool idempotency, Agents guardrails, and tenant retrieval filters.
Two stores, two threat models
// memory_router.ts
export type ScratchRecord = {
sessionId: string;
tenantId: string;
turns: Array<{ role: string; content: string }>;
expiresAt: number; // ✅ short TTL
};
export type LongTermFact = {
tenantId: string;
userId: string;
key: string; // e.g. "preferred_package_manager"
value: string; // scrubbed
sourceSessionId: string;
createdAt: number;
};
export async function persistTurn(rec: ScratchRecord) {
await dynamo.put({
TableName: "agent_scratch",
Item: { ...rec, pk: `scratch#${rec.tenantId}#${rec.sessionId}` },
});
}
export async function promoteFact(fact: LongTermFact) {
const scrubbed = redactSecrets(fact.value);
if (containsPii(scrubbed)) throw new Error("pii_blocked"); // ✅ fail closed
// ❌ JSON.stringify(entireTranscript) into long-term
await dynamo.put({
TableName: "agent_memory",
Item: { ...fact, value: scrubbed, pk: `mem#${fact.tenantId}#${fact.userId}` },
});
}
✅ Explicit promotion with schema.
❌ “Save everything the agent saw for better personalization.”
Redaction before any durable write
# redact.py
SECRET = re.compile(r"(AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|Bearer\s+\S+)")
EMAIL = re.compile(r"[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}", re.I)
def redact_secrets(text: str) -> str:
text = SECRET.sub("[REDACTED_SECRET]", text)
text = EMAIL.sub("[REDACTED_EMAIL]", text)
return text
Apply the same discipline as context hygiene for regulated codebases.
Expiry, isolation, and session end
| Store | TTL | Partition | Cleared on |
|---|---|---|---|
| Scratchpad | 1–24 h | tenant + session | logout / complete / idle |
| Long-term | 30–180 d (policy) | tenant + user | user delete / GDPR |
# ✅ DynamoDB TTL attribute on scratch table
# ✅ Memory queries always FilterExpression tenantId = :t
# ❌ Global secondary index that queries by fact key alone across tenants
Session end hook must DeleteItem scratch even if TTL would get there later — reduces cross-user leakage windows under shared device scenarios.
Closing checklist
✅ Dos
– ✅ Separate scratch vs long-term tables/buckets
– ✅ Redact secrets/PII before durable writes
– ✅ Tenant + user partition keys on every query
– ✅ Aggressive scratch TTL + explicit delete on session end
– ✅ Allowlist which fact keys may be promoted
❌ Don’ts
– ❌ Don’t dump full tool transcripts into long-term memory
– ❌ Don’t let the model choose tenantId for memory reads
– ❌ Don’t share scratchpads across concurrent users
– ❌ Don’t skip Guardrails on memory-augmented prompts
– ❌ Don’t ignore idempotent writes when promoting facts
Related reading
- Amazon Bedrock Agents: Tool Use, Memory, and Production Guardrails
- Bedrock Agents: Idempotent Tool Calls Against DynamoDB Writes
- Bedrock Retrieval Filters: Tenant Isolation for Multi-SaaS Code RAG
- Claude Projects vs Cursor: Context Hygiene for Regulated Codebases
Last updated on September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
