You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
memory_dir categorization is currently flat: the server-returned category field (from categorize_memory_dir in config.py) is one of user, claude-memory, claude-plans, codex. The Web UI's Sources-tab memory_dirs panel renders these as four peer groups in a fixed order
(sources-memory-dirs.js: _MEMORY_DIR_CATEGORY_ORDER).
The flat model doesn't match the mental model it describes. claude-memory
and claude-plans aren't peers of codex — they're two products of one
vendor (Anthropic's Claude tooling). If Claude later exposes a third dir
type (skills? agents? another local surface), the flat bucket list keeps
growing sideways instead of nesting under claude.
The same question is latent for codex — it is currently a single bucket
and a single vendor (OpenAI), but if/when OpenAI adds another local dir
surface, the codex → openai/codex migration would be forced on users
by that external decision rather than chosen now.
parent collapse; user open, vendors closed; no per-child state
Q5
product-level reindex only
Q6
keep codex category; tag provider: "openai"
Corrections to the Phase 1 / Phase 2 sketches below (original sketches
preserved for historical record):
Phase 1 mapping table last row: codex → codex is now codex → openai (Q6).
Phase 2 parent-label i18n keys: sources.memory_dirs.provider.{user,claude,openai}
— key matches the server provider wire value, notcodex. Category leaf codex keeps its existing category.codex key.
Phase 2 also adds a value-side vocabulary lock _VALID_PROVIDERS = frozenset({"user", "claude", "openai"}) paired with the
category-axis lock from fix(config): lock provider category vocabulary (RFC #304 prep) #313, so adding a new _CATEGORY_TO_PROVIDER entry
with an unknown provider value trips an import-time assert.
Phase A follow-up audit (see #314 comment) folded in here:
Literal[...] typing sweep on categorize_memory_dir — deferred.
UI parallel-literal in sources-memory-dirs.js:249-259 — absorbed by Phase 2.
PR #301 (closes #299) shipped the server-side category field deliberately
scoped to classification drift only — no API shape change, one additive
field. Layering a vendor/product hierarchy on top of that would have broken
the thesis in five ways:
API shape change.category: str becomes either a {provider, product} object, a path-style string ("claude/memory"),
or a flat string plus a separate provider field. All three change
client parsing.
i18n rewrite. Four flat keys (category.user, .claude_memory, .claude_plans, .codex) become parent/child two-axis keys; en+ko
parity re-check + graceful deprecation of the existing keys.
UI complexity._MEMORY_DIR_CATEGORY_{ORDER,LABEL_KEY,COLLAPSED}
are flat dicts today. A tree render needs nested <details> with
parent/child collapse state and a re-scoped "Reindex group" button
(operates on parent? on child? both?).
Asymmetric leaves.claude has two children (memory, plans), codex has none, user has none. Do lone leaves render under a
synthetic parent, or does the tree mix flat-top-level and nested nodes?
Codex consistency. If the hierarchy axis is "vendor", codex
eventually becomes openai/codex, forcing a user-facing migration for
existing configs. Keeping codex flat under a vendor tree breaks
symmetry; migrating it bloats the PR.
Each of these is a design decision in its own right. Bundling them under a
drift-fix PR would've violated the scope discipline established by the #295 → #297 → #300 → #301 sequence (one concern per PR).
Design questions (resolved 2026-04-20 — see block above)
Kept for historical record. Q1–Q6 are resolved in the
"Resolved 2026-04-20" block above; Q7 ("Out of scope") is a scope
declaration and is unchanged.
Axis: vendor vs source. Is the parent level "who made this tool"
(claude, openai, ...) or "who supplied this path" (user-provided
vs provider-provided)? If "source", user is a peer of claude/ openai. If "vendor", user has no natural vendor and needs either
a synthetic user bucket or a mixed tree/flat renderer.
Where does user live? Top-level peer of the vendor groups, or
an escape-hatch bucket common to all vendors? Peer = simpler tree,
smaller blast radius.
Wire format. Three options:
Keep category: "claude-memory" string, add sibling provider: "claude" string (additive, no type change, easy client migration).
Switch to path-style category: "claude/memory" (single field,
splits client-side).
Switch to category: {provider: "claude", product: "memory"}
object (most type-strict, largest client diff, breaks existing
consumers).
Collapse semantics. Does collapsing a parent hide all children?
Does each child still remember its own collapse state? What's the
default open/closed for parent vs child on first load?
Group reindex scope. "Reindex group" on the parent = reindex all
children. On a child summary = reindex just that child. Both present
simultaneously, or parent-only when children exist?
Codex migration. If we adopt a vendor hierarchy, is codex moved
to openai/codex in the same change, deferred (flat exception), or
left at its current name to keep the PR minimal?
Break the work into phases so each ships with reviewable scope:
Phase 1 — server-side provider field (additive, non-breaking)
Extend the memory_dir_stats / /api/memory-dirs/status response with a
new provider: str field derived from category:
category
provider
user
user
claude-memory
claude
claude-plans
claude
codex
codex
Clients ignore the field initially. Risk: YAGNI violation if Phase 2 is
never shipped. Justified only if we commit to following through.
Phase 2 — UI tree render + i18n re-key
Switch sources-memory-dirs.js to a two-level render driven by provider → category, with single-leaf providers collapsed to a
single row (no synthetic parent bar for user / codex).
Introduce sources.memory_dirs.provider.{user,claude,codex} keys for
parent labels; keep category.* for leaf labels.
Define the deprecation path for existing consumers of the flat category field (likely: none in-tree; external MCP clients, if any,
continue to work because category is kept).
Phase 3 — Codex rename (separate issue)
File under a new issue if/when we decide to normalize codex → openai/codex. Not required for Phases 1–2.
Non-goals for this RFC
Adding new categories (skills, agents, etc.) — separate issues when
needed.
Context
memory_dircategorization is currently flat: the server-returnedcategoryfield (fromcategorize_memory_dirinconfig.py) is one ofuser,claude-memory,claude-plans,codex. The Web UI's Sources-tabmemory_dirspanel renders these as four peer groups in a fixed order(
sources-memory-dirs.js:_MEMORY_DIR_CATEGORY_ORDER).The flat model doesn't match the mental model it describes.
claude-memoryand
claude-plansaren't peers ofcodex— they're two products of onevendor (Anthropic's Claude tooling). If Claude later exposes a third dir
type (skills? agents? another local surface), the flat bucket list keeps
growing sideways instead of nesting under
claude.The same question is latent for
codex— it is currently a single bucketand a single vendor (OpenAI), but if/when OpenAI adds another local dir
surface, the
codex→openai/codexmigration would be forced on usersby that external decision rather than chosen now.
Resolved 2026-04-20
Q1–Q6 settled in the working-space issue (#314). Full rationale in the
#314 resolution comment.
usertop-level peerprovider: strfielduseropen, vendors closed; no per-child statecodexcategory; tagprovider: "openai"Corrections to the Phase 1 / Phase 2 sketches below (original sketches
preserved for historical record):
codex → codexis nowcodex → openai(Q6).sources.memory_dirs.provider.{user,claude,openai}— key matches the server
providerwire value, notcodex. Category leafcodexkeeps its existingcategory.codexkey._VALID_PROVIDERS = frozenset({"user", "claude", "openai"})paired with thecategory-axis lock from fix(config): lock provider category vocabulary (RFC #304 prep) #313, so adding a new
_CATEGORY_TO_PROVIDERentrywith an unknown provider value trips an import-time assert.
Phase A follow-up audit (see #314 comment) folded in here:
Literal[...]typing sweep oncategorize_memory_dir— deferred.sources-memory-dirs.js:249-259— absorbed by Phase 2.Why not bundle this with #299 / PR #301
PR #301 (closes #299) shipped the server-side
categoryfield deliberatelyscoped to classification drift only — no API shape change, one additive
field. Layering a vendor/product hierarchy on top of that would have broken
the thesis in five ways:
category: strbecomes either a{provider, product}object, a path-style string ("claude/memory"),or a flat string plus a separate
providerfield. All three changeclient parsing.
category.user,.claude_memory,.claude_plans,.codex) become parent/child two-axis keys; en+koparity re-check + graceful deprecation of the existing keys.
_MEMORY_DIR_CATEGORY_{ORDER,LABEL_KEY,COLLAPSED}are flat dicts today. A tree render needs nested
<details>withparent/child collapse state and a re-scoped "Reindex group" button
(operates on parent? on child? both?).
claudehas two children (memory,plans),codexhas none,userhas none. Do lone leaves render under asynthetic parent, or does the tree mix flat-top-level and nested nodes?
codexeventually becomes
openai/codex, forcing a user-facing migration forexisting configs. Keeping
codexflat under a vendor tree breakssymmetry; migrating it bloats the PR.
Each of these is a design decision in its own right. Bundling them under a
drift-fix PR would've violated the scope discipline established by the
#295 → #297 → #300 → #301 sequence (one concern per PR).
Design questions (resolved 2026-04-20 — see block above)
Axis: vendor vs source.Is the parent level "who made this tool"(
claude,openai, ...) or "who supplied this path" (user-providedvs provider-provided)? If "source",
useris a peer ofclaude/openai. If "vendor",userhas no natural vendor and needs eithera synthetic
userbucket or a mixed tree/flat renderer.Where doesTop-level peer of the vendor groups, oruserlive?an escape-hatch bucket common to all vendors? Peer = simpler tree,
smaller blast radius.
Wire format.Three options:category: "claude-memory"string, add siblingprovider: "claude"string (additive, no type change, easy client migration).category: "claude/memory"(single field,splits client-side).
category: {provider: "claude", product: "memory"}object (most type-strict, largest client diff, breaks existing
consumers).
Collapse semantics.Does collapsing a parent hide all children?Does each child still remember its own collapse state? What's the
default open/closed for parent vs child on first load?
Group reindex scope."Reindex group" on the parent = reindex allchildren. On a child summary = reindex just that child. Both present
simultaneously, or parent-only when children exist?
Codex migration.If we adopt a vendor hierarchy, iscodexmovedto
openai/codexin the same change, deferred (flat exception), orleft at its current name to keep the PR minimal?
auto-discovery is driven by per-file-not-per-dir layout and
secrets-in-parent concerns (auto_ns silently collapses to
defaultwhen memory_dir basename is non-discriminating (e.g.,.../FOO/memory) #296,feedback_namespace_ownership.md),not by the hierarchy question. Keep separate.
Proposed sequencing
Break the work into phases so each ships with reviewable scope:
Phase 1 — server-side
providerfield (additive, non-breaking)Extend the
memory_dir_stats//api/memory-dirs/statusresponse with anew
provider: strfield derived fromcategory:useruserclaude-memoryclaudeclaude-plansclaudecodexcodexClients ignore the field initially. Risk: YAGNI violation if Phase 2 is
never shipped. Justified only if we commit to following through.
Phase 2 — UI tree render + i18n re-key
sources-memory-dirs.jsto a two-level render driven byprovider→category, with single-leaf providers collapsed to asingle row (no synthetic parent bar for
user/codex).sources.memory_dirs.provider.{user,claude,codex}keys forparent labels; keep
category.*for leaf labels.categoryfield (likely: none in-tree; external MCP clients, if any,continue to work because
categoryis kept).Phase 3 — Codex rename (separate issue)
File under a new issue if/when we decide to normalize
codex→openai/codex. Not required for Phases 1–2.Non-goals for this RFC
needed.
~/.memtomem/config.jsonmemory_dirsentries.auto_nsbehavior (auto_ns silently collapses todefaultwhen memory_dir basename is non-discriminating (e.g.,.../FOO/memory) #296 covers the basename-collapse bug).Acceptance criteria
providercontract before Phase 2begins (so the field isn't churned once UI consumers exist).
test_i18n.pyparity + placeholder tests continue to pass througheach phase.