-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat(memory): add thread cloning functionality #11517
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Adds the ability to clone conversation threads with all their messages. This creates an independent copy that can diverge from the original. Features: - `cloneThread()` method on Memory class with message filtering options - Clone metadata stored on new thread (sourceThreadId, clonedAt, lastMessageId) - Utility methods: isClone(), getCloneMetadata(), getSourceThread(), listClones(), getCloneHistory() - Embeddings created for cloned messages when semantic recall is enabled - API endpoint: POST /api/memory/threads/:threadId/clone - Clone button in playground UI Memory tab Implemented for all storage adapters: InMemory, PostgreSQL, LibSQL, Upstash 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: e11022a The changes in this PR will be included in the next version bump. This PR includes changesets to release 22 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds thread-cloning across the memory subsystem: new storage types and cloneThread implementations (InMemory, PG, LibSQL, Upstash), core Memory APIs and utilities, server route and schemas, client SDK method and types, Playground UI hook/button, documentation, and extensive unit/integration tests (including a duplicated suite). Changes
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🚨 Redirect Validation FailedThe redirect validation found issues in Action Required: Review and fix the redirect configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (6)
packages/memory/src/index.test.ts (1)
144-472: cloneThread tests thoroughly cover behavior and edge casesThe
cloneThreadsuite exercises default cloning, custom titles, limits, date/ID filters, cross‑resource clones, metadata overrides, and error paths (sourceThreadIdmissing, duplicatenewThreadId), which aligns well with the storage‑level semantics.One small future‑proofing tweak would be to avoid depending on
clonedMessages[0]/[1]ordering in the message‑limit and filter tests by asserting on sortedcreatedAtor sets of texts, in case a backend ever changes its default ordering.packages/core/src/memory/memory.ts (1)
747-752: AbstractcloneThreadAPI is well‑shaped; consider updating the docstringThe new abstract
cloneThread(args: StorageCloneThreadInput): Promise<StorageCloneThreadOutput>cleanly exposes the storage‑level clone contract and matches the shared types.Since
StorageCloneThreadInputsupportsmessageLimitandmessageFilter, you might want to tweak the comment from “Clones a thread with all its messages” to “Clones a thread (optionally with a filtered subset of messages)” for accuracy.packages/memory/integration-tests/src/shared/agent-memory.ts (1)
1287-1686: Thread cloning integration tests look solid; consider hardening timing around embeddingsThis
Thread Cloningblock gives strong end‑to‑end coverage (agent + storage + vector + semantic recall), including custom IDs, history, and searchability of cloned embeddings.The two
setTimeout(500)waits around embedding in the last test could be a source of flakiness under slow I/O. As a later improvement, consider pollingrecalluntil results appear (with a max retry) or wiring in an explicit sync point from the vector adapter instead of fixed sleeps.packages/server/src/server/handlers/memory.ts (1)
41-42: CLONE_THREAD_ROUTE correctly wires the HTTP API tomemory.cloneThreadThe new route:
- Validates
threadIdviavalidateBodyand path schema.- Resolves the correct
MastraMemoryviaagentId.- Delegates to
memory.cloneThreadwith{ sourceThreadId: threadId!, newThreadId, resourceId, title, metadata, options }.- Uses dedicated body/response schemas and funnels failures through
handleError('Error cloning thread').The only minor nit is the description “Creates a copy … with all its messages” while the options allow cloning a subset; you might later clarify that in the summary.
Also applies to: 496-530
packages/core/src/storage/types.ts (1)
141-191: Clone typing is clear; watchclonedAtrepresentation across persisted stores
ThreadCloneMetadata,StorageCloneThreadInput, andStorageCloneThreadOutputnicely document the clone contract and are used consistently across memory backends and APIs.One subtle type/detail mismatch to be aware of: in PG (and likely other SQL stores),
metadatais JSON‑stringified on write andJSON.parsed on read, socloneMetadata.clonedAtwill come back as a string unless you normalize it ingetCloneMetadata. Right now the type advertisesDate, and tests only assertinstanceof Dateon the in‑memory backend.As a follow‑up, consider either:
- Normalizing
clonedAtto aDateingetCloneMetadata, or- Relaxing the type to
Date | stringand documenting that it may need coercion when coming from a DB‑backed store.packages/memory/src/index.ts (1)
1286-1309: Consider potential performance impact for resources with many threads.This method fetches all threads for a resource (
perPage: false) and filters in memory. For resources with a large number of threads, this could be slow. The current approach is acceptable for the initial implementation, but consider adding storage-level filtering (querying bymetadata.clone.sourceThreadId) if this becomes a performance bottleneck.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
.changeset/spotty-grapes-fetch.mdclient-sdks/client-js/src/resources/memory-thread.tsclient-sdks/client-js/src/types.tspackages/core/src/memory/memory.tspackages/core/src/memory/mock.tspackages/core/src/storage/domains/memory/base.tspackages/core/src/storage/domains/memory/inmemory.tspackages/core/src/storage/types.tspackages/memory/integration-tests/src/shared/agent-memory.tspackages/memory/src/index.test.tspackages/memory/src/index.tspackages/playground-ui/src/domains/agents/components/agent-information/agent-memory.tsxpackages/playground-ui/src/domains/memory/hooks/use-memory.tspackages/server/src/server/handlers/memory.tspackages/server/src/server/schemas/memory.tspackages/server/src/server/server-adapter/routes/memory.tsstores/libsql/src/storage/domains/memory/index.tsstores/pg/src/storage/domains/memory/index.tsstores/upstash/src/storage/domains/memory/index.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Run
pnpm typecheckto validate TypeScript types across all packages
Files:
packages/server/src/server/server-adapter/routes/memory.tspackages/playground-ui/src/domains/memory/hooks/use-memory.tspackages/core/src/memory/memory.tspackages/playground-ui/src/domains/agents/components/agent-information/agent-memory.tsxpackages/core/src/memory/mock.tspackages/memory/integration-tests/src/shared/agent-memory.tspackages/core/src/storage/types.tspackages/server/src/server/schemas/memory.tsstores/pg/src/storage/domains/memory/index.tsclient-sdks/client-js/src/resources/memory-thread.tspackages/server/src/server/handlers/memory.tsclient-sdks/client-js/src/types.tsstores/libsql/src/storage/domains/memory/index.tspackages/memory/src/index.test.tspackages/core/src/storage/domains/memory/inmemory.tspackages/memory/src/index.tspackages/core/src/storage/domains/memory/base.tsstores/upstash/src/storage/domains/memory/index.ts
**/*.{ts,tsx,js,jsx,json,md}
📄 CodeRabbit inference engine (CLAUDE.md)
Run
pnpm prettier:formatto format code andpnpm formatto run linting with auto-fix across all packages
Files:
packages/server/src/server/server-adapter/routes/memory.tspackages/playground-ui/src/domains/memory/hooks/use-memory.tspackages/core/src/memory/memory.tspackages/playground-ui/src/domains/agents/components/agent-information/agent-memory.tsxpackages/core/src/memory/mock.tspackages/memory/integration-tests/src/shared/agent-memory.tspackages/core/src/storage/types.tspackages/server/src/server/schemas/memory.tsstores/pg/src/storage/domains/memory/index.tsclient-sdks/client-js/src/resources/memory-thread.tspackages/server/src/server/handlers/memory.tsclient-sdks/client-js/src/types.tsstores/libsql/src/storage/domains/memory/index.tspackages/memory/src/index.test.tspackages/core/src/storage/domains/memory/inmemory.tspackages/memory/src/index.tspackages/core/src/storage/domains/memory/base.tsstores/upstash/src/storage/domains/memory/index.ts
packages/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
packages/**/*.{ts,tsx}: All packages must use TypeScript with strict type checking enabled
Use telemetry decorators for observability across components
Files:
packages/server/src/server/server-adapter/routes/memory.tspackages/playground-ui/src/domains/memory/hooks/use-memory.tspackages/core/src/memory/memory.tspackages/playground-ui/src/domains/agents/components/agent-information/agent-memory.tsxpackages/core/src/memory/mock.tspackages/memory/integration-tests/src/shared/agent-memory.tspackages/core/src/storage/types.tspackages/server/src/server/schemas/memory.tspackages/server/src/server/handlers/memory.tspackages/memory/src/index.test.tspackages/core/src/storage/domains/memory/inmemory.tspackages/memory/src/index.tspackages/core/src/storage/domains/memory/base.ts
.changeset/*.md
⚙️ CodeRabbit configuration file
.changeset/*.md: Changeset files are really important for keeping track of changes in the project. They'll be used to generate release notes and inform users about updates.Review the changeset file according to these guidelines:
- The target audience are developers
- Write short, direct sentences that anyone can understand. Avoid commit messages, technical jargon, and acronyms. Use action-oriented verbs (Added, Fixed, Improved, Deprecated, Removed)
- Avoid generic phrases like "Update code", "Miscellaneous improvements", or "Bug fixes"
- Highlight outcomes! What does change for the end user? Do not focus on internal implementation details
- Add context like links to issues or PRs when relevant
- If the change is a breaking change or is adding a new feature, ensure that a code example is provided. This code example should show the public API usage (the before and after). Do not show code examples of internal implementation details.
- Keep the formatting easy-to-read and scannable. If necessary, use bullet points or multiple paragraphs (Use bold text as the heading for these sections, do not use markdown headings).
- For larger, more substantial changes, also answer the "Why" behind the changes
- Each changeset file contains a YAML frontmatter at the top. It will be one or more package names followed by a colon and the type of change (patch, minor, major). Do not modify this frontmatter. Check that the description inside the changeset file only applies to the packages listed in the frontmatter. Do not allow descriptions that mention changes to packages not listed in the frontmatter. In these cases, the user must create a separate changeset file for those packages.
Files:
.changeset/spotty-grapes-fetch.md
packages/playground-ui/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (packages/playground-ui/CLAUDE.md)
packages/playground-ui/src/**/*.{ts,tsx}: Use Tailwind CSS (v3.x) for all styling in packages/playground-ui components
Use design tokens from src/ds/tokens/index.ts instead of arbitrary Tailwind values (e.g., do not use bg-[#1A1A1A])
Use PascalCase for component names (e.g., EntryList)
Use kebab-case for component filenames (e.g., entry-list.tsx)
Use named exports only; avoid default exports in components
Use TanStack Query for all data fetching hooks in packages/playground-ui
Use useMastraClient SDK for API calls instead of direct fetch() calls
Export explicit prop types separately from components (e.g., export type ComponentProps = {...})
Prefer derived values over useState + useEffect; calculate values directly when possible
Keep type definitions alongside components within the same file
Minimize useEffect usage in components; only use it when necessary
packages/playground-ui/src/**/*.{ts,tsx}: Use Tailwind CSS v3.x for all styling inpackages/playground-ui
Use design tokens fromsrc/ds/tokens/index.tsfor Tailwind styling inpackages/playground-ui; forbidden to use arbitrary values (e.g.,bg-[#1A1A1A]) unless explicitly requested
Use PascalCase for component names inpackages/playground-ui
Use kebab-case for component file names inpackages/playground-ui
Use named exports only; avoid default exports inpackages/playground-ui
Use TanStack Query for all data-fetching hooks inpackages/playground-ui; forbidden to use directfetch()calls
UseuseMastraClientSDK for API calls in data-fetching hooks inpackages/playground-ui
Export explicit prop types separately from components inpackages/playground-ui; keep type definitions alongside components
Prefer derived values overuseState+useEffectinpackages/playground-ui; minimizeuseEffectusage and calculate values directly when possible
Use TanStack Query for all server state management inpackages/playground-ui
Files:
packages/playground-ui/src/domains/memory/hooks/use-memory.tspackages/playground-ui/src/domains/agents/components/agent-information/agent-memory.tsx
packages/playground-ui/src/**/*.{tsx,css,scss}
📄 CodeRabbit inference engine (packages/playground-ui/CLAUDE.md)
Prefer Tailwind utilities over custom CSS files for shadows, gradients, and other styles
Files:
packages/playground-ui/src/domains/agents/components/agent-information/agent-memory.tsx
packages/playground-ui/src/**/*.{tsx,css}
📄 CodeRabbit inference engine (packages/playground-ui/.cursor/rules/frontend.mdc)
Prefer Tailwind utilities over custom CSS files for shadows, gradients, and other styling; only use CSS files when Tailwind cannot express the style
Files:
packages/playground-ui/src/domains/agents/components/agent-information/agent-memory.tsx
**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{test,spec}.{ts,tsx}: Use Vitest for testing framework in test files
Co-locate test files with source code using naming patterns like *.test.ts or *.spec.ts
Files:
packages/memory/src/index.test.ts
🧠 Learnings (4)
📚 Learning: 2025-11-24T16:41:10.784Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/playground-ui/.cursor/rules/frontend.mdc:0-0
Timestamp: 2025-11-24T16:41:10.784Z
Learning: Applies to packages/playground-ui/src/**/*.{ts,tsx} : Use `useMastraClient` SDK for API calls in data-fetching hooks in `packages/playground-ui`
Applied to files:
packages/playground-ui/src/domains/memory/hooks/use-memory.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/__fixtures__/**/*.ts : Create test fixtures by copying examples DIRECTLY from migration guides in `docs/src/content/en/guides/migrations/upgrade-to-v1/` without hallucinating or inventing changes
Applied to files:
packages/core/src/memory/mock.tspackages/memory/integration-tests/src/shared/agent-memory.tspackages/memory/src/index.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/__fixtures__/**/*.ts : In output fixtures, ensure all NEGATIVE test cases remain EXACTLY IDENTICAL to their input fixture counterparts to verify the codemod only transforms intended patterns
Applied to files:
packages/memory/integration-tests/src/shared/agent-memory.tspackages/memory/src/index.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/**/*.test.ts : Include test cases for multiple occurrences of the transformation pattern, aliased imports, type imports, and mixed imports to verify the codemod works consistently
Applied to files:
packages/memory/integration-tests/src/shared/agent-memory.tspackages/memory/src/index.test.ts
🧬 Code graph analysis (15)
packages/server/src/server/server-adapter/routes/memory.ts (1)
packages/server/src/server/handlers/memory.ts (1)
CLONE_THREAD_ROUTE(496-530)
packages/playground-ui/src/domains/memory/hooks/use-memory.ts (2)
packages/core/src/agent/agent.ts (1)
thread(2788-2959)packages/playground-ui/src/lib/toast.tsx (1)
toast(51-60)
packages/core/src/memory/memory.ts (1)
packages/core/src/storage/types.ts (2)
StorageCloneThreadInput(156-181)StorageCloneThreadOutput(186-191)
packages/playground-ui/src/domains/agents/components/agent-information/agent-memory.tsx (2)
packages/core/src/storage/domains/memory/inmemory.ts (1)
cloneThread(549-663)packages/playground-ui/src/domains/memory/hooks/use-memory.ts (1)
useCloneThread(105-126)
packages/core/src/memory/mock.ts (2)
packages/core/src/storage/types.ts (2)
StorageCloneThreadInput(156-181)StorageCloneThreadOutput(186-191)packages/memory/src/index.ts (2)
StorageCloneThreadInput(1347-1347)StorageCloneThreadOutput(1347-1347)
packages/memory/integration-tests/src/shared/agent-memory.ts (1)
packages/memory/src/index.ts (1)
Memory(55-1341)
stores/pg/src/storage/domains/memory/index.ts (5)
packages/core/src/storage/types.ts (3)
StorageCloneThreadInput(156-181)StorageCloneThreadOutput(186-191)ThreadCloneMetadata(144-151)packages/core/src/error/index.ts (1)
MastraError(142-142)packages/core/src/storage/utils.ts (1)
createStorageErrorId(208-210)packages/core/src/memory/types.ts (2)
StorageThreadType(36-43)MastraDBMessage(5-5)packages/core/src/agent/types.ts (1)
MastraDBMessage(42-42)
client-sdks/client-js/src/resources/memory-thread.ts (2)
client-sdks/client-js/src/types.ts (2)
CloneMemoryThreadParams(309-323)CloneMemoryThreadResponse(325-328)client-sdks/client-js/src/utils/index.ts (1)
requestContextQueryString(110-120)
packages/server/src/server/handlers/memory.ts (2)
packages/server/src/server/schemas/memory.ts (4)
threadIdPathParams(5-7)agentIdQuerySchema(12-14)cloneThreadBodySchema(374-391)cloneThreadResponseSchema(396-399)packages/server/src/server/handlers/utils.ts (1)
validateBody(4-15)
client-sdks/client-js/src/types.ts (2)
packages/core/src/memory/types.ts (1)
StorageThreadType(36-43)packages/core/src/agent/types.ts (1)
MastraDBMessage(42-42)
stores/libsql/src/storage/domains/memory/index.ts (2)
packages/core/src/storage/types.ts (3)
StorageCloneThreadInput(156-181)StorageCloneThreadOutput(186-191)ThreadCloneMetadata(144-151)packages/core/src/error/index.ts (1)
MastraError(142-142)
packages/memory/src/index.test.ts (2)
packages/memory/src/index.ts (1)
Memory(55-1341)packages/core/src/storage/mock.ts (1)
InMemoryStore(28-64)
packages/core/src/storage/domains/memory/inmemory.ts (3)
packages/core/src/storage/types.ts (4)
StorageCloneThreadInput(156-181)StorageCloneThreadOutput(186-191)StorageMessageType(201-209)ThreadCloneMetadata(144-151)packages/core/src/memory/types.ts (1)
StorageThreadType(36-43)packages/core/src/storage/utils.ts (1)
safelyParseJSON(32-46)
packages/core/src/storage/domains/memory/base.ts (1)
packages/core/src/storage/types.ts (2)
StorageCloneThreadInput(156-181)StorageCloneThreadOutput(186-191)
stores/upstash/src/storage/domains/memory/index.ts (4)
packages/core/src/storage/types.ts (3)
StorageCloneThreadInput(156-181)StorageCloneThreadOutput(186-191)ThreadCloneMetadata(144-151)packages/core/src/storage/utils.ts (2)
createStorageErrorId(208-210)filterByDateRange(298-320)packages/core/src/workflows/workflow.ts (1)
options(1028-1030)packages/core/src/memory/types.ts (1)
StorageThreadType(36-43)
🪛 GitHub Check: Lint
packages/memory/src/index.test.ts
[failure] 2-2:
'StorageThreadType' is defined but never used. Allowed unused vars must match /^_/u
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: Cursor Bugbot
- GitHub Check: Prebuild
- GitHub Check: test
- GitHub Check: test (hono)
- GitHub Check: test
- GitHub Check: test (express)
- GitHub Check: test
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (20)
packages/core/src/storage/domains/memory/base.ts (1)
65-77: LGTM!The abstract
cloneThreadmethod follows the established pattern for storage adapters with clear documentation and an informative not-implemented error message.packages/server/src/server/server-adapter/routes/memory.ts (1)
12-12: LGTM!The CLONE_THREAD_ROUTE is correctly imported and registered following the established pattern for memory routes.
Also applies to: 39-39
packages/playground-ui/src/domains/agents/components/agent-information/agent-memory.tsx (2)
36-48: LGTM!The clone thread handler properly awaits the mutation, checks the result before navigation, and relies on the mutation's error handling. The implementation is clean and follows React best practices.
76-90: LGTM!The Clone Thread UI section is well-implemented with:
- Clear labels and description for user understanding
- Appropriate loading state ("Cloning..." vs "Clone")
- Disabled button during operation to prevent duplicate requests
- Semantically appropriate Copy icon
.changeset/spotty-grapes-fetch.md (2)
1-38: Changeset follows guidelines well.The changeset is well-written with:
- Clear, action-oriented language ("Adds thread cloning...")
- Practical code example showing public API usage
- Outcome-focused description (independent copies that can diverge)
- Appropriate bullet points for implementation details
As per coding guidelines, the content is developer-friendly and scannable.
14-31: All documented APIs are properly implemented—no action needed.Verification confirms that
isClone(),getSourceThread(), andlistClones()are all implemented as public methods inpackages/memory/src/index.ts(lines 1244, 1262, and 1286 respectively). All three methods have proper documentation and comprehensive test coverage. The code examples in the changeset accurately reflect the actual API implementation.packages/core/src/memory/mock.ts (1)
309-312: LGTM!The
cloneThreadimplementation correctly delegates to the underlying memory storage, following the same pattern as other methods inMockMemory.client-sdks/client-js/src/resources/memory-thread.ts (1)
114-128: LGTM!The
clonemethod is well-implemented with:
- Clear JSDoc documentation
- Correct type usage for parameters and response
- Consistent pattern matching other methods in the class (e.g.,
update,delete)- Proper handling of
requestContextvia query stringpackages/server/src/server/schemas/memory.ts (1)
371-399: LGTM!The clone thread schemas are well-structured:
cloneThreadBodySchemaproperly defines all optional parameters with correct types- Date coercion (
z.coerce.date()) ensures proper parsing from query stringscloneThreadResponseSchemacorrectly uses existingthreadSchemaandmessageSchema- JSDoc comments clearly document the endpoint path and purpose
The schemas align with the corresponding TypeScript types (
StorageCloneThreadInput,StorageCloneThreadOutput) used throughout the codebase.packages/playground-ui/src/domains/memory/hooks/use-memory.ts (1)
105-126: Query key pattern is consistent—no changes needed.The duplication of
agentIdin the query key['memory', 'threads', agentId, agentId]is intentional and correct. In the playground context,resourceIdis always set toagentId(as seen in agent-memory.tsx line 32), so the query key created byuseThreadsis['memory', 'threads', agentId, agentId]. The mutation hooks correctly invalidate this same key.packages/memory/src/index.test.ts (1)
474-702: clone utility tests give good coverage of metadata and lineage helpersThe
clone utility methodssuite validatesisClone,getCloneMetadata,getSourceThread,listClones, andgetCloneHistoryacross positive, negative, and non‑existent thread cases, which is exactly what these helpers need. This should catch regressions in clone metadata shape or traversal logic early.packages/core/src/storage/domains/memory/inmemory.ts (1)
549-663: InMemorycloneThreadcorrectly implements the clone semanticsThe in‑memory implementation matches
StorageCloneThreadInput/Outputsemantics:
- Validates source existence and prevents
newThreadIdcollisions.- Applies
messageFilter(IDs + date range) first, thenmessageLimitfrom the most recent messages viaslice(-limit).- Builds
ThreadCloneMetadatawithsourceThreadId,clonedAt, andlastMessageIdafter filtering/limiting.- Creates a new thread with appropriate
resourceId, default title (Clone of …), andmetadatamerged withclone.- Clones messages with new IDs while preserving
createdAt, role, and content, and returns parsedMastraDBMessages.This aligns with the tests and PG implementation, and I don’t see correctness issues here.
client-sdks/client-js/src/types.ts (1)
309-328: CloneMemoryThread types align with core and server contracts
CloneMemoryThreadParamsmirrors the server’scloneThreadBodySchema(newThreadId/resourceId/title/metadata/options) plusrequestContext, andCloneMemoryThreadResponsematchesStorageCloneThreadOutput. Given the threadId comes from the URL in the client resource, this shape looks appropriate.stores/pg/src/storage/domains/memory/index.ts (1)
16-25: MemoryPGcloneThreadis transactional and mirrors in‑memory behaviorThe PG implementation:
- Validates that the source thread exists and that the target
newThreadId(or generated UUID) does not.- Builds a parameterized message query with optional date and ID filters plus an optional “most recent N”
messageLimitby wrapping a DESC‑ordered subquery, then re‑sorting ASC.- Computes
ThreadCloneMetadataafter filtering/limiting to get an accuratelastMessageId.- Inserts the new thread row with JSON‑encoded metadata (including
clone) and propercreatedAt/updatedAt+*Zcolumns.- Clones messages inside the same transaction with new IDs, preserving content, timestamps, role/type, and using
resourceIdoverride or the source thread’sresourceId.- Returns fully parsed
MastraDBMessages for the cloned messages.Error handling via
MastraError(including specificSOURCE_NOT_FOUNDandTHREAD_EXISTScodes, plus a genericFAILEDwrapper) is consistent with the rest of the PG domain. I don’t see correctness gaps here.Also applies to: 1116-1293
stores/libsql/src/storage/domains/memory/index.ts (1)
906-1087: LGTM! Well-structuredcloneThreadimplementation.The implementation correctly:
- Validates source thread existence and new thread ID uniqueness
- Uses parameterized queries for safe SQL construction
- Applies filters (date range, messageIds) and limit correctly with the DESC/LIMIT/ASC pattern
- Uses a transaction to ensure atomicity of thread and message creation
- Properly re-throws
MastraErrorinstances while wrapping other errorsstores/upstash/src/storage/domains/memory/index.ts (1)
1127-1285: LGTM! Consistent implementation with appropriate Redis patterns.The implementation:
- Follows the same validation and error handling patterns as LibSQL
- Uses
filterByDateRangeutility for consistent filtering behavior- Properly maintains message ordering via sorted set scores
- Uses Redis pipeline for efficient bulk writes
- Correctly maintains the message ID → threadId index for fast lookups
packages/memory/src/index.ts (4)
1115-1129: LGTM! Clean delegation with semantic recall integration.The
cloneThreadmethod correctly:
- Delegates to the storage layer for the actual cloning
- Conditionally embeds cloned messages when semantic recall is enabled
- Returns the complete result with thread and messages
1135-1206: Embedding logic is correct and handles edge cases appropriately.The optional chaining (
message.content?.content) is a good defensive choice here since cloned messages may have content parsed from various storage backends with potentially different structures.
1323-1340: N+1 query pattern is acceptable for typical clone depth.The method makes one query per ancestor in the clone chain. While this is an N+1 pattern, clone chains are typically shallow (1-3 levels deep), so the impact is minimal. Consider adding a depth limit if deep cloning becomes a supported use case to prevent potential runaway queries.
1345-1347: Good practice to re-export clone-related types.Re-exporting
StorageCloneThreadInput,StorageCloneThreadOutput, andThreadCloneMetadatafrom@mastra/core/storageprovides a convenient single import path for consumers of the memory package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is being reviewed by Cursor Bugbot
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| return null; | ||
| } | ||
| return thread.metadata.clone as ThreadCloneMetadata; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clone metadata Date not restored after JSON deserialization
The ThreadCloneMetadata type declares clonedAt: Date, but when clone metadata is stored in persistent databases (LibSQL, PG, Upstash), it goes through JSON.stringify which converts the Date to an ISO string. When the thread is read back via JSON.parse, clonedAt remains a string. The getCloneMetadata method casts the raw metadata to ThreadCloneMetadata, making TypeScript believe clonedAt is a Date when it's actually a string. This type mismatch will cause runtime errors if code attempts to call Date methods on clonedAt. The unit test passes only because it uses InMemoryStore which preserves Date objects without serialization.
Additional Locations (1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
packages/memory/src/index.test.ts (2)
204-205: Consider using proper typing instead ofas any.The type assertion
as anybypasses TypeScript's type checking. Consider using thegetCloneMetadata()utility method (which you're already testing later) or defining a proper type for clone metadata.🔎 Alternative approach using getCloneMetadata
- expect(clonedThread.metadata?.clone).toBeDefined(); - expect((clonedThread.metadata?.clone as any).sourceThreadId).toBe(sourceThread.id); + const cloneMetadata = memory.getCloneMetadata(clonedThread); + expect(cloneMetadata).toBeDefined(); + expect(cloneMetadata?.sourceThreadId).toBe(sourceThread.id);
153-471: Comprehensive test coverage for cloneThread functionality.The test suite thoroughly covers:
- Basic cloning with message preservation
- Custom titles and thread IDs
- Message filtering (limits, date ranges, specific IDs)
- Error handling (non-existent source, duplicate IDs)
- Cross-resource cloning and metadata preservation
Optional: Consider adding edge case tests
Additional test cases that could further strengthen coverage:
- Cloning an empty thread (no messages)
- Combining
messageLimitwithmessageFilter(e.g., limit + date range)- Invalid filter parameters (e.g.,
startDate > endDate)- Cloning with very large message counts
These are nice-to-have improvements rather than critical gaps.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/memory/src/index.test.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Run
pnpm typecheckto validate TypeScript types across all packages
Files:
packages/memory/src/index.test.ts
**/*.{ts,tsx,js,jsx,json,md}
📄 CodeRabbit inference engine (CLAUDE.md)
Run
pnpm prettier:formatto format code andpnpm formatto run linting with auto-fix across all packages
Files:
packages/memory/src/index.test.ts
packages/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
packages/**/*.{ts,tsx}: All packages must use TypeScript with strict type checking enabled
Use telemetry decorators for observability across components
Files:
packages/memory/src/index.test.ts
**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{test,spec}.{ts,tsx}: Use Vitest for testing framework in test files
Co-locate test files with source code using naming patterns like *.test.ts or *.spec.ts
Files:
packages/memory/src/index.test.ts
🧠 Learnings (4)
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/__fixtures__/**/*.ts : Create test fixtures by copying examples DIRECTLY from migration guides in `docs/src/content/en/guides/migrations/upgrade-to-v1/` without hallucinating or inventing changes
Applied to files:
packages/memory/src/index.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/**/*.test.ts : Include test cases for multiple occurrences of the transformation pattern, aliased imports, type imports, and mixed imports to verify the codemod works consistently
Applied to files:
packages/memory/src/index.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/test/__fixtures__/**/*.ts : In output fixtures, ensure all NEGATIVE test cases remain EXACTLY IDENTICAL to their input fixture counterparts to verify the codemod only transforms intended patterns
Applied to files:
packages/memory/src/index.test.ts
📚 Learning: 2025-11-24T16:42:04.244Z
Learnt from: CR
Repo: mastra-ai/mastra PR: 0
File: packages/codemod/AGENTS.md:0-0
Timestamp: 2025-11-24T16:42:04.244Z
Learning: Applies to packages/codemod/src/codemods/v1/**/*.ts : When transforming TypeScript type imports and usages, check the parent node type to skip transforming identifiers that are part of import declarations to avoid transforming imports from other packages with the same type name
Applied to files:
packages/memory/src/index.test.ts
🧬 Code graph analysis (1)
packages/memory/src/index.test.ts (2)
packages/memory/src/index.ts (1)
Memory(55-1341)packages/core/src/storage/mock.ts (1)
InMemoryStore(28-64)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: Cursor Bugbot
- GitHub Check: Prebuild
- GitHub Check: test
- GitHub Check: Lint
- GitHub Check: test
- GitHub Check: test (hono)
- GitHub Check: test (express)
- GitHub Check: test
- GitHub Check: test
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
packages/memory/src/index.test.ts (2)
143-151: Excellent test isolation setup.The
beforeEachhooks correctly create freshMemoryinstances withInMemoryStorefor each test, ensuring no state leakage between tests. This is the right pattern for testing stateful components.Also applies to: 473-481
473-701: Excellent coverage of clone utility methods.The utility method tests are thorough and well-structured:
isClone: Tests positive, negative, and null casesgetCloneMetadata: Validates metadata retrieval with proper null handlinggetSourceThread: Tests source retrieval for both clones and regular threadslistClones: Tests multiple clones and empty resultsgetCloneHistory: Tests clone chains (original → clone1 → clone2), which is particularly valuable for ensuring transitive relationships work correctlyThe tests demonstrate careful attention to edge cases and defensive programming.
| } | ||
|
|
||
| return history; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing cycle detection causes potential infinite loop
The getCloneHistory method traverses the clone chain by following sourceThreadId references in a while loop without any cycle detection. If clone metadata is manually manipulated to create a circular reference (e.g., thread A points to thread B as its source, and thread B points to thread A), the method would loop infinitely. Adding a Set to track visited thread IDs would prevent this edge case from causing a hang or denial of service.
| clonedAt: Date; | ||
| /** ID of the last message included in the clone (if messages were copied) */ | ||
| lastMessageId?: string; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clone metadata Date field becomes string after database retrieval
The ThreadCloneMetadata type declares clonedAt as a Date, but when threads are retrieved from SQL databases (LibSQL, PostgreSQL, Upstash), the metadata is JSON-parsed without converting ISO date strings back to Date objects. This means getCloneMetadata() returns an object where clonedAt is actually a string at runtime, despite the type saying Date. Code relying on Date methods like .getTime() would fail. The unit test checking toBeInstanceOf(Date) only passes because it uses InMemoryStore which preserves Date objects.
Additional Locations (1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
docs/src/content/en/reference/server/routes.mdx (1)
156-196: Inconsistent date type representation compared to rest of document.Lines 167-168 use
Datetype forstartDateandendDate, but elsewhere in this document (lines 291-292), dates are documented asstringwith "ISO date string" comment. In JSON APIs, dates are transmitted as ISO 8601 strings, not JavaScriptDateobjects.Consider using
stringwith an "ISO date string" comment for consistency, or note that the client SDK handles the conversion.🔎 Suggested fix for request body
options?: { messageLimit?: number; // Max messages to clone messageFilter?: { - startDate?: Date; // Clone messages after this date - endDate?: Date; // Clone messages before this date + startDate?: string; // ISO date string - clone messages after this date + endDate?: string; // ISO date string - clone messages before this date messageIds?: string[]; // Clone specific messages }; };
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
docs/src/content/en/reference/client-js/memory.mdxdocs/src/content/en/reference/memory/clone-utilities.mdxdocs/src/content/en/reference/memory/cloneThread.mdxdocs/src/content/en/reference/memory/memory-class.mdxdocs/src/content/en/reference/server/routes.mdx
✅ Files skipped from review due to trivial changes (3)
- docs/src/content/en/reference/memory/cloneThread.mdx
- docs/src/content/en/reference/client-js/memory.mdx
- docs/src/content/en/reference/memory/memory-class.mdx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{md,mdx}
📄 CodeRabbit inference engine (.cursor/rules/writing-documentation.mdc)
**/*.{md,mdx}: When writing developer documentation, do not use adjectives like 'powerful' or 'built-in' as these read like marketing copy and developers don't like that
When writing developer documentation, do not use 'complete', 'out-of-the-box', 'hands-on', or overly enthusiastic exhortations like 'Check out', 'Learn more', 'Explore'. Do not use words like 'essential' or 'offers'
When writing developer documentation, do not use 'your needs', 'production-ready', 'makes it easy', or 'choose the right...solution' as these are marketing jargon that developers dislike
When writing developer documentation, avoid phrasing like 'without changing your code' or 'automatically handles' that obscures implementation details
In developer documentation, avoid phrasing that glides between benefits without diving into details. Focus on technical specifics and implementation details rather than high-level benefits. For example, avoid sentences like: 'This makes it easy to build AI applications that maintain meaningful conversations and remember important details, whether you're building a simple chatbot or a sophisticated AI assistant'
All H1 headings (# Heading) must use title case format, capitalizing the first letter of each major word. Examples: 'Getting Started', 'Human In-the-Loop Workflow', 'Agent as a Step'
Files:
docs/src/content/en/reference/memory/clone-utilities.mdxdocs/src/content/en/reference/server/routes.mdx
**/*{docs,documentation}/**/*.{md,mdx}
📄 CodeRabbit inference engine (.windsurfrules)
**/*{docs,documentation}/**/*.{md,mdx}: When writing developer documentation, do not use adjectives like 'powerful' or 'built-in' as they read like marketing copy
When writing developer documentation, do not use words like 'complete', 'out-of-the-box', 'hands-on', or overly enthusiastic exhortations such as 'Check out', 'Learn more', 'Explore', 'essential', or 'offers'
When writing developer documentation, avoid marketing jargon such as 'your needs', 'production-ready', 'makes it easy', or 'choose the right...solution'
When writing developer documentation, do not use phrases like 'without changing your code' or 'automatically handles' as they glide over implementation details
In developer documentation, avoid phrasing that glides between benefits without diving into details; instead, focus on technical nuts and bolts with specific implementation details rather than abstract benefits
Files:
docs/src/content/en/reference/memory/clone-utilities.mdxdocs/src/content/en/reference/server/routes.mdx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: Validate build outputs
- GitHub Check: Cursor Bugbot
- GitHub Check: test
- GitHub Check: test (express)
- GitHub Check: test
- GitHub Check: test (hono)
- GitHub Check: test
- GitHub Check: test
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
docs/src/content/en/reference/memory/clone-utilities.mdx (1)
1-345: Documentation looks good overall.The documentation follows coding guidelines—no marketing language detected, H1 heading uses title case, and code examples are clear and technically accurate. The API surface documented here aligns with the clone utilities described in the PR.
docs/src/content/en/reference/server/routes.mdx (1)
142-142: Route table entry looks correct.The new clone endpoint is properly documented in the Memory routes table, consistent with the route structure used elsewhere.
| metadata: { | ||
| ...metadata, | ||
| clone: cloneMetadata, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Source thread metadata not merged during clone operation
The documentation states that the metadata parameter will be "merged with the source thread's metadata", but the implementation only spreads the user-provided metadata without including sourceThread.metadata. This means any existing metadata on the source thread is lost during cloning unless the caller explicitly re-passes it. The clone metadata construction uses { ...metadata, clone: cloneMetadata } but is missing ...sourceThread.metadata as the first spread to preserve original metadata.
Additional Locations (2)
| if (result?.thread?.id) { | ||
| navigate(paths.agentThreadLink(agentId, result.thread.id)); | ||
| } | ||
| }, [threadId, agentId, cloneThread, navigate, paths]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unhandled promise rejection in clone thread handler
The handleCloneThread callback awaits cloneThread() (mutateAsync) without a try-catch block. While the mutation's onError callback shows a toast, mutateAsync still rejects the promise when an error occurs. This can result in unhandled promise rejection warnings in the browser console. The async function needs error handling to prevent the rejection from propagating.
Adds thread cloning to create independent copies of conversations that can diverge. <img width="1450" height="785" alt="image" src="https://github.com/user-attachments/assets/c24435f8-5410-4e37-93eb-03a9940de78c" /> ```typescript // Clone a thread const { thread, clonedMessages } = await memory.cloneThread({ sourceThreadId: 'thread-123', title: 'My Clone', options: { messageLimit: 10, // optional: only copy last N messages }, }); // Check if a thread is a clone if (memory.isClone(thread)) { const source = await memory.getSourceThread(thread.id); } // List all clones of a thread const clones = await memory.listClones('thread-123'); ``` Includes: - Storage implementations for InMemory, PostgreSQL, LibSQL, Upstash - API endpoint: `POST /api/memory/threads/:threadId/clone` - Embeddings created for cloned messages (semantic recall) - Clone button in playground UI Memory tab - Unit and integration tests <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adds end-to-end support for cloning conversation threads, enabling independent branches of a conversation. > > - Core: `Memory.cloneThread()` plus utilities (`isClone`, `getCloneMetadata`, `getSourceThread`, `listClones`, `getCloneHistory`); embeddings generated for cloned messages when semantic recall is enabled > - Storage: implementations for InMemory, PostgreSQL, LibSQL, and Upstash; new types (`StorageCloneThreadInput/Output`, `ThreadCloneMetadata`) > - Server: new endpoint `POST /api/memory/threads/:threadId/clone` with schemas and route wiring > - Client SDK: `thread.clone(params)` and related types (`CloneMemoryThreadParams/Response`) > - UI: Clone button in playground Memory tab with navigation to the new thread > - Docs: references for client SDK, `cloneThread`, clone utilities, and server routes > - Tests: unit and integration coverage for cloning behavior, filters, limits, metadata, and embeddings > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e11022a. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Clone memory threads into independent copies with optional message filtering/limits, custom IDs/titles, and embeddings for cloned messages * Clone utilities: detect clones, retrieve source thread, list clones, and view clone history * API endpoint and a "Clone Thread" button in the playground Memory tab * **Tests** * Extensive integration and unit tests covering cloning scenarios, filters, limits, metadata, and embeddings * **Documentation** * Added changelog and docs for cloneThread and clone utilities <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Adds thread cloning to create independent copies of conversations that can diverge.
Includes:
POST /api/memory/threads/:threadId/cloneNote
Adds end-to-end support for cloning conversation threads, enabling independent branches of a conversation.
Memory.cloneThread()plus utilities (isClone,getCloneMetadata,getSourceThread,listClones,getCloneHistory); embeddings generated for cloned messages when semantic recall is enabledStorageCloneThreadInput/Output,ThreadCloneMetadata)POST /api/memory/threads/:threadId/clonewith schemas and route wiringthread.clone(params)and related types (CloneMemoryThreadParams/Response)cloneThread, clone utilities, and server routesWritten by Cursor Bugbot for commit e11022a. This will update automatically on new commits. Configure here.
Summary by CodeRabbit
New Features
Tests
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.