●AGENTS — When you create a Managed Agents session, pass agent_with_overrides to swap the model, system prompt, tools, MCP servers, or skills for that single session●KEYS — API keys in the Claude Console can now carry an expiration (a preset, a custom duration, or Never), with an email reminder before keys that live 7 days or longer expire●MCP — Admins can provision MCP connectors org-wide through their identity provider, starting with Okta, so users get connector access automatically on first login●WORKBENCH — The legacy Workbench at platform.claude.com/workbench retires on August 17, along with the experimental prompt generate, improve, and templatize APIs●REVIEW — Claude Code adds a background /code-review so you can keep working while a review runs, with better MCP and Windows path handling alongside●FASTEND — Fast mode for Claude Opus 4.7 is removed today, July 24. Any speed: 'fast' calls need to move over to Opus 4.8●AGENTS — When you create a Managed Agents session, pass agent_with_overrides to swap the model, system prompt, tools, MCP servers, or skills for that single session●KEYS — API keys in the Claude Console can now carry an expiration (a preset, a custom duration, or Never), with an email reminder before keys that live 7 days or longer expire●MCP — Admins can provision MCP connectors org-wide through their identity provider, starting with Okta, so users get connector access automatically on first login●WORKBENCH — The legacy Workbench at platform.claude.com/workbench retires on August 17, along with the experimental prompt generate, improve, and templatize APIs●REVIEW — Claude Code adds a background /code-review so you can keep working while a review runs, with better MCP and Windows path handling alongside●FASTEND — Fast mode for Claude Opus 4.7 is removed today, July 24. Any speed: 'fast' calls need to move over to Opus 4.8
When Memory Store Listings Returned Half the Rows: Migrating to agent-memory-2026-07-22
The Managed Agents memory listing behavior changed under agent-memory-2026-07-22: fixed ordering, stricter depth, and segment-based path_prefix matching. Here is how an audit script quietly dropped rows, and how to design a memory access layer that survives the change.
As an indie developer, I hand the day-to-day operations of a handful of apps to a single Managed Agents session. Overnight it looks at crawl status, billing, and inbound questions, then leaves a summary for me to read at breakfast. To support that, I keep a memory store per project, writing down "the decision we made last time" and "the setting nobody should touch."
Every morning I list those memories and look at the diff. It had become a habit. Then one morning the listing returned fewer rows. The day before it had returned 68 across all projects; that morning, 39. Nearly half. No error. No exception, no warning, nothing. A few projects' memories had simply disappeared from the results.
The cause was a single beta header I had bumped the night before: agent-memory-2026-07-22.
Why call the memory listing API yourself
A Managed Agents memory store is mounted into the session container at /mnt/memory/, and the agent reads and writes it with the ordinary file tools. For that part, you can leave everything to the agent.
Calling the listing API from the human side serves a different purpose. Reviewing what the agent wrote. Correcting a bad memory. Exporting the store periodically for an audit. In my case, it was the daily diff: I would walk each project by narrowing memories.list with path_prefix.
Each memory is capped at 100 kB (roughly 25,000 tokens), and the docs explicitly advise structuring memory as "many small focused files, not a few large ones." I followed that, running a tree where the project name sits in the first path segment, like /projects/alpha/decisions/2026-07.md. That design turned out to be exactly where this change landed.
The three things that changed in agent-memory-2026-07-22
Once you bump the beta header to agent-memory-2026-07-22, memories.list behaves differently in three ways.
Aspect
Before
After (agent-memory-2026-07-22)
List ordering
Controlled by order_by / order
Fixed to a stable, server-defined order. order_by / order are ignored
You can brace for the first two just by reading them. The third is the troubling one, because its failure mode is "not an error, just a quietly different result."
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦The three changes in agent-memory-2026-07-22: fixed list ordering, restricted depth values, and segment-based path_prefix matching
✦The trap where a path_prefix without a trailing slash silently returns zero rows, with steps to reproduce it
✦A memory audit layer that depends on neither ordering nor substring prefix behavior
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
The counterintuitive part: realizing I had leaned on substring matching
To sweep across a project and its archive in one call, my audit script did this:
# Before. This picked up both /projects/alpha/... and /projects/alpha_archive/...memories = client.beta.memory_stores.memories.list( memory_store_id=store_id, path_prefix="/projects/alpha", # no trailing slash view="basic", betas=["managed-agents-2026-04-01"],)
The old path_prefix was a substring match. "/projects/alpha" is a prefix of "/projects/alpha/decisions/2026-07.md", but it is equally a prefix of "/projects/alpha_archive/old.md". Half-consciously, I had come to rely on that as a convenient way to grab a project and its archive together.
Under agent-memory-2026-07-22, path_prefix now requires a trailing slash and matches by whole segments. Two things follow.
First, "/projects/alpha" without a trailing slash no longer picks up the range I intended, because it does not land on a segment boundary. Second, the correct form "/projects/alpha/" does not include /projects/alpha_archive/, because alpha and alpha_archive are different segments.
So what used to be "one call returns both the live tree and the archive" became "only the live tree comes back." That was the real reason the count looked halved. No error fired, because the path_prefix itself was still syntactically valid; only the matched range had quietly narrowed.
This was the biggest lesson for me. Of all breaking changes, the dangerous ones are not those that throw, but those that silently return something different. The former you always notice. The latter slips past you unless you are watching.
Rewriting the access layer to survive the change
The fix rests on three principles. Normalize the prefix. Do not depend on ordering. Walk the tree explicitly. With those in mind, I rewrote the audit layer as follows. This is lifted from the code that actually runs every morning, with secrets stripped out.
from anthropic import Anthropicclient = Anthropic() # ANTHROPIC_API_KEY comes from the environmentBETAS = ["managed-agents-2026-04-01", "agent-memory-2026-07-22"]def normalize_prefix(prefix: str) -> str: """A path_prefix must end with a slash. Normalize root to "/".""" if not prefix.startswith("/"): prefix = "/" + prefix if not prefix.endswith("/"): prefix = prefix + "/" return prefixdef list_all_memories(store_id: str, prefix: str = "/") -> list[dict]: """ Return every memory under prefix. - Do not depend on return order (the caller always sorts) - depth accepts only 0/1/omitted, so walk the tree recursively ourselves """ prefix = normalize_prefix(prefix) collected: list[dict] = [] cursor: str | None = None while True: page = client.beta.memory_stores.memories.list( memory_store_id=store_id, path_prefix=prefix, depth=1, # take only direct children; recurse into subtrees view="basic", # metadata only; retrieve content on demand after_id=cursor, betas=BETAS, ) for item in page.data: if item.type == "memory_prefix": # a directory-like node; descend one level collected.extend(list_all_memories(store_id, item.path)) else: collected.append({"id": item.id, "path": item.path}) if not getattr(page, "has_more", False): break cursor = page.last_id # Server order is not guaranteed, so sort ourselves for stable diffs collected.sort(key=lambda m: m["path"]) return collected
A few notes that live outside the code.
I pinned depth=1 and made the function recurse whenever it hits a memory_prefix (a directory-like node). I used to pass a large depth to grab everything at once, but that value is now a 400. Walking the tree myself frees me from the depth limit and, as a bonus, lets me see the count at each level.
I lean on ordering nowhere. After fetching, I sort by path. A day-over-day diff should be compared as a set, not as a sequence, and written that way it does not break when the server order shifts.
And I do not pull content: view="basic". An audit needs only the path and ID. When I actually need the body, I memories.retrieve a narrowed set. Downloading 100 kB times many files every morning was a waste, both in time and in cost.
Rewriting "the live tree plus the archive" as an explicit intent
The old, substring-driven behavior of grabbing both the live tree and the archive was convenient, but the intent was implicit. I took the migration as a chance to rewrite it as an explicit enumeration.
def list_project_including_archive(store_id: str, project: str) -> list[dict]: """Deliberately combine the live tree and archive as two separate prefixes.""" live = list_all_memories(store_id, f"/projects/{project}/") archive = list_all_memories(store_id, f"/projects/{project}_archive/") return live + archive
Writing it out showed me just how fragile the old code had been. Once I had more projects named around "alpha," the substring match would have swept in alpha2 and alpha_experimental too. Segment matching did not make things less convenient; it forced my vague intent to become explicit. That is how I have come to see it.
Safe corrections with a content_sha256 precondition
Half of what an audit is for is correcting bad memories, and there is a hazard here that is independent of the version change. Because the agent and I touch the same memories, overwrite conflicts are possible.
memories.update supports optimistic concurrency. Pass the content_sha256 you read, and the update applies only if the stored hash still matches.
def safe_correct(store_id: str, memory_id: str, new_content: str) -> bool: current = client.beta.memory_stores.memories.retrieve( memory_store_id=store_id, memory_id=memory_id, betas=BETAS, ) try: client.beta.memory_stores.memories.update( memory_store_id=store_id, memory_id=memory_id, content=new_content, precondition={ "type": "content_sha256", "content_sha256": current.content_sha256, }, betas=BETAS, ) return True except Exception: # hash mismatch = someone (usually the agent itself) wrote first. # do not clobber silently; re-read and decide again return False
There really were nights when the agent was appending to the same file. Back when I corrected memories without a precondition, I must have occasionally stomped those appends. Every change is retained as an immutable memory version (memver_...), so you can notice after the fact, but not stomping it is better still.
Situational guidance
Here are the judgment calls the migration tends to raise, laid out by situation.
Situation
Recommendation
Narrowing by prefix
Normalize every path_prefix to end with a slash. Hunt down places that relied on substring matching
Controlling order with order_by
Sort yourself after fetching. Move paging to after_id and drop any order-dependent logic
Fetching a deep tree at once
Switch to depth=1 plus recursion, following memory_prefix nodes
Human and agent write the same store
Always use a content_sha256 precondition for corrections. Attach reference-only stores as read_only
One last thing. Right after you bump the beta header, snapshot the total count once. The only reason I caught the halving was that I happened to have the previous day's count on hand. Things that change quietly can only be caught by watching quietly.
Memory changes bite hardest in long-running operations. This path_prefix change cut deepest precisely where the tree had been designed most straightforwardly. If you want to dig into the background of that design thinking, I would also point you to Pitfalls and patterns from running Claude agent memory in production.
I am still feeling my way through much of this area. If this gives even one person who was puzzled by a quiet change in row counts a place to start checking, I would be glad. Thank you for reading.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.