Description
This one predates 3.x entirely and is latent rather than burning — we are not affected today, and
we mention it only because it is the kind of thing that is much cheaper to fix before someone
trips over it than after.
server/routes/hub.py calls storage.set_brain(...) from two GET handlers. The storage
object is the process-wide singleton, and the consolidation and decay loops read
storage.brain_id off that same singleton when they wake up. So a read-only request can decide
which brain the next scheduled maintenance pass operates on.
To Reproduce
- Start the server with consolidation enabled and at least two brains,
A (active) and B.
curl http://127.0.0.1:8000/hub/status/B — a GET, no body, nothing documented as mutating.
- Wait for the next consolidation tick.
- It runs against
B.
Without a running server, the wiring is visible statically:
$ grep -n "set_brain" src/surreal_memory/server/routes/hub.py
139: storage.set_brain(body.brain_id) # POST /register — expected
214: storage.set_brain(body.brain_id) # POST /sync — expected
269: storage.set_brain(brain_id) # GET /status/{brain_id}
296: storage.set_brain(brain_id) # GET /devices/{brain_id}
350: storage.set_brain(body.brain_id) # POST /sync/merkle — expected
$ grep -n "brain_id = storage.brain_id" src/surreal_memory/server/app.py
136: 203: 237: 270: 338:
_consolidation_loop and _decay_loop are started at server/app.py:103 and :110 with that
same storage instance.
Expected Behavior
A GET handler answers a question about brain_id without changing what the process considers
the current brain. Either resolve the brain per request (a scoped context or a per-request
storage view), or restore the previous brain before returning.
Actual Behavior
The brain switch outlives the request. The get_storage override in server/app.py:505-537
does not reset the context for requests that arrive without an X-Brain-ID header, so nothing
puts it back.
Code Sample
# src/surreal_memory/server/routes/hub.py — inside the GET handler at line 261
@router.get(...)
async def hub_status(brain_id: str, ...):
...
storage.set_brain(brain_id) # line 269 — mutates the shared singleton
Environment
- OS: Linux (x86-64)
- Python version: 3.12.13
- Surreal-Memory version: 3.0.3 (
ac20df41)
- Installation method: source
Additional Context
The file is byte-identical across v2.20.1 and v3.0.3, so this is long-standing rather than a
regression:
$ git diff v2.20.1 v3.0.3 -- src/surreal_memory/server/routes/hub.py
(empty)
We have not sent a patch because the fix is a design choice — per-request brain context versus
save/restore around the handler — and that is yours to make. Say which shape you want and we
will write it.
On our own install the path is latent rather than live: consolidation is enabled on a 24-hour
schedule, and nothing here calls /hub/*. It becomes live for anyone who does.
Description
This one predates 3.x entirely and is latent rather than burning — we are not affected today, and
we mention it only because it is the kind of thing that is much cheaper to fix before someone
trips over it than after.
server/routes/hub.pycallsstorage.set_brain(...)from two GET handlers. The storageobject is the process-wide singleton, and the consolidation and decay loops read
storage.brain_idoff that same singleton when they wake up. So a read-only request can decidewhich brain the next scheduled maintenance pass operates on.
To Reproduce
A(active) andB.curl http://127.0.0.1:8000/hub/status/B— a GET, no body, nothing documented as mutating.B.Without a running server, the wiring is visible statically:
_consolidation_loopand_decay_loopare started atserver/app.py:103and:110with thatsame
storageinstance.Expected Behavior
A GET handler answers a question about
brain_idwithout changing what the process considersthe current brain. Either resolve the brain per request (a scoped context or a per-request
storage view), or restore the previous brain before returning.
Actual Behavior
The brain switch outlives the request. The
get_storageoverride inserver/app.py:505-537does not reset the context for requests that arrive without an
X-Brain-IDheader, so nothingputs it back.
Code Sample
Environment
ac20df41)Additional Context
The file is byte-identical across
v2.20.1andv3.0.3, so this is long-standing rather than aregression:
We have not sent a patch because the fix is a design choice — per-request brain context versus
save/restore around the handler — and that is yours to make. Say which shape you want and we
will write it.
On our own install the path is latent rather than live: consolidation is enabled on a 24-hour
schedule, and nothing here calls
/hub/*. It becomes live for anyone who does.