Skip to content

🤖 feat: consolidate project memory in dream runs#3551

Merged
ThomasK33 merged 10 commits into
mainfrom
memory-consolidation-y5wm
Jun 14, 2026
Merged

🤖 feat: consolidate project memory in dream runs#3551
ThomasK33 merged 10 commits into
mainfrom
memory-consolidation-y5wm

Conversation

@ThomasK33

Copy link
Copy Markdown
Member

Summary

Consolidates host-local project memory in dream runs for single-project workspaces, while keeping project memory unavailable for multi-project workspaces. The Memory tab now shows separate workspace/project/global consolidation coverage, and launch sweeps dedupe shared project/global work without treating pre-upgrade workspace records as project coverage.

Background

Project memory is now host-local/private, so the dream agent can safely consolidate it without creating checkout diffs. The old consolidation pass still disabled project scope entirely and showed one workspace-local status timestamp, which became misleading once project memory is shared by sibling workspaces.

Implementation

  • Enables /memories/project/... mutations only when a stable single-project identity is available; pinned project memories remain protected.
  • Resolves consolidation project identity from actual workspace project refs, so real-bucket multi-project task/fork workspaces do not get project memory accidentally.
  • Extends the consolidation sidecar with explicit project coverage records and structured status (workspaceRecord, projectRecord, globalRecord, projectAvailable).
  • Updates launch sweep qualification/dedupe for workspace, project, and global writes, including upgrade handling for old workspace-only sidecars.
  • Emits consolidation-status change events so no-op dream runs and sibling project/global coverage updates invalidate open Memory tabs.
  • Updates the Memory tab footer, Storybook mocks/stories, debug CLI, dream prompt, generated agent docs/content, and regression tests.

Deep review fixes

Ran deep-review-workflow with auto-fix enabled. It found and fixed:

  • Real-bucket multi-project workspaces incorrectly enabling project memory.
  • Archive/final-pass prompt still steering workspace lessons to global only.
  • Recent legacy workspace records debouncing away first project launch coverage.
  • Manual Memory-tab consolidate fabricating project coverage from a bare record.
  • No-op consolidation sidecar writes failing to notify open Memory tabs.

Validation

  • bun test src/node/services/memoryConsolidation.test.ts src/node/services/memoryConsolidationService.test.ts src/node/orpc/router.memory.test.ts src/browser/features/RightSidebar/Memory/MemoryTab.test.tsx
  • make static-check
  • Storybook dogfood at 360px Memory-tab width, with screenshot/video captured locally.

Risks

Medium. This touches background consolidation orchestration, persisted sidecar upgrade semantics, and Memory-tab status subscriptions. Project memory remains disabled for multi-project workspaces, and old sidecars without projects are normalized rather than inferred as project coverage.


📋 Implementation Plan

Plan: include project memory in dream consolidation

Goal

Update memory consolidation ("dream") so a run from a single-project workspace consolidates all relevant private memory scopes:

  • /memories/workspace/... for the current workspace
  • /memories/project/... for the current project
  • /memories/global/... shared across projects

The recent main refactor made project memory host-local/private (<muxHome>/memory/project/<project dir>/) instead of repo-backed, so consolidation can safely include it without creating checkout diffs. Multi-project workspaces should continue to exclude project memory because they have no single stable project identity.

Current evidence

  • src/common/constants/memory.ts now documents only global, project, and workspace; project is host-local/private and never committed.
  • src/node/services/memoryService.ts resolves project memory from ctx.projectPath to <muxHome>/memory/project/<project dir>/ and disables it when projectPath === "" or the workspace is multi-project.
  • src/node/services/memoryConsolidationService.ts still constructs dream runs with projectPath: "", intentionally disabling project memory.
  • src/node/services/memoryConsolidation.ts still rejects consolidation mutations outside workspace and global.
  • The Memory tab currently fetches one consolidation record per workspaceId. That is not enough once project memory is included, because older workspace records predate project consolidation and sibling workspaces share one project memory store.

