Why the oldest lines are the ones that go
Persistent memory in OpenHands is off unless you ask for it. The documentation is explicit: the feature is "opt-in and off by default," and without it "agents keep the existing AGENTS.md-based guidance and prompts are unchanged." You enable it by setting load_memory to true on the agent's AgentContext.
Once on, there are two tiers. A user tier at ~/.openhands/memory/ holds "knowledge and preferences that apply across all projects," and a project tier at <workspace>/.openhands/memory/ holds "knowledge specific to the current repository." Each tier contains a MEMORY.md — described as "a curated index of durable facts. This is the only file injected into the prompt" — plus dated daily logs which "are never injected automatically; the agent reads them on demand with its file tools when MEMORY.md points to them."
That split is the important architecture, and it is also where the eviction comes from:
"Size budget: the combined indexes are capped at ~6,000 characters. When the budget is exceeded, whole lines are dropped from the top of each over-budget tier (the oldest content) -- partial lines never survive, the tier headers are always kept, and a truncation notice appears under the header of any tier that lost lines. Keep indexes curated."
Read it slowly, because four separate behaviors are packed in there.
The cap is on the combined indexes across both tiers, so your personal preferences and your project facts compete for the same budget. A verbose user-tier file eats into what the project tier can carry.
Lines are dropped from the top, and the documentation glosses the top as "the oldest content." Since the agent appends as it learns, the top of the file is the earliest thing it ever recorded. Most caps evict the newest thing or fail the write. This one removes your foundations first.
The dropping is line-granular and clean — "partial lines never survive" — so you never get a half-sentence that changes meaning. Good design, and it means a single long line is all-or-nothing.
And there is a signal: "a truncation notice appears under the header of any tier that lost lines." Nothing is silent here if you look. The problem is that nobody looks at an injected system-prompt block during a normal working session.
Worth noting for anyone running multiple tools: 6,000 characters is the same budget Devin Desktop documents for its global rules file. Somewhere around six thousand characters is where several vendors independently decided an always-loaded file stops being worth its cost, which is a useful number to internalize regardless of which tool you use.
What people try instead
Raising the budget. The first instinct, and the documentation does not offer it as a knob — the cap is described as a property of the feature. Even if it were adjustable, a bigger always-loaded block is a worse trade, not a better one, for the reasons how much memory you should give an AI agent works through.
Rewriting MEMORY.md by hand every few weeks. This works and it is what "keep indexes curated" is asking for. It is also a chore with no trigger, so it happens twice and then stops, and the file grows back.
Moving everything into AGENTS.md so it is never evicted. Tempting, and it misreads the division of labor the documentation sets out. The agent is instructed to "keep AGENTS.md for instructions addressed to any agent working in the repository -- memory is for what the agent learned itself." Dumping learned history into an instruction file gives you a long always-loaded file with a different name and no eviction notice at all.
Putting the important facts in a skill so they load on demand. A reasonable instinct that runs into the wrong mechanism, for the reasons in why agent skills aren't memory. Skills answer "how do I do this task." A rejected architectural alternative is not a task.
Committing .openhands/memory/ and treating it as team documentation. The docs explicitly allow this — "a project team can even commit .openhands/memory/ to share agent-learned knowledge" — and it is genuinely useful. But it makes the eviction problem worse rather than better, because now several people's agents are appending to a shared index against the same 6,000-character ceiling.
There is a further reason none of these quite work, and it is the sentence people miss:
"Untrusted by design: the injected block is wrapped in<UNTRUSTED_CONTENT>. Memory files are typically agent-written, but anyone with access to the workspace or repository can edit or commit them (a cloned repo may ship a.openhands/memory/MEMORY.md), so the agent is told they may contain prompt injection, and to treat them as unverified hints, never as authoritative instructions."
The agent is instructed to treat its own memory as unverified hints. That is the correct security posture — a cloned repository really can ship a memory file — and it settles a design question for you. Anything that must be followed cannot live in memory, because memory is explicitly not authoritative. Memory is for context the agent may find useful. Instructions belong in AGENTS.md, where they are addressed to any agent and read as instructions.
So the two documented properties combine into one rule: the memory index is a small, non-authoritative, lossy-at-the-top pointer file. Treat it as anything more and it will disappoint you in one of two ways.
The Fix: Keep the index a pointer, not a store
Three steps. The first two cost ten minutes; the third is what stops the problem recurring.
Step 1: Turn MEMORY.md into an index of pointers
The documentation already tells you the intended shape — MEMORY.md is "a curated index of durable facts," the agent is told to "put long detail in daily logs," and the logs are read "on demand with its file tools when MEMORY.md points to them."
So every line in the index should be short and should point somewhere. One line per fact, phrased so the agent knows both what is true and where the detail is. Prose paragraphs, code samples, and long explanations move into the dated log files, where they are read only when needed and cost nothing against the budget.
Do this and the 6,000-character cap stops being a constraint. A hundred one-line pointers fit comfortably; a dozen paragraphs do not.
Step 2: Reorder the index so the top is disposable
Because eviction removes lines from the top and the top is the oldest content, the file's chronological order works directly against you. Fix it by making the order semantic instead.
Put the facts you would be sorry to lose at the bottom of each tier's index — the ones about architecture, constraints, and long-standing decisions. Put transient operational notes at the top. Now when the budget is exceeded, the lines that go are the ones you would have pruned anyway.
Then rebalance across tiers. Because the cap is combined, a bloated ~/.openhands/memory/MEMORY.md full of personal preferences is directly taking space from project facts. Keep the user tier to genuine cross-project preferences and let the project tier have the room.
While you are in there, check for the truncation notice under each tier header. If it is present, you have already lost lines, and the daily logs are where you will find what they said — which is a good argument for doing Step 1 first.
Step 3: Separate the three things currently sharing one file
The index is now holding three kinds of content that want different homes.
Instructions — things that must be followed — belong in AGENTS.md, per the documentation's own division. They are not memory, and memory is not authoritative.
Agent-learned operational detail — the environment quirk, the flaky test, the command that actually works — belongs exactly where it is: a pointer line in the index and the detail in a daily log. This is what the feature is for.
Project decisions and their reasons — why the queue library was rejected, what the compliance constraint actually requires, what was tried in March and did not work — belong in neither. They are not instructions, so AGENTS.md is wrong. They must not be lossy or non-authoritative, so the memory index is wrong. And they need to be answerable by every tool your team uses, not just the one with load_memory switched on.
Setting this up in MemoryLake
MemoryLake is the home for that third category. It holds decisions and their reasons outside any single agent, with no always-loaded budget to compete for, and answers questions about them over MCP or the API. Your MEMORY.md stays a short pointer index and OpenHands keeps maintaining it exactly as documented; the durable reasoning lives somewhere a character cap cannot reach.
Step 1: Create an API key
Generate a key and make your first request in about thirty seconds. Do this before Step 2 above, so you have somewhere to move each decision as you reorder the index.

Step 2: Upload your first memories
Read through the current index and the daily logs it points at. Every entry that is a decision rather than an observation gets written down with what was chosen, what was rejected, and why. Supporting documents and files go in the same place.

Step 3: Connect your AI & agents
Give OpenHands, Claude, Codex, and your other agents access over MCP or the API. When any of them needs to know why the project is the way it is, the answer arrives with its reasoning attached instead of competing for space in a system prompt.

What this changes in practice
The first change is that eviction stops mattering. A pointer index ordered with the disposable content at the top loses only what you would have pruned, and the detail behind every pointer is still in a log file the agent can open.
The second is that the memory feature gets to be good at its actual job. Recording that the integration tests need a specific environment variable is exactly what an agent-maintained store should do, and it does that well when it is not also being asked to be the team's decision record.
The third is that the untrusted-content framing stops being a problem. Once nothing load-bearing is in memory, the fact that the agent treats it as unverified hints is simply correct — and the things that do need to be authoritative are in AGENTS.md, where they are read as instructions.
The fourth is that committing .openhands/memory/ becomes safe to do at team scale. Several agents appending pointer lines against a 6,000-character budget is sustainable in a way that several agents appending paragraphs is not. And when two entries genuinely disagree, you have somewhere to resolve it rather than letting alphabetical or chronological accident decide — the problem memory conflict detection exists to catch.
Best practices for OpenHands persistent memory
One line, one fact, one pointer. The index is documented as a curated index. Anything longer than a line belongs in a daily log with the index pointing at it.
Order by importance, not by time. Eviction takes lines from the top. Put what you can afford to lose there.
Budget the two tiers against each other. The cap is combined, so a verbose user tier silently shrinks your project tier.
Check for the truncation notice. It appears under the header of any tier that lost lines. It is the only signal you get, and it is reliable if you look.
Never put credentials in memory. The agent's own instructions say to never record secrets or credentials. Do not undercut that by adding them yourself, and audit periodically — auditing what your AI remembers covers the habit.
Skip anything trivially re-discoverable. The maintenance instructions say as much: directory listings and obvious commands cost budget and teach nothing.
Keep instructions out of memory entirely. Memory is documented as unverified hints. If it must be obeyed, it goes in AGENTS.md.
Remember memory is re-read, not stored in the session. The docs note the resolved text is excluded from conversation persistence and API payloads and re-read from disk each session, so editing the file by hand takes effect on the next conversation.
Conclusion
OpenHands documents its persistent memory precisely: opt-in and off by default, two tiers, only MEMORY.md injected, a combined cap of about 6,000 characters, whole lines dropped from the top of an over-budget tier with a truncation notice under the header, and the whole block wrapped in an untrusted-content marker the agent is told to treat as unverified hints rather than authoritative instructions. Every one of those is a reasonable decision. Together they describe a small, lossy, advisory pointer file — which is a genuinely useful thing and not a place to keep your project's reasoning.
Keep the index to one-line pointers, order it so the top is what you can afford to lose, and split out the three kinds of content currently sharing it. Instructions go to AGENTS.md. Observations stay in memory and its logs, which is what the feature was built for. Decisions and their reasons go somewhere with no character budget and no expiry, because those are the ones you will still need in a year — and, as keeping less in agent memory argues, a smaller always-loaded file is better on every axis once the rest has a home.