fix(storage): skip non-string keys in SnapshotVars 🤖🤖🤖 - #295
fix(storage): skip non-string keys in SnapshotVars 🤖🤖🤖#295sushant-mishra-dtu wants to merge 1 commit into
Conversation
SnapshotVars exists so "one bad value can't take down the whole snapshot
(and with it every other var) on the next resume". It checks the value on
write and skips it, but never checks the key, and snapshots are JSON.
A single non-string key therefore does the exact thing the class was
built to prevent: serialize() raises on it, AgentSnapshot.from_agent()
catches SerializationError and drops the whole attribute, and every other
var in the container goes with it.
a.vars["safe_var"] = "valuable state"
a.vars[123] = "bad key"
"vars" in AgentSnapshot.from_agent(a).attributes -> False
Validate the key the same way the value is already validated: warn,
name the offending key, and skip the store. This also matches the
existing `key: str` annotation.
🤖🤖🤖
Signed-off-by: sushant-mishra-dtu <sushant.arh@gmail.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 12 included reviews per hour; 11 remain after this review. 📝 WalkthroughWalkthrough
ChangesSnapshot key validation
Estimated code review effort: 1 (Trivial) | ~5 minutes Merge Risk: ⚪ Minimal · up to Snapshots now ignore invalid non-string variable keys rather than allowing them to disrupt serialization of valid variables. The targeted behavior is covered, with no remaining merge-blocking risk identified. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
What this fixes
SnapshotVarsexists for one reason, stated in its own module docstring(
src/nooa/storage/snapshot_vars.py:6-10):It checks the value on write and skips it. It never checks the key
(
src/nooa/storage/snapshot_vars.py:57-69):Snapshots are JSON, and
serialize()rejects non-string dict keys outright(
src/nooa/storage/serialization.py:124-128). So a single non-string key does the exactthing this class was built to prevent: it survives the write, then blows up at snapshot
time, where
AgentSnapshot.from_agent()catchesSerializationErrorand drops the wholeattribute (
src/nooa/storage/snapshot.py:109-121) -- taking every other var with it.The comment right there names the consequence exactly (
snapshot.py:112-114):That guard is doing its job.
SnapshotVarsis the layer meant to stop anything reaching it,and for keys it does not.
Why it matters
self.varsis agent-writable surface. It isInteractiveAgent's persistent variable store(
src/nooa/interactive.py:346,:351), documented as "survives across turns AND acrosssessions", and it is the type of
Todo.vars(src/nooa/tools/todo.py:77), which coercesany dict handed to it (
:90). An agent writingself.vars[step] = statewith an integerstep, or
Todo(vars={1: "..."}), gets a container that looks like it stored the value.The loss is silent from the agent's side: the write succeeds, the read succeeds, and the
warning that fires is at snapshot time, about
varsas a whole, not about the key thatcaused it. On the next
/exit+ resume every durable variable is gone.Reproduction
On
main:safe_varwas never at fault and is lost anyway. On this branch:The fix
Validate the key the way the value is already validated -- warn, name the offending key,
skip the store:
Nine lines, no signature change. It matches the existing
key: strannotation and theclass's documented "skips the store, logs a warning" contract, so the behaviour is the one
already described in the docstring. It also moves the warning to the write that caused it,
which is where a user can act on it.
Test
One test,
test_snapshot_serialize_succeeds_even_after_non_string_key_write, placed besidethe existing
test_snapshot_serialize_succeeds_even_after_bad_writeit mirrors. Verified tofail on the unfixed tree before being kept:
tests/storage/test_snapshot_vars.pyis 15 passed on this branch against 14 onmain.Scope
Only the missing key check. Nothing else in the container changes: reads, iteration,
deletion and the value path are untouched, and a non-string key was never retrievable from
a restored snapshot anyway -- it was only ever taking the rest of the snapshot down with it.
Summary by CodeRabbit