How ai-memory move-project Handles Merging vs. True Moves Between Workspaces
The move-project command in ai-memory automatically chooses between a lossless "true-move" strategy when the destination is empty, or a "copy-purge" merge strategy when a project already exists, reporting the chosen path via the moved_via response field.
The move-project operation is the only ai-memory command that relocates an entire project across workspace boundaries. Implemented across crates/ai-memory-wiki/src/wiki.rs and crates/ai-memory-store/src/ops.rs, this operation inspects the destination workspace at runtime and executes one of two mutually exclusive strategies—each with distinct data fidelity guarantees, transactional semantics, and conflict resolution behaviors.
What Triggers Each Strategy
The destination workspace state determines which path executes:
| Destination Condition | Strategy | Response Value |
|---|---|---|
| No project with matching name exists | True-move | "moved_via": "true-move" |
| Project with same name already exists | Copy-purge merge | "moved_via": "copy-purge" |
This branching logic resides in crates/ai-memory-mcp/src/admin.rs, which handles the POST /admin/move-project endpoint and constructs the final JSON response.
True-Move: Atomic Project Relocation
When the destination workspace has no conflicting project, move-project performs a true-move—an O(1) operation that preserves every project artifact without rewriting history.
Execution Flow
-
Source validation: Resolve
(from_workspace, project); return 404 if absent perdocs/lifecycle-ops.md#L231-L236. -
Same-workspace guard: Reject if source and destination match (use
rename-projectinstead) perdocs/lifecycle-ops.md#L31-L33. -
Workspace provisioning: Get-or-create the destination workspace row via
wiki.rs#L284-L300—no new project row is created. -
Admission gate: Acquire the wiki's exclusive mutation lock and fire
op=move_projectwebhooks; any reject-policy aborts before changes peradmission.rs#L96-L104. -
Atomic directory rename: Verify destination absence, then rename
<wiki>/<from_ws>/<proj>→<wiki>/<to_ws>/<proj>atomically perwiki.rs#L40-L45. -
Transaction re-stamp: Update
workspace_idacross all domain tables—projects,pages,sessions,observations,handoffs,audit_log, auto-improvement state—in one SQLite transaction perops.rs#L2919-L2965. Theproject_idremains unchanged, preserving all foreign-key relationships. -
Response: Return
"moved_via":"true-move"with new location permove_project.rs#L1-L30.
Key Guarantees
- Lossless: Embeddings, session history, handoffs, observations, and audit trails travel intact
- Fast: Constant-time filesystem rename plus single database transaction
- Safe: Blocked by live-session guard unless
--forcesupplied
Copy-Purge Merge: Content Consolidation
When ai-memory detects an existing project with the same name at the destination, it switches to copy-purge strategy—merging content while discarding source project identity.
Execution Flow
-
Collision detection: Identify existing destination project per
docs/lifecycle-ops.md#L78-L84. -
Page copy: Invoke
Wiki::write_pagefor each source page, triggering sanitization, link re-resolution, FTS updates, and admission/git-mirror webhooks perwiki.rs#L325-L340. -
Embedding migration: Copy source embeddings verbatim (stored separately from markdown) per
ops.rs#L2920-L2930. -
Source purge: Delete source directory and all source DB rows except copied pages, marking
merged_into_existing:trueandsource_purged:trueperops.rs#L5305-L5315. -
Conflict resolution via
--on-conflict:block(default): Abort with 409, list conflicting pathsoverwrite: Source page replaces destination pageduplicate: Save as<stem>-from-<src_workspace>.mdusingDEDUP_FROM_TOKENconstant perdocs/lifecycle-ops.md#L100-L112
-
Response: Return
"moved_via":"copy-purge"withconflicts,merged_into_existing,source_purgeddetails permove_project.rs#L45-L60.
Critical Limitations
- Sessions lost: Source project sessions are not migrated
- Observations dropped: Handoffs and observations belonging to source are purged
- History truncated: Only latest page bodies copied, not full revision history
- Identity change: Destination retains its original
project_id; sourceproject_idceases to exist
Live-Session Protection Guard
Both strategies implement identical active-project protection. If a running session has pending observations that would receive a stale workspace_id, the move aborts unless --force (CLI) or "force": true (JSON API) is explicitly provided. The guard lifts only the admission-router check—all transactional and conflict-policy protections remain enforced.
Practical Usage Examples
True-Move to Empty Workspace
ai-memory move-project \
--from-workspace default \
--project my-project \
--to-workspace archive \
--confirm
Expected response: "moved_via":"true-move" with complete project preservation.
Merge with Conflict Overwrite
ai-memory move-project \
--from-workspace default \
--project my-project \
--to-workspace archive \
--confirm \
--on-conflict overwrite
Expected response: "moved_via":"copy-purge" with merged_into_existing:true.
Direct API Call for Forced Merge
curl -X POST https://localhost:49374/admin/move-project \
-H "Content-Type: application/json" \
-d '{
"workspace":"default",
"project":"my-project",
"destination_workspace":"archive",
"destination_project":"my-project",
"confirm":true,
"on_conflict":"overwrite"
}'
Summary
- True-move (
"moved_via":"true-move"): Atomic, lossless, O(1) relocation when destination empty—ideal for archiving or reorganizing - Copy-purge (
"moved_via":"copy-purge"): Content-merging consolidation when destination occupied—useful for deduplication, sacrifices source identity and session data - Both paths: Transactional, webhook-respecting, protected by live-session guard with
--forceoverride
Frequently Asked Questions
What happens to my embeddings during a move-project operation?
Embeddings are always preserved. In true-move, they travel with the unchanged project_id via database re-stamping in ops.rs#L2920-L2930. In copy-purge, they are copied verbatim to the destination project before source purge.
Can I recover my source project after a copy-purge merge?
No. The copy-purge strategy permanently deletes the source directory and all associated database rows per ops.rs#L5305-L5315, except for pages successfully copied. The source project_id ceases to exist; only its content survives merged into the destination.
Why does move-project require --confirm?
The --confirm flag acknowledges awareness of destructive potential, particularly for copy-purge merges where source project identity is extinguished. This requirement is enforced at the CLI layer in move_project.rs before forwarding to the MCP admin endpoint, preventing accidental workspace consolidations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →