Repository navigation
fix(memory): take the outside result in one query, not a fixed window - #283
Conversation
_last_outside_result scanned only the newest 12 tool results, so a held memory showed no snippet once more than 12 failed or declined calls came after the real outside content (existence-master#270). Ask SQLite directly for the newest tool result from an untrusted-content tool that is not an error or decline, so any number of failed calls after the read can never bury it. A CASE guard means a non-JSON or non-object result neither raises nor counts as a failure. Regression test: a real read with 60 declined calls after it, plus a non-JSON result, still recovers the original snippet.
|
All contributors have signed the CLA ✍️ ✅ |
|
I have read the CLA Document and I hereby sign the CLA |
itsskofficial
left a comment
There was a problem hiding this comment.
Thanks @Reitzzz for picking this up, the fix works! I ran it for real on an engine from your branch: one approved page read, then 16 declined browser_open calls, then memory_remember. The held memory's snippet was the page text, and on the same database the old 12-row window returned nothing. Your new test also fails on the old code, which is great. The query uses the session index too, so it stays fast even on very long chats: about 10 ms under 20,000 declines.
One thing before merge: the SQL check for "failed" doesn't decide things quite the way the old _failed() did, which used Python truthiness. Two cases:
- Now skipped wrongly. SQLite treats
0 != ''as true and returns{}and[]as non-empty text, so a successful result like{"error": false, "data": ...},{"error": 0}or{"error": {}}is now skipped as failed. - Now accepted wrongly.
{"declined": "yes"}or{"declined": {...}}counts as a success, because SQLite reads that text as 0.
The engine's own errors and declines still match. But MCP and web results can carry an error key with any value.
Simplest fix: let SQL do only the cheap part (session, role, the name IN list and the ordering, which uses the index) and keep _failed() in Python. Read rows in pages, say 50 at a time with LIMIT ? OFFSET ?, and return the first one _failed() accepts. That keeps your #270 fix with no fixed window and keeps the old meaning exactly. Please also add a test where a successful result has "error": false and is still picked, and update the docstring to match.
Addresses review on existence-master#283: SQLite's text coercion diverged from the old _failed() semantics -- {"error": false}/{"error": 0}/{"error": {}} were wrongly skipped and {"declined": "yes"} was wrongly accepted. SQL now does only the index-backed part (session, role, name IN, ordering) with no fixed window; _failed() stays in Python and rows are read 50 at a time. Adds a test that a falsey error field is still picked.
|
Thanks for the thorough review and for actually running it against an engine — good catch on the truthiness mismatch. Pushed Added CI is green (Engine tests ubuntu + windows, Lint, Desktop build, Secret scan) and CodeRabbit found no actionable comments. |
itsskofficial
left a comment
There was a problem hiding this comment.
Approved after a real end-to-end test.
|
Merged, thanks @Reitzzz! I re-ran the real test on |
Summary
_last_outside_resultscanned only the newest 12 tool results, so a heldmemory showed no snippet once more than 12 failed or declined calls came
after the real outside content (#270).
This asks SQLite directly for the newest result from an untrusted-content
tool that isn't an error or decline, so no fixed window can bury it — the
approach you suggested on the issue. A
CASE WHEN json_validguard means anon-JSON or non-object result neither raises nor counts as a failure.
Testing
Regression test (
test_review.py): a real read with 60 declined calls afterit, plus a non-JSON result, still recovers the original snippet.
pytest tests/memory+tests/test_untrusted_content.py→ 130 passed.Closes #270
Summary by CodeRabbit