fix(storage): mm agent list misses registered-but-empty namespaces#473
Merged
fix(storage): mm agent list misses registered-but-empty namespaces#473
Conversation
`mm agent register planner && mm agent list` printed `Agents: 0`
even though both `Agent registered: planner` and `Agent registered:
coder` ran successfully. The metadata rows existed; the listing
just never saw them.
Root cause: `list_namespace_meta` queried
`FROM chunks c LEFT JOIN namespace_metadata m`
which iterated the chunks side, so a `namespace_metadata` row with
no matching `chunks` row was filtered out by `GROUP BY c.namespace`.
Newly-registered agents have no chunks yet, so they were invisible
until someone wrote into their namespace. The Web UI's
`GET /namespaces` route had the same blind spot via the same
storage method.
`shared` continued to appear in `mm agent list` only because the
CLI fetches it separately via `get_namespace_meta(SHARED_NAMESPACE)`
(`agent_cmd.py:168`), which reads `namespace_metadata` directly.
Fix: source `list_namespace_meta` from the union of
`namespace_metadata.namespace` and `chunks.namespace`, then LEFT
JOIN both sides for chunk count + description/color. Three states
now surface correctly:
- metadata only (registered, 0 chunks) — the bug
- chunks only (legacy / un-registered chunks)
- both (registered + has chunks)
Test coverage: existing `test_agent_cmd.py` mocks the storage
method, so the SQL bug never tripped (the
`feedback_storage_artifact_false_pass.md` pattern). Add two
regression tests against the real SQLite backend in
`test_server_tools_org.py::TestNamespace`:
- `test_list_namespace_meta_includes_registered_empty_namespace`
pins the exact reproducer (`set_namespace_meta` then
`list_namespace_meta` with no chunks in between)
- `test_list_namespace_meta_unions_chunks_and_metadata`
enumerates all three union states in one fixture so a future
schema regression on either side fails loudly
Reported by an external tester running the v0.1.28 multi-agent
test scenarios walkthrough at scenario 1 step 1 — exactly the
class of bug the test scenarios doc was written to surface.
Co-Authored-By: Claude <[email protected]>
Two reviewer minors: - Drop the redundant `DISTINCT` from the inner `SELECT namespace FROM chunks` — the outer `UNION` already dedupes, so the inner DISTINCT only forced an extra sort pass. Cosmetic perf hit, no semantic change. - Add a symmetric `color == ""` assertion to the chunks-only state in the union-states regression test, mirroring the existing `description == ""` line. Locks the COALESCE empty-string fallback contract for both metadata fields, not just one. Skipping reviewer #3 (docstring on the empty-string contract) and #4 (Web `GET /namespaces` e2e for registered-but-empty case) per the reviewer's own "안 해도 됨" / "별도 follow-up PR 가능" notes. Co-Authored-By: Claude <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Repro
Both registers reported success and wrote a
namespace_metadatarow, but the listing showed zero agents. The user-visible symptom
is "did the register actually take effect?"; the answer is yes,
the listing was just blind to namespaces that had no chunks yet.
The Web UI's
GET /namespacesroute had the same blind spot viathe same storage method — registered-but-empty namespaces never
appeared in the namespaces tab.
Root cause
list_namespace_metainsqlite_namespace.pywasIterating from the
chunksside meant anamespace_metadatarowwith zero matching chunks rows produced zero output rows. Newly-
registered agents have zero chunks until someone writes into their
namespace, so
mm agent register planner && mm agent listwasguaranteed to lose
planner.sharedcontinued to show up inmm agent listonly because theCLI fetches it separately via
get_namespace_meta(SHARED_NAMESPACE)(
agent_cmd.py:168), which readsnamespace_metadatadirectly.That asymmetry made the bug class non-obvious.
Fix
Source
list_namespace_metafrom the union ofnamespace_metadata.namespaceandchunks.namespace, then LEFTJOIN both sides for chunk count + description/color:
Three states surface correctly now:
chunk_count=0No caller-side signature change. The same return shape
(
{namespace, chunk_count, description, color}) is preserved, sothe Web
GET /namespacesroute picks up the fix transparently.Why tests didn't catch this
tests/test_agent_cmd.pystubs the storage withAsyncMockreturning canned
list_namespace_metapayloads — the SQL bugnever tripped because the SQL never ran. This is the
feedback_storage_artifact_false_pass.mdpattern in memory:asserting on processing-stage spies instead of the actual
storage-layer artifact gives a false PASS.
Two regression tests added in
tests/test_server_tools_org.py::TestNamespaceagainst the realSQLite backend:
test_list_namespace_meta_includes_registered_empty_namespace— exact reproducer (
set_namespace_metathenlist_namespace_metawith noupsert_chunksin between).test_list_namespace_meta_unions_chunks_and_metadata—enumerates all three union states in one fixture so a
regression on either side fails loudly.
How it was found
External tester running the v0.1.28 multi-agent test scenarios
walkthrough (
memtomem-docs/memtomem/testing/multi-agent-test-scenarios.md,private repo PR #3) caught this at scenario 1 step 1 — exactly the
class of bug the scenarios doc was written to surface. This is a
data point for the doc's value: gate-2-precondition (external e2e
report) effectively delivered a real bug report on first contact.
Test plan
uv run pytest packages/memtomem/tests/test_server_tools_org.py::TestNamespace -q— 21 passed (incl. 2 new)uv run pytest packages/memtomem/tests/test_agent_cmd.py packages/memtomem/tests/test_web_routes_extended.py -q— 72 passeduv run ruff check + format --check— cleanmm agent register planner && mm agent listshould showAgents: 1.🤖 Generated with Claude Code