Bedrock Agents Memory: Session Scratchpads Versus Long-Term Stores

Bedrock Agents Memory: Session Scratchpads Versus Long-Term Stores

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

Last updated on September 11, 2026


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply