Skip to content

[BUG] Seven smaller findings from a v3.0.3 review: dashboard date filter, storage-interface gaps, stale docs #154

Description

@RobertSigmundsson

Description

Housekeeping, bundled deliberately so it costs you one notification instead of seven. Nothing here
is urgent and none of it is a crash on SurrealDB — close any line you disagree with and we will not
raise it again.

A read of v3.0.3 (ac20df41) turned up seven things too small for their own issues. Each is
stated with the command that shows it. None is a crash on the SurrealDB backend; two are wrong
answers, two are interface gaps of the #139 family, three are documentation.

1. The Tool Stats date filter in the dashboard is a no-op

GET /api/dashboard/tool-stats?days=N returns a byte-identical summary for days=7, 30
and 90. The route takes days: int = Query(default=30, ge=1, le=365) and the daily series
honours it, but the summary comes from:

$ grep -n "def get_tool_stats" src/surreal_memory/storage/surrealdb/tool_events.py
205:    async def get_tool_stats(self, brain_id: str) -> dict[str, Any]:

There is no days parameter, so the summary is all-time by construction. The UI shows a working
filter that does not exist. Fixing it means changing the signature, not the call site.

Verified against a brain with 406 rows in tool_events, so the identical responses are not an
artefact of empty data — the three responses hash the same under SHA-256.

2. get_tool_stats / get_tool_stats_by_period exist only on the SurrealDB mixin

Same function, the other half of the problem. Both are defined only in
storage/surrealdb/tool_events.py — nothing in storage/base.py or the in-memory store — and
both are called without a guard:

$ git grep -n "get_tool_stats(" v3.0.3 -- src/
src/surreal_memory/mcp/lifecycle_handler.py:305:   await storage.get_tool_stats(brain.id)  # type: ignore[attr-defined]
src/surreal_memory/server/routes/dashboard_api.py:1543:   await storage.get_tool_stats(brain.id)  # type: ignore[attr-defined]

On any backend other than SurrealDB that is an AttributeError. This is the family #139 moved
onto the interface for pinning and #145 for watch state; the ratchet missed this pair.

3. The _query unwrap heuristic is a trap the next SELECT VALUE walks into

# src/surreal_memory/storage/surrealdb/store.py:686
return result[0] if isinstance(result[0], list) else result

This is the mechanism behind the first bug #143 fixed: SELECT VALUE <array-field> returns a
list of arrays, the heuristic collapses it to the first row, and the caller then iterates that
row's id strings character by character. #143 fixed the one call site that was exposed; the
heuristic is unchanged.

We checked every SELECT VALUE under storage/surrealdb/:

$ grep -rn "SELECT VALUE" src/surreal_memory/storage/surrealdb/ | wc -l
6

Of the six, one is a comment (pinning.py:113, the warning #143 left behind), four are nested
subqueries the unwrap never touches (retrieval_trace.py:177, typed_memory.py:392 and :409,
store.py:1751). Exactly one is top-level — store.py:2712 in get_connected_neuron_ids — and
it is safe only because in/out are scalar record links rather than arrays. Copy that line
onto an array field and #143 happens again.

It also contradicts _query's declared return type of list[dict], which is why mypy is quiet
about it.

4. smem doctor / /healthschema_version went 40 → 9 without a line in the migration guide

#134 was right to fix it: 2.x reported the SQLite constant 40 unconditionally, including on
SurrealDB installs, and 3.x reports the active backend's 9 via _active_schema_version(). But:

$ grep -c -i "schema.version" docs/guides/migrating-to-3.0.md
0

Any monitoring that parses schema_version sees 40 → 9 and reads it as a regression. One
sentence in the guide would cover it, ideally noting that the field to check for a version is
version, not schema_version.

5. The brain-files panel shows SurrealDB-only brains a path to a file that is not there

#144 made brains that live only in SurrealDB visible, which is the right fix. The files panel
then builds a path for each of them regardless:

# src/surreal_memory/server/routes/dashboard_api.py:830-843
db_path = Path(cfg.get_brain_db_path(name))
size = 0
if db_path.exists():
    size = db_path.stat().st_size

For a SurrealDB-only brain the row shows a path to a file that does not exist and
size_bytes: 0. What makes it worse than merely blank: if a stale .db from a 2.x install
happens to still be on disk, that one row looks real while its neighbours do not, and there is
no way for the user to tell the column is fiction.

6. aiosqlite is still a hard dependency — and one helper's name says the wrong thing

$ grep -n "aiosqlite" pyproject.toml
42:    "aiosqlite>=0.19.0",

This is not a leftover to deletedb_introspector.py:385 uses it for smem train-db
against external SQLite files, which is a product feature, and doctor and the test suite use it
too. The most that seems right is moving it to optional-dependencies.

Separately: utils/sandbox.py::ensure_aiosqlite_or_exit_cli reads as though it checks the
aiosqlite package. Its own docstring says otherwise — "SQLite (stdlib sqlite3) is the
minimum requirement"
— and the body checks sqlite3. Worth renaming.

7. AGENTS.md asks for the AI attribution that its own Hard Rule #2 forbids

AGENTS.md:27-46 (Hard Rule #2) says:

Do not put AI attribution in commits, PR descriptions, or the CHANGELOG. No
Co-Authored-By: ... trailer, no Built with: … footer, no "Generated with …" line.
[...] Earlier revisions of this file asked for the opposite. That guidance is withdrawn.

AGENTS.md:105-127 (PR Template), in the same file, says a PR body must contain:

## Verified by

@your-github-handle — built with <agent name + version>.

The second block looks like a leftover of the revision the first block withdraws. It is not
academic: an agent reading the file top to bottom is told to add exactly the footer the hard
rule prohibits, and the local no-ai-attribution hook in .pre-commit-config.yaml will reject
the resulting commit message.

For what it is worth we resolved it in your favour: ## Verified by kept, tool name dropped, on
the grounds that Hard Rule #2 is explicitly the newer of the two and that none of your own merged
PRs carries a tool attribution. We mention it only because we had to pick one and would rather you
knew which — a one-line edit would spare the next contributor the same guess. Raising it rather
than patching it, since AGENTS.md is on the list of files agents are told not to touch without
asking first (AGENTS.md:134).

To Reproduce

Each finding above carries the command that shows it, run on a clean v3.0.3 checkout
(ac20df41) with the package installed. The short version:

# 1 and 2 - the summary has no days parameter, and the method exists on one backend only
grep -n "def get_tool_stats" src/surreal_memory/storage/surrealdb/tool_events.py
git grep -n "get_tool_stats(" v3.0.3 -- src/

# 3 - the unwrap heuristic and the single top-level SELECT VALUE it does not protect
sed -n '686p' src/surreal_memory/storage/surrealdb/store.py
grep -rn "SELECT VALUE" src/surreal_memory/storage/surrealdb/

# 4 - the migration guide never mentions the field that changed
grep -c -i "schema.version" docs/guides/migrating-to-3.0.md

# 5 - the panel builds a path whether or not a file is there
sed -n '830,843p' src/surreal_memory/server/routes/dashboard_api.py

# 6 - a hard dependency, and a helper whose name says the wrong thing
grep -n "aiosqlite" pyproject.toml
sed -n '15,22p' src/surreal_memory/utils/sandbox.py

# 7 - the two blocks of AGENTS.md that contradict each other
sed -n '27,46p;105,127p' AGENTS.md

Expected Behavior

  • (1) ?days=N either filters the summary or is not offered on it.
  • (2) get_tool_stats / get_tool_stats_by_period live on NeuralStorage, as pinning did after
    #139 and watch state after #145.
  • (3) _query either honours its declared list[dict] return type or exposes a separate method
    for scalar projections, so the next SELECT VALUE cannot repeat #143.
  • (4) One sentence in the migration guide about schema_version going 40 → 9.
  • (5) A SurrealDB-only brain shows no file path rather than one that does not exist.
  • (6) aiosqlite sits under optional-dependencies, and the helper's name matches what it checks.
  • (7) AGENTS.md asks for one thing about AI attribution, not two opposite things.

Actual Behavior

  • (1) identical summary for days=7, 30 and 90.
  • (2) AttributeError on any backend other than SurrealDB, hidden behind
    # type: ignore[attr-defined].
  • (3) the heuristic that caused #143 is unchanged; one top-level call site is safe only by
    accident of field type.
  • (4) grep -c -i "schema.version" docs/guides/migrating-to-3.0.md -> 0.
  • (5) a path to a missing file with size_bytes: 0, and one accidentally-real row next to it.
  • (6) hard dependency at pyproject.toml:42; ensure_aiosqlite_or_exit_cli checks sqlite3.
  • (7) an agent following the file top to bottom writes a footer its own hard rule forbids, and
    the repo's no-ai-attribution hook then rejects the commit.

Environment

  • OS: Linux (x86-64)
  • Python version: 3.12.13
  • Surreal-Memory version: 3.0.3 (ac20df41)
  • Installation method: source, pip install -e ".[dev,server,surrealdb]"
  • Backend: SurrealDB 3.2.0

Additional Context

We are happy to send patches for any of these. (1) and (2) are one change to a signature and its
two call sites; (4) is a sentence; (6) is a rename plus a dependency move. (3) and (5) look like
they want a decision from you first.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions