Skip to content

fix(storage): mm agent list misses registered-but-empty namespaces#473

Merged
memtomem merged 2 commits intomainfrom
fix/agent-list-empty-namespace
Apr 25, 2026
Merged

fix(storage): mm agent list misses registered-but-empty namespaces#473
memtomem merged 2 commits intomainfrom
fix/agent-list-empty-namespace

Conversation

@memtomem
Copy link
Copy Markdown
Owner

Repro

$ mm agent register planner --description "Architecture & roadmap"
Agent registered: planner
- Namespace: agent-runtime:planner
- Shared namespace: shared
$ mm agent register coder
Agent registered: coder
- Namespace: agent-runtime:coder
- Shared namespace: shared
$ mm agent list
Agents: 0

Shared: shared (0 chunk(s)) — Shared knowledge base for all agents

Both registers reported success and wrote a namespace_metadata
row, 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 /namespaces route had the same blind spot via
the same storage method — registered-but-empty namespaces never
appeared in the namespaces tab.

Root cause

list_namespace_meta in sqlite_namespace.py was

SELECT c.namespace, COUNT(*), m.description, m.color
FROM chunks c
LEFT JOIN namespace_metadata m ON c.namespace = m.namespace
GROUP BY c.namespace

Iterating from the chunks side meant a namespace_metadata row
with 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 list was
guaranteed to lose planner.

shared continued to show up 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.
That asymmetry made the bug class non-obvious.

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:

SELECT
    ns.namespace,
    COALESCE(c.chunk_count, 0),
    COALESCE(m.description, ''),
    COALESCE(m.color, '')
FROM (
    SELECT namespace FROM namespace_metadata
    UNION
    SELECT DISTINCT namespace FROM chunks
) ns
LEFT JOIN (SELECT namespace, COUNT(*) AS chunk_count FROM chunks GROUP BY namespace) c
    ON c.namespace = ns.namespace
LEFT JOIN namespace_metadata m ON m.namespace = ns.namespace
ORDER BY ns.namespace

Three states surface correctly now:

State Pre-fix Post-fix
metadata only (registered, 0 chunks) invisible (the bug) listed, chunk_count=0
chunks only (legacy / un-registered) listed listed (unchanged)
both listed listed (unchanged)

No caller-side signature change. The same return shape
({namespace, chunk_count, description, color}) is preserved, so
the Web GET /namespaces route picks up the fix transparently.

Why tests didn't catch this

tests/test_agent_cmd.py stubs the storage with AsyncMock
returning canned list_namespace_meta payloads — the SQL bug
never tripped because the SQL never ran. This is the
feedback_storage_artifact_false_pass.md pattern 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::TestNamespace against the real
SQLite backend:

  1. test_list_namespace_meta_includes_registered_empty_namespace
    — exact reproducer (set_namespace_meta then
    list_namespace_meta with no upsert_chunks in between).
  2. 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 passed
  • uv run ruff check + format --check — clean
  • Manual: rerun the repro after merge — mm agent register planner && mm agent list should show Agents: 1.

🤖 Generated with Claude Code

pandas-studio and others added 2 commits April 25, 2026 17:11
`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]>
@memtomem memtomem merged commit d985c25 into main Apr 25, 2026
7 checks passed
@memtomem memtomem deleted the fix/agent-list-empty-namespace branch April 25, 2026 08:28
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 25, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants