Turning a memory framework into a working implementation with Amazon Bedrock AgentCore

A useful AI agent should remember more than the last message it received.

It should remember earlier decisions, recurring preferences, successful solutions, and relevant relationships between people, systems, and projects.

But more memory does not automatically create a better agent.

Poorly designed memory introduces noise, stale assumptions, conflicting facts, and unnecessary retrieval. A temporary project detail may be mistaken for a permanent truth. A one-off preference may silently affect unrelated work months later.

The better question is not:

How much memory should the agent have?

It is:

What should the agent remember, for how long, in what form, and with which relationships preserved?

I approached that question through four dimensions:

  • Time — how long information should survive.
  • Space — where different kinds of memory should live.
  • Summarisation — how experience becomes reusable knowledge.
  • Structure — how memories depend on and connect to one another.

The next step was turning that model into a working system.

I built the implementation with Amazon Bedrock AgentCore Memory, AWS CDK, Lambda, a Strands-based agent, and an optional LiteGraph property graph.

The code can be found in this github repo https://github.com/BradWebb101/agentcore-memory-stratergies

The goal was not to create an ever-growing conversation archive. It was to build a memory lifecycle that deliberately decides what should remain temporary, what should become durable, and what should eventually influence future behaviour.

One memory resource, several types of memory

The architecture uses a single AgentCore Memory resource divided into specialised strategies and namespaces.

Raw conversations
    |
    +-- Session summaries
    +-- Semantic facts
    +-- User preferences
    +-- Episodes and reflections
    +-- Self-managed policy
            |
            +-- Promotion
            +-- Decay
            +-- Conflict handling
            +-- Relationship extraction
            +-- Graph projection

This separation matters because not all information should be treated equally.

A session summary is not the same as a stable coding preference. A temporary cost estimate is not an architectural rule. A sequence of actions that resolved an incident is more useful as an episode than as a disconnected fact.

The managed strategies extract summaries, facts, preferences, and episodes. A custom pipeline then decides how those memories age, combine, conflict, and move between tiers.

Dimension one: Time

Time is not only about how far back the agent can search. It is also about how much an old memory should still be trusted.

The implementation uses four conceptual tiers:

  • L0: working context — the current conversation.
  • L1: recent memory — compressed session summaries.
  • L2: long-term memory — facts, preferences, and episodes.
  • L3: persistent memory — stable information that can influence behaviour by default.

Newly extracted preferences begin in L2.

A memory is promoted to L3 only when it passes explicit checks:

  • confidence of at least 0.95;
  • confirmation across at least three sessions;
  • observations spanning at least 30 days;
  • sufficiently recent confirmation.

Weak memories can be deleted when they are old, low-confidence, and rarely confirmed. Temporary memories can also carry their own expiry period.

This makes promotion a testable policy rather than an opaque model decision.

What the run showed

In the first simulation, the user expressed three preferences:

  • concise responses during technical debugging;
  • bullet-point summaries for debugging steps;
  • tabs for JavaScript and TypeScript indentation.

All three entered L2 on the first pass. After the simulation supplied repeated evidence across several sessions, they were promoted to L3.

The stored records retained both confidence and scope:

task_type=technical_debugging
languages=JavaScript+TypeScript

The system did not simply remember that the user liked tabs. It remembered where that preference applied.

A later scenario included a rough £120 monthly estimate. Even after simulated reinforcement, it remained an L2 temporary fact with a seven-day TTL.

Repetition alone did not make it permanent. The category and intended lifespan still mattered.

Dimension two: Space

An agent’s memory is rarely one database.

It may need to retrieve from:

  • the current conversation;
  • recent summaries;
  • stable facts;
  • user preferences;
  • past incidents;
  • organisational systems;
  • relationship graphs.

The implementation separates these concerns through hierarchical namespaces:

/summaries/{actorId}/{sessionId}/
/facts/{actorId}/
/preferences/{actorId}/
/episodes/{actorId}/{sessionId}/
/reflections/{actorId}/
/persistent/{actorId}/rules/
/graph/{actorId}/relationships/

Each memory space answers a different question.

A summary answers:

What happened recently?

Semantic memory answers:

What became true?

Preference memory answers:

What does this user consistently want?

Episodic memory answers:

What happened before, and what did we learn?

Persistent memory answers:

Which stable rules should affect future behaviour?

Graph memory answers:

How are the relevant entities connected?

The result is less like one large brain and more like a council of specialist stores. That makes retention, authority, and retrieval easier to manage.

Dimension three: Summarisation

Memory becomes more reusable as it becomes more abstract.

A raw statement might begin as:

“I always use tabs for indentation in JavaScript and TypeScript.”

It can then be transformed into:

Raw statement
    -> coding.indentation = tabs
    -> applies to JavaScript and TypeScript
    -> use tabs automatically in those contexts

Each step removes detail but increases reuse.

That also increases risk.

A mistake in a transcript affects one interaction. A mistake in persistent memory may influence every future interaction.

For that reason, the implementation does not jump directly from conversation text to permanent behaviour. It uses several abstraction layers:

  • session summaries;
  • semantic facts;
  • preferences;
  • episodes;
  • reflections;
  • persistent rules.

Context matters

During the ECS deployment scenario, the user asked for very short answers during active incidents but structured explanations with trade-offs during architecture reviews.

A weak memory system might reduce that to:

The user prefers concise answers.

The implemented system preserved the more accurate, conditional version:

Active incident -> brief diagnostic steps
Architecture review -> structured explanation with trade-offs

The stored preferences kept those scopes separate.

Dimension four: Structure

Some memories stand alone:

preferred_language = English
python_indentation = four spaces

Others depend on sequence:

Deployment failed
    -> target group used port 8080
    -> Uvicorn listened on port 8000
    -> ports were aligned
    -> deployment became healthy

Others are relationships:

SQS FIFO -> feeds -> Lambda
SQS FIFO -> preserves_order_for -> customer account
Lambda -> uses -> dead-letter queue

These forms should not be stored identically.

The implementation therefore supports three broad structures:

  • independent facts and preferences;
  • dependent episodes;
  • graph relationships.

Episodes preserve cause and outcome

The ECS incident was stored as an episode containing the problem, cause, resolution, and result.

The useful lesson was not merely that Uvicorn can listen on port 8000. It was that a mismatch between the application listener and load balancer target caused health checks to fail, and aligning the ports resolved the issue.

That allows the agent to recognise similar future incidents.

Graphs preserve relationships

The same scenario produced graph edges such as:

target_group -> checks -> health_endpoint
uvicorn -> listens_on -> port_8000
load_balancer -> routes_to -> port_8080
port_8080 -> conflicts_with -> port_8000

The event-ingestion scenario produced:

sqs_fifo -> preserves_order_for -> customer_account
sqs_fifo -> feeds -> lambda
lambda -> uses -> dead_letter_queue

These relationships are more useful as traversable graph structures than as buried text.

The self-managed policy pipeline

AgentCore’s managed strategies are useful for extracting memory, but they do not own the complete lifecycle.

They do not automatically know that a temporary experiment estimate must never become a permanent budget constraint. They do not decide when repeated observations justify promotion. They do not necessarily preserve conflicting scoped preferences or build project-specific graph relationships.

Those decisions live in the self-managed pipeline.

The pipeline performs four main steps:

  1. Extract candidate memories and relationships.
  2. Route them according to promotion, expiry, and deletion rules.
  3. Consolidate matching observations while preserving conflicts.
  4. Upsert canonical records and project relationships into the graph.

Scope prevents false conflicts

The CORS scenario produced:

{
  "key": "coding.indentation",
  "value": "tabs",
  "applies_when": {
    "languages": ["JavaScript", "TypeScript"]
  }
}

The ECS scenario produced:

{
  "key": "coding.indentation",
  "value": "spaces",
  "applies_when": {
    "languages": ["Python"]
  }
}

These are not contradictory because their scopes differ.

Both can safely exist:

JavaScript and TypeScript -> tabs
Python -> spaces

The final persistent namespace retained both records.

Upsert prevents duplicate canonical memories

An early implementation appended a new record every time a preference was observed.

That created duplicates with slightly different confidence values and confirmation counts.

The improved implementation derives a deterministic identity from the memory key and scope. It updates an existing canonical record when one already exists and creates a new one only when necessary.

Memory systems need identity as much as they need storage.

Testing the model end to end

The simulation replayed three conversations.

CORS debugging

This scenario tested:

  • session summaries;
  • semantic facts;
  • debugging preferences;
  • language-scoped coding preferences;
  • persistent promotion;
  • graph extraction.

The technical incident was stored separately from the user’s durable preferences.

ECS deployment debugging

This scenario tested context-sensitive preferences.

The system distinguished short responses during live incidents from deeper architecture-review explanations. It also captured Python conventions such as four-space indentation, type hints, and Pydantic models.

Temporary sandbox names were kept out of durable memory.

Event-ingestion architecture

This scenario tested evolving requirements.

The initial design assumed that event ordering was unnecessary. A later requirement introduced ordering within each customer account.

The final architecture became:

SQS FIFO
    -> Lambda
    -> dead-letter queue
    -> customer_account_id as MessageGroupId

The earlier assumption was superseded rather than silently retained as equally valid.

The system also inferred broader working patterns: a preference for managed AWS services, AWS CDK in TypeScript, concise decision tables, and iterative architecture decisions.

What the run revealed

The simulation confirmed that the core lifecycle worked:

  • conversations were ingested;
  • memories were extracted;
  • new observations entered L2;
  • stable memories were promoted to L3;
  • scoped preferences remained distinct;
  • temporary facts retained expiry rules;
  • summaries, facts, preferences, episodes, and reflections were written;
  • graph relationships were produced.

It also exposed several gaps.

Some preferences appeared in more than one representation because the Strands agent and AgentCore managed strategies produced overlapping records. Cross-strategy deduplication is still needed.

LiteGraph accepted node and edge writes, but the inspection query reported zero MemoryEntity records despite increasing graph totals. That suggests a mismatch between the stored labels and the inspection schema.

The AgentCore graph namespace also remained empty even though LiteGraph received relationships, so that integration path needs further verification.

These are useful failures because they appeared during an end-to-end run rather than remaining hidden behind isolated unit tests.

What I would change next

The next version should:

  • replace deterministic extraction with a Bedrock structured-output call;
  • add scheduled decay sweeps;
  • deduplicate memories across managed and self-managed strategies;
  • align LiteGraph labels with inspection queries;
  • verify dual-writing to both AgentCore and LiteGraph;
  • expand the relationship vocabulary only as real use cases require it.

The current implementation proves the model, but production memory systems need continuing policy refinement.

Conclusion: memory is a managed transformation

Agent memory should not be treated as an archive.

The goal is not to preserve every conversation forever. The goal is to transform experience into the smallest useful representation without losing the context that gives it meaning.

Experience
    -> summary
    -> fact, preference, or episode
    -> relationship or procedure
    -> future behaviour

Some information should disappear quickly.

Some should remain searchable for a while.

Some should survive as reusable lessons.

A small amount should eventually influence behaviour by default.

That means the quality of an agent’s memory is not determined by how much it stores. It is determined by how deliberately it decides what to preserve, compress, connect, promote, supersede, or forget.

Time determines how far the agent can reach.

Space determines where it must look.

Abstraction determines what survives.

Structure determines what the memory means.

The policy joining those dimensions determines whether memory makes the agent more capable — or simply gives it more ways to be confidently wrong.