Repository navigation
fix(*): stop naming the memory files when a backend owns memory - #894
Conversation
With a MemoryBackend plugin wired, memory lives in the plugin's store and reaches the turn as the `# Memory` section. The identity block still named two files under agent home, so "remember this" became an edit_file against user_memory/profile/user.md. On a non-interactive turn that write is refused at the ask tier, and the agent reported the refusal as the memory system having failed, while the backend had already stored the turn and recalled it in the next session. identity_text takes a has_memory_backend flag, threaded from the one place each caller already holds the backend: build_context_engine, and AgentLoop for the token-estimation prompt in ContextBuilder, which must render what the turn renders. With a backend the two file lines become one that says memory is not the agent's to write, that the host persists the conversation after the turn ends, and that the agent cannot observe that write so must never report it as done or as failed. The last clause is the other half of the same defect: the store is dispatched after the turn, so a claim either way is one the agent never checked, and whatever it claims is extracted into long-term memory next turn as if it were an observation of the system. Without a backend the rendered prompt is unchanged, byte for byte. Whether the episodic log earns its line at all is a separate question with its own issue, and answering it here would couple the two. The approach is AmirF194's, from the closed PR #216. That branch cannot be reopened: it predates the history linearization, so it shares no merge base with main, its six files no longer apply, and it did not allow maintainer edits. Co-authored-by: Amir Fathi <amirfathi.me@gmail.com> Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: remove the new issue-number references from Python source docstrings.
The backend-presence signal is otherwise threaded consistently through the production loop, token-estimation renderer, unified context factory, and curator wrapper. The no-backend default preserves existing callers, and the tests were strengthened rather than weakened.
Covered: the full diff; AGENTS.md and CONTEXT-MAP.md/Runtime terminology; changed-component callers and backend recall/store lifecycle; relevant history; backward compatibility; test changes; and architecture boundaries. After syncing the declared dev extra with uv sync --extra dev, all 403 tests in every file that directly constructs or calls the changed components passed. The initial run did not reach tests because the fresh environment lacked raven_everos; no test was skipped or weakened.
No live EverOS behavioral run was performed. The remaining item is the repository-rule violation marked inline.
Run against a live EverOSThe description said this had not been driven against a real backend. It has now, and the Four isolated
Item 1, verbatim
("Noted: your bike is a green Brompton called Pickle. But I could not write this into the The backend had stored it the whole time. Same command on this branch, same home layout: No refused action, nothing written under agent home, and the next session answers "Your Item 3, which is why the last clause is in the promptThis is the part worth a reviewer's attention, because it is the half that is easy to Four durable, recallable records of something that never happened. The same grep over the Scope of the evidenceOne run per cell, not a distribution; the wording a model picks varies and these are Teardown: the four EverOS servers were killed by pid, the temporary |
|
No blockers in the reviewed scope. Scope: I reviewed only the I examined the in-scope diff and where its Confirmed findings:
Verification:
Checked and not reported:
|
Review found both wiring lines deletable with the suite green. The existing test hands `IdentitySegmentBuilder` and `ContextBuilder` the flag itself, so it pins that the two renderers agree once told, not that anything tells them. Removing `factory.py`'s argument reverted the user-visible half of the fix and nothing went red. Two tests, each asserting on what a real object renders rather than on an argument passed to it. One builds an `AgentLoop` with and without a backend and reads `loop.context._get_identity()`; one builds the engine through `build_context_engine` and reads the `IdentitySegmentBuilder` it chose. Each was watched failing with its own call site deleted, and the files restored and checksummed afterwards. Also drops the issue numbers from the new test docstrings, which section 1.1 forbids in source. The behavioural rationale stays; each docstring now says what it means without the reference. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
|
You are right, and the mutation you ran is the part that mattered: deleting Fixed in f8d59bf, with both tests asserting on what a real object renders rather
Each was watched failing with its own call site deleted, one mutation at a time, and
797 passed across the 18 files that touch these constructors, up from 756. The blocking finding on |
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
The incremental commit fixes the prior source-documentation violation and adds regression coverage at both decision points: a real AgentLoop now verifies the estimation renderer, and a factory-built engine verifies the request-path identity builder. These tests would fail if either production flag handoff were removed; they do not weaken existing assertions.
Reviewed the incremental diff and rechecked the full change against repository rules, callers, history, backward compatibility, and the memory/context architecture covered in the prior round. uv run pytest across all direct changed-component callers completed with 444 passed. The previously opened blocker thread is resolved.
## Summary
With a `MemoryBackend` plugin wired, memory lives in the plugin's store
and reaches the
turn as the `# Memory` section. The identity block still named two files
under agent home,
so "remember this" became an `edit_file` against
`user_memory/profile/user.md`. On a
non-interactive turn that write is refused at the ask tier, and the
agent reported the
refusal as the memory system having failed -- while the backend had
already stored the
turn and recalled it in the next session.
`identity_text` takes a `has_memory_backend` flag, threaded from the one
place each caller
already holds the backend: `build_context_engine`, and `AgentLoop` for
the
token-estimation prompt in `ContextBuilder`, which must render what the
turn renders.
With a backend the two file lines become one:
```
- Agent home: <home> -- your own skills, not a place for user artifacts.
- Memory is not yours to write: what is relevant is recalled into the `# Memory`
section each turn, and the host persists the conversation after it ends. You
cannot observe that write, so never report it to the user as done or as failed.
- Custom skills: <home>/skills/{skill-name}/SKILL.md
```
Three clauses, three jobs. Do not write it. It is being saved, so stop
looking for a place
to put it. You cannot see that write, so do not narrate it.
The last clause is the other half of the same defect, and the third item
issue 440 asks to
separate. `backend.store` is dispatched after the turn ends, so a claim
either way is one
the agent never checked -- and whatever it claims is extracted into
long-term memory next
turn as if it were an observation of the system. The issue's own repro
produced exactly
that: an episode titled `Memory Save Failure for Green Brompton Bike
'Pickle' Noted on
2026-09-16`, whose content was right and whose narrative was wrong.
Filtering that
server-side is not available from here, so the prompt is where it is
addressed.
Without a backend the rendered prompt is unchanged, byte for byte.
Whether the episodic
log earns its line at all is issue 122's question, and answering it on
this branch would
couple the two.
The approach is AmirF194's, from the closed PR #216. That branch cannot
be reopened: it
predates the history linearization, so it shares no merge base with
`main`
(`git merge-tree` refuses unrelated histories), its six files no longer
apply, and it did
not allow maintainer edits. It is reimplemented here against current
`main`, with the
author credited as co-author on the commit.
## Type
- [x] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other
## Verification
Run on this branch's head, rebased onto `main` at c3d2821, Python
3.12, `--extra dev`.
- `pytest` over the 17 test files that construct `ContextBuilder`,
`IdentitySegmentBuilder`,
`build_context_engine` or call `identity_text` (the `git grep -l` list):
756 passed, 0 failed.
`SERPER_API_KEY` is unset for the run; with it set,
`test_agents_code_launcher.py::test_the_products_tool_face_is_the_forks_config_intent_minus_the_ledger`
fails on an extra `web_search` in the tool face, on this branch and on
the base alike.
- The three new tests were each watched failing without the change, by
neutering the
behaviour in place rather than the signature, then restoring the file
and confirming it
byte-identical by checksum:
- forcing the backend branch off reddens
`test_identity_with_a_backend_names_no_memory_file`;
- dropping the flag from `ContextBuilder._get_identity` reddens
`test_identity_with_a_backend_matches_the_estimation_prompt`;
- `test_identity_without_a_backend_still_names_both_memory_files` pins
the unchanged half.
- The no-backend claim is measured, not argued: the whole identity block
rendered from the
base and from this head is 3341 bytes on both sides and `diff` is empty.
- `make lint-python`, `make lint-types`, `make lint-imports`, `make
check-large-files` -- all exit 0.
- `RANGE=github/main..HEAD scripts/check_source_language.py` -- exit 0.
- `commitlint --config commitlint.config.cjs` on the commit body and on
the prospective
squash header -- both exit 0.
Driven against a live EverOS after this description was first written.
All three
symptoms issue 440 describes reproduce on the merge base and none
reproduce here: on
`main` turn 1 calls `read_file` then `edit_file` on
`user_memory/profile/user.md` and
grows it to 897 bytes, at the ask tier that write is refused and the
agent reports the
save as failed, and EverOS extracts that false report into four durable
memory entries.
On this branch turn 1 makes no tool call, agent home is untouched, and
the same grep over
the store returns nothing. Both sides recall the fact in a fresh
session. Full method,
verbatim output and teardown in a comment on this PR.
One run per cell, not a distribution. The host still dumps `user.md`
into `# Memory` with
a backend wired, which is unchanged here and out of scope below.
- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [ ] User-facing docs or screenshots are updated when needed
No docs change: the only user-facing text is the system prompt itself,
and no document
quotes this block.
## Risk
User-visible change is confined to installs that have a memory backend
wired. There, three
prompt lines become three different ones; nothing else in the prompt,
and no behaviour
outside prompt assembly, moves. Installs without a backend render a
byte-identical prompt,
which is the measurement above.
`has_memory_backend` defaults to `False` on all four signatures it is
added to, so every
call site not updated here renders what it rendered before. The three
updated call sites
are the only ones that hold a backend.
Rollback is a revert of the single commit. Nothing persists and no
format changes.
Checked and deliberately not changed:
- `MemorySegmentBuilder` still calls `get_memory_context()`
unconditionally, so `user.md`
content reaches `# Memory` with a backend wired. Turning that off is the
question of
whether two memory mechanisms should coexist, which is larger than this
issue and which
four comparable systems answer by keeping both.
- The episodic log line, under the default no-backend configuration. It
belongs to issue 122.
- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes
## Related Issues
Fixes #440 -- items 1 and 3 of the three the issue asks to separate,
both reproduced on
the merge base and verified gone here. Item 2 landed in #544.
#122 -- untouched, deliberately.
---------
Co-authored-by: gloryfromca <23442919+gloryfromca@users.noreply.github.com>
Co-authored-by: Amir Fathi <amirfathi.me@gmail.com>
Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
Summary
With a
MemoryBackendplugin wired, memory lives in the plugin's store and reaches theturn as the
# Memorysection. The identity block still named two files under agent home,so "remember this" became an
edit_fileagainstuser_memory/profile/user.md. On anon-interactive turn that write is refused at the ask tier, and the agent reported the
refusal as the memory system having failed -- while the backend had already stored the
turn and recalled it in the next session.
identity_texttakes ahas_memory_backendflag, threaded from the one place each calleralready holds the backend:
build_context_engine, andAgentLoopfor thetoken-estimation prompt in
ContextBuilder, which must render what the turn renders.With a backend the two file lines become one:
Three clauses, three jobs. Do not write it. It is being saved, so stop looking for a place
to put it. You cannot see that write, so do not narrate it.
The last clause is the other half of the same defect, and the third item issue 440 asks to
separate.
backend.storeis dispatched after the turn ends, so a claim either way is onethe agent never checked -- and whatever it claims is extracted into long-term memory next
turn as if it were an observation of the system. The issue's own repro produced exactly
that: an episode titled
Memory Save Failure for Green Brompton Bike 'Pickle' Noted on 2026-09-16, whose content was right and whose narrative was wrong. Filtering thatserver-side is not available from here, so the prompt is where it is addressed.
Without a backend the rendered prompt is unchanged, byte for byte. Whether the episodic
log earns its line at all is issue 122's question, and answering it on this branch would
couple the two.
The approach is AmirF194's, from the closed PR #216. That branch cannot be reopened: it
predates the history linearization, so it shares no merge base with
main(
git merge-treerefuses unrelated histories), its six files no longer apply, and it didnot allow maintainer edits. It is reimplemented here against current
main, with theauthor credited as co-author on the commit.
Type
Verification
Run on this branch's head, rebased onto
mainat c3d2821, Python 3.12,--extra dev.pytestover the 17 test files that constructContextBuilder,IdentitySegmentBuilder,build_context_engineor callidentity_text(thegit grep -llist): 756 passed, 0 failed.SERPER_API_KEYis unset for the run; with it set,test_agents_code_launcher.py::test_the_products_tool_face_is_the_forks_config_intent_minus_the_ledgerfails on an extra
web_searchin the tool face, on this branch and on the base alike.behaviour in place rather than the signature, then restoring the file and confirming it
byte-identical by checksum:
test_identity_with_a_backend_names_no_memory_file;ContextBuilder._get_identityreddenstest_identity_with_a_backend_matches_the_estimation_prompt;test_identity_without_a_backend_still_names_both_memory_filespins the unchanged half.base and from this head is 3341 bytes on both sides and
diffis empty.make lint-python,make lint-types,make lint-imports,make check-large-files-- all exit 0.RANGE=github/main..HEAD scripts/check_source_language.py-- exit 0.commitlint --config commitlint.config.cjson the commit body and on the prospectivesquash header -- both exit 0.
Driven against a live EverOS after this description was first written. All three
symptoms issue 440 describes reproduce on the merge base and none reproduce here: on
mainturn 1 callsread_filethenedit_fileonuser_memory/profile/user.mdandgrows it to 897 bytes, at the ask tier that write is refused and the agent reports the
save as failed, and EverOS extracts that false report into four durable memory entries.
On this branch turn 1 makes no tool call, agent home is untouched, and the same grep over
the store returns nothing. Both sides recall the fact in a fresh session. Full method,
verbatim output and teardown in a comment on this PR.
One run per cell, not a distribution. The host still dumps
user.mdinto# Memorywitha backend wired, which is unchanged here and out of scope below.
No docs change: the only user-facing text is the system prompt itself, and no document
quotes this block.
Risk
User-visible change is confined to installs that have a memory backend wired. There, three
prompt lines become three different ones; nothing else in the prompt, and no behaviour
outside prompt assembly, moves. Installs without a backend render a byte-identical prompt,
which is the measurement above.
has_memory_backenddefaults toFalseon all four signatures it is added to, so everycall site not updated here renders what it rendered before. The three updated call sites
are the only ones that hold a backend.
Rollback is a revert of the single commit. Nothing persists and no format changes.
Checked and deliberately not changed:
MemorySegmentBuilderstill callsget_memory_context()unconditionally, souser.mdcontent reaches
# Memorywith a backend wired. Turning that off is the question ofwhether two memory mechanisms should coexist, which is larger than this issue and which
four comparable systems answer by keeping both.
The episodic log line, under the default no-backend configuration. It belongs to issue 122.
Security impact considered
Backward compatibility considered
Rollback path is clear for risky changes
Related Issues
Fixes #440 -- items 1 and 3 of the three the issue asks to separate, both reproduced on
the merge base and verified gone here. Item 2 landed in #544.
#122 -- untouched, deliberately.