Advisor-reviewed recommendation (product code estimate: ~130–190 net LoC)

Implement the narrow explicit-state version:

  1. Consolidation covers workspace + project + global for single-project workspaces.
  2. Multi-project workspaces keep project memory disabled.
  3. The existing sidecar gains an optional projects map so project coverage is tracked explicitly from the first version that actually covers project memory.
  4. The status endpoint returns structured workspace/project/global records so the UI can stop pretending one workspace timestamp answers all scopes.

This is slightly larger than copy-only, but avoids two correctness traps the advisor called out:

  • deriving project coverage from old workspace records would falsely treat pre-change runs as having covered project memory;
  • Workspace B would otherwise show stale/misleading project status after Workspace A covered the shared project memory.

Phase 1 — Resolve project identity for consolidation

  1. Add a small local helper in src/node/services/memoryConsolidationService.ts, e.g. resolveConsolidationProjectPath(foundWorkspace): string.
    • Use Config.findWorkspace(workspaceId) as the source already available to this service.
    • Treat foundWorkspace.projectPath === MULTI_PROJECT_CONFIG_KEY as "".
    • Do not call resolveMemoryProjectIdentity on the findWorkspace return value; that helper expects WorkspaceMetadata, not the config lookup shape.
  2. In runLocked, set ctx.projectPath to the resolved single-project identity or "".
  3. Keep runtime: null and checkoutCwd: ""; project memory is host-local now, so the dream run must continue working with stopped runtimes/checkouts.
  4. Update stale comments that still describe project memory as repo-backed or universally out-of-scope.

Acceptance criteria

  • A manual run for a single-project workspace can resolve the project store without a runtime.
  • A manual run for a multi-project workspace does not bind project memory to the arbitrary first project.
  • Existing workspace/global consolidation behavior is unchanged.

Phase 2 — Expand consolidation mutation rails and dream prompt

  1. In src/node/services/memoryConsolidation.ts, expand the guard whitelist from workspace | global to workspace | project | global only when ctx.projectPath !== "" for project paths.
  2. Keep pin protection unchanged, but update the assertion to accept project scope after the guard has validated it.
  3. Update the guard error copy to explain that project memory is available only for single-project runs.
  4. Update src/node/builtinAgents/dream.md so the model is no longer told “project scope is protected”.
    • Keep the tool rails authoritative in code.
    • On archive/final pass, instruct it to promote repo-specific durable lessons from workspace memory to project memory, and cross-project/user-preference lessons to global memory.
    • Run the formatting/generation step required for built-in agent content (make fmt in this repo regenerates the built-in agent content file).

Acceptance criteria

  • Dream can create, edit, rename, delete, and merge unpinned /memories/project/... files within the existing mutation budget.
  • Pinned project memories remain editable but cannot be deleted or renamed.
  • Multi-project project-memory attempts fail as recoverable tool errors, not stream failures.
  • The prompt and code rails agree about project memory availability.

Phase 3 — Persist explicit project coverage

  1. Extend the private ConsolidationSidecarFileSchema in MemoryConsolidationService from:
    • { workspaces: Record<string, MemoryConsolidationRecord> }
      to:
    • { workspaces: Record<string, MemoryConsolidationRecord>, projects?: Record<string, MemoryConsolidationRecord> }
  2. Normalize projects to {} on load for backward compatibility/self-healing.
  3. After a successful run:
    • always save workspaces[workspaceId] = record as today;
    • when projectPath !== "", also save projects[projectPath] = record in the same atomic read-modify-write cycle.
  4. Keep global coverage derived from the newest successful run anywhere; old records really did cover global, unlike project memory.

Acceptance criteria

  • Existing memory-consolidation.json files without projects continue to load.
  • A successful single-project run writes both workspace and project coverage records.
  • A successful multi-project run writes only the workspace record.
  • A pre-change workspace record does not count as project coverage.

Phase 4 — Update launch sweep selection and dedupe

Project memory is shared by all workspaces in the same project, so automatic launch sweeps should not consolidate the same project-only write once per sibling workspace.

  1. Build a per-workspace project identity map while iterating config workspaces.
  2. For each idle, unarchived candidate, qualify it if any of these have writes since their relevant anchor:
    • workspace-scope write for that workspace since workspaces[workspaceId]?.lastRunAt ?? 0;
    • project-scope write for its single-project identity since projects[projectPath]?.lastRunAt ?? 0;
    • global-scope write since the newest successful run anywhere.
  3. During the sequential sweep, after a successful run:
    • update globalLastRunAt as today;
    • update the in-memory projectLastRunAt/sidecar.projects[projectPath] equivalent for that project so later siblings in the same sweep are skipped for project-only writes.
  4. Keep the existing launch sweep cap, idle threshold, debounce, and archive skip.

Acceptance criteria

  • A project-only write with no project coverage record qualifies one idle workspace.
  • An old workspace record from before project consolidation does not suppress that project-only write.
  • Two idle sibling workspaces with only one shared project write trigger at most one run.
  • Workspace-specific writes still qualify their own workspace independently.
  • Global-only writes retain the current one-covering-run behavior.

Phase 5 — Structured status and UI wording

  1. Add a small status payload schema/type, preferably near the memory schemas, e.g.:
    • workspaceRecord: MemoryConsolidationRecord | null
    • projectRecord: MemoryConsolidationRecord | null
    • globalRecord: MemoryConsolidationRecord | null
    • projectAvailable: boolean
  2. Change memory.consolidationStatus to return that payload instead of a bare nullable record.
  3. Add a service method such as getStatus(workspaceId) so router/UI code does not duplicate sidecar/project/global derivation.
  4. Update src/browser/features/Memory/MemoryBrowser.tsx to render compact scope-aware status in the footer.
    • Replace the current uncommitted copy-only change rather than layering on top of it.
    • Suggested narrow-sidebar shape: stacked text left of the button, e.g. workspace/project/global rows with short labels and relative times.
    • Hide or mark project as unavailable for multi-project workspaces rather than showing a false “never”.
    • Use counter-nums for changing numeric text if the layout shows counts/times prominently.
  5. Update src/browser/stories/mocks/orpc.ts and MemoryTab.stories.tsx so Storybook can render the expanded status shape.

Acceptance criteria

  • Workspace A and Workspace B in the same project show the same project coverage timestamp after either workspace consolidates project memory.
  • Workspace-specific status remains separate.
  • Global status is shown as shared/global coverage or otherwise clearly not workspace-only.
  • The footer remains readable at ~360–375px without right-edge overflow.

Phase 6 — Tests

Add/update behavior-focused tests; avoid tautological string tests.

Backend tests:

  • src/node/services/memoryConsolidation.test.ts
    • Allow project-scope mutations when ctx.projectPath is present.
    • Reject project mutations when ctx.projectPath === "".
    • Reject delete/rename of pinned project files.
  • src/node/services/memoryConsolidationService.test.ts
    • Single-project run saves both workspace and project records.
    • Multi-project run saves no project record and does not use the first listed project.
    • Older sidecars without projects self-heal/default correctly.
    • Old workspace records do not count as project coverage after upgrade.
    • Launch sweep selects one sibling for a project-only write and skips the other after the first successful run.
    • Existing global-only dedupe still passes.
  • Router/schema tests, if existing nearby patterns make this cheap:
    • consolidationStatus returns structured payload and null project record when project is unavailable.

UI/story tests:

  • Avoid exact long-copy assertions.
  • If adding a Storybook play, assert branch behavior (records present/null, button enabled/disabled, footer visible at narrow width) rather than reasserting prose.

Phase 7 — Dogfooding / quality gates

Quality gates between phases:

  1. After scope/guard/prompt changes: run targeted consolidation runner tests.
  2. After sidecar/status/launch sweep changes: run targeted service tests for upgrade/defaulting and project/global/workspace sweep qualification.
  3. After UI changes: run formatting/lint/typecheck and inspect the Memory tab story at narrow/mobile width.

Dogfooding steps for implementation mode:

  1. Start an isolated dev environment with memory and memory-consolidation experiments enabled.
  2. Create/open two workspaces for the same single-project repo.
  3. Seed:
    • a workspace memory in Workspace A,
    • a project memory for the shared project,
    • a global memory,
    • two redundant project memories that dream can merge.
  4. Open the Memory tab in Workspace A, click the consolidate button, and verify project/global/workspace changes are reflected.
  5. Open Workspace B and verify:
    • project memory changes are visible there;
    • project consolidation status matches Workspace A;
    • Workspace B's workspace-specific status remains separate.
  6. Repeat with a multi-project workspace and verify project memory is unavailable rather than misattributed.
  7. Capture reviewer evidence:
    • screenshot before/after of Memory tab in Workspace A,
    • screenshot of Workspace B showing shared project memory/status,
    • screenshot of multi-project behavior,
    • short video recording of the manual consolidate flow.

Validation commands:

bun test src/node/services/memoryConsolidation.test.ts src/node/services/memoryConsolidationService.test.ts
bunx prettier --check src/common/orpc/schemas/memory.ts src/common/orpc/schemas/api.ts src/node/services/memoryConsolidation.ts src/node/services/memoryConsolidationService.ts src/browser/features/Memory/MemoryBrowser.tsx src/browser/stories/mocks/orpc.ts src/browser/features/RightSidebar/Memory/MemoryTab.stories.tsx
bunx eslint src/common/orpc/schemas/memory.ts src/common/orpc/schemas/api.ts src/node/services/memoryConsolidation.ts src/node/services/memoryConsolidationService.ts src/browser/features/Memory/MemoryBrowser.tsx src/browser/stories/mocks/orpc.ts src/browser/features/RightSidebar/Memory/MemoryTab.stories.tsx
make typecheck

Risks / decisions

  • Sidecar upgrade semantics: Explicit projects coverage is required because old workspace records did not cover project memory. Do not infer project coverage from workspace records created before this change.
  • Multi-project identity: Keep project memory unavailable for multi-project workspaces until there is explicit per-project selection.
  • Provider privacy: Project memory is still private host-local storage, but dream sends memory contents to the configured model provider, as normal agent memory does. UI/release copy should not imply local-only processing.
  • Archive final pass: Project memory should be consolidated and can receive repo-specific workspace lessons, but archiving one workspace must not delete or wholesale promote project memory.
  • Schema breakage: IPC breakage is acceptable in this repo because frontend/backend are in sync, but update Storybook ORPC mocks at the same time.

Evidence and advice incorporated

  • Explore agents confirmed the backend touch points, tests, prompt update, and narrow-sidebar UI/dogfooding requirements.
  • Advisor recommended the explicit projects sidecar/status middle path: smaller than a full per-scope history system, but more correct than copy-only or deriving project status from old workspace records.

Generated with mux • Model: openai:gpt-5.5 • Thinking: xhigh • Cost: $38.29

Include project-scope memory in dream consolidation for single-project workspaces, track explicit project coverage records, and surface scope-aware status in the Memory tab.

Validation: bun test src/node/services/memoryConsolidation.test.ts src/node/services/memoryConsolidationService.test.ts && make static-check

---

_Generated with `mux` • Model: `openai:gpt-5.5` • Thinking: `xhigh` • Cost: `$35.69`_

<!-- mux-attribution: model=openai:gpt-5.5 thinking=xhigh costs=35.69 -->
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Launch sweep now uses project coverage as the debounce anchor when a recent legacy workspace-only record would otherwise suppress first project-memory coverage. Workspace debounce skips are filtered before spending launch sweep cap slots.\n\nValidation:\n- bun test src/node/services/memoryConsolidationService.test.ts --test-name-pattern "recent legacy workspace-only|launch cap"\n- bun test src/node/services/memoryConsolidationService.test.ts\n- make typecheck\n\n---\n\n_Generated with `mux` • Model: `openai:gpt-5.5` • Thinking: `xhigh`_\n\n<!-- mux-attribution: model=openai:gpt-5.5 thinking=xhigh -->

Signed-off-by: Thomas Kosiewski <tk@coder.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Emit a dedicated consolidation-status invalidation after coverage sidecar writes, including successful no-op dream runs, and route it through memory subscriptions so open Memory tabs refetch footer status without relying on file mutations.

Validation:
- bun test src/node/services/memoryConsolidationService.test.ts src/node/orpc/router.memory.test.ts
- make typecheck
- bun test src/browser/features/RightSidebar/Memory/MemoryTab.test.tsx
- make fmt-check
- git diff --check
Use the repo-preferred ReadonlyArray type for consolidation project refs.

Validation: bun test src/node/services/memoryConsolidation.test.ts src/node/services/memoryConsolidationService.test.ts src/node/orpc/router.memory.test.ts src/browser/features/RightSidebar/Memory/MemoryTab.test.tsx && make static-check

---

_Generated with `mux` • Model: `openai:gpt-5.5` • Thinking: `xhigh` • Cost: `$35.69`_

<!-- mux-attribution: model=openai:gpt-5.5 thinking=xhigh costs=35.69 -->
@mintlify

mintlify Bot commented Jun 14, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
Mux 🟢 Ready View Preview Jun 14, 2026, 11:15 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3555fc31ae

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/memoryConsolidationService.ts
Treat recent project coverage as the debounce anchor when project memory is the only launch-sweep qualifier, so sibling workspaces do not duplicate provider runs within the debounce window.

Validation: bun test src/node/services/memoryConsolidation.test.ts src/node/services/memoryConsolidationService.test.ts src/node/orpc/router.memory.test.ts src/browser/features/RightSidebar/Memory/MemoryTab.test.tsx && make static-check

---

_Generated with `mux` • Model: `openai:gpt-5.5` • Thinking: `xhigh` • Cost: `$38.29`_

<!-- mux-attribution: model=openai:gpt-5.5 thinking=xhigh costs=38.29 -->
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Please take another look after the project-only launch debounce fix.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: 83946beff6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Config.findWorkspace now exposes stored project refs for consolidation identity checks, so update the exact-shape regression to include them.

Validation: bun test src/node/config.test.ts -t 'preserves the config key' && bun test src/node/services/memoryConsolidation.test.ts src/node/services/memoryConsolidationService.test.ts src/node/orpc/router.memory.test.ts src/browser/features/RightSidebar/Memory/MemoryTab.test.tsx && make static-check

---

_Generated with `mux` • Model: `openai:gpt-5.5` • Thinking: `xhigh` • Cost: `$38.29`_

<!-- mux-attribution: model=openai:gpt-5.5 thinking=xhigh costs=38.29 -->
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Small follow-up for the unit CI failure: updated the Config.findWorkspace exact-shape test to include the newly exposed project refs.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 8fb77df4aa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Mock the experiment hook in MemoryTab tests instead of relying on persisted experiment state, preventing full-suite state contamination from hiding the consolidation footer.

Validation: bun test src/browser/features/RightSidebar/Memory/MemoryTab.test.tsx && bun test src/node/config.test.ts -t 'preserves the config key' && bun test src/node/services/memoryConsolidation.test.ts src/node/services/memoryConsolidationService.test.ts src/node/orpc/router.memory.test.ts && make static-check

---

_Generated with `mux` • Model: `openai:gpt-5.5` • Thinking: `xhigh` • Cost: `$38.29`_

<!-- mux-attribution: model=openai:gpt-5.5 thinking=xhigh costs=38.29 -->
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

Small follow-up for the MemoryTab unit CI failure: stabilized the test by mocking the experiment hook rather than depending on persisted experiment state.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: 8031ba85e2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ThomasK33 ThomasK33 added this pull request to the merge queue Jun 14, 2026
Merged via the queue into main with commit 5ac7532 Jun 14, 2026
24 checks passed
@ThomasK33 ThomasK33 deleted the memory-consolidation-y5wm branch June 14, 2026 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant