Mastra working memory with tool calls: fixing 'use: tool-call' and messages missing before tool calls
Mastra issue #4221 reported that working memory in 'tool-call' mode dropped the text the agent wrote before a tool call. Here's what changed since, why the old config now throws, the current working memory setup, and how to check what was actually saved.
Disclosure: I'm Mohammed, founder of MemorySync. I drafted this article with AI assistance and reviewed it for accuracy against the official Mastra and MemorySync docs. The install, type checks and a full agent run with a mock model were run (no real LLM call); see the notes at the end of each step.
If you searched for "Mastra working memory tool-call messages not saved", you probably landed on mastra-ai/mastra#4221: with workingMemory: { enabled: true, use: 'tool-call' }, the text an agent wrote before calling a tool never reached the database. Turning working memory off made the messages come back.
That issue was filed against @mastra/memory 0.3.x and @mastra/core 0.9.x. A maintainer merged a fix in May 2025 and closed it. Today the bigger change is that the use option is gone altogether. Below is what that means for an old config, the current setup, and how to confirm nothing goes missing. Mastra behaviour comes from the Mastra working memory docs and the package type definitions. I'm building MemorySync, a hosted memory layer, and it gets one short section at the end (if you want it with Mastra, see the MemorySync Mastra guide).
Step 1: Upgrade, then drop the use option
Install current versions (Mastra's packages need Node.js 22.13 or later):
npm install @mastra/core @mastra/memory @mastra/libsql
If your config still looks like the one in the issue, it won't load any more. In @mastra/core 1.79.0 the type of use is never, with the note "The use option has been removed. Working memory always uses tool-call mode." TypeScript flags it, and plain JavaScript fails when Memory is constructed:
Error: The workingMemory.use option has been removed. Working memory always uses tool-call mode.
So the fix is to delete the line. Tool-call mode isn't something you opt into now; it's the only mode.
Step 2: The current working memory config
This is the docs' working memory setup with a custom template and libSQL storage:
import { Agent } from '@mastra/core/agent'
import { Memory } from '@mastra/memory'
import { LibSQLStore } from '@mastra/libsql'
const memory = new Memory({
storage: new LibSQLStore({ id: 'mastra-storage', url: 'file:./mastra.db' }),
options: {
lastMessages: 20,
workingMemory: {
enabled: true, // no `use` option any more
template: `# User
- Name:
- Preferred seat:`,
},
},
})
export const agent = new Agent({
id: 'assistant',
name: 'Assistant',
instructions: 'You are a helpful assistant.',
model: 'openai/gpt-6.1-sol', // the model string from Mastra's quickstart; use your own
memory,
})
The agent keeps working memory up to date by calling the updateWorkingMemory tool. Working memory is resource-scoped by default, so it follows the user across threads. libSQL is one of the adapters the docs list as supporting resource-scoped working memory, along with PostgreSQL, OracleDB, Upstash and MongoDB.
If your agent doesn't update it when you expect, the docs suggest saying how and when to fill the template in the agent's instructions.
Step 3: Call it with a thread and a resource
const res = await agent.generate("I'm Sam and I always pick the aisle seat.", {
memory: { thread: 'thread-1', resource: 'user-42' },
})
resource is the stable user ID and thread is the conversation. Resource-scoped working memory needs resource on every call.
Step 4: Check what was actually saved
This is the check the issue was really about. After a run, read the thread back and look at working memory:
const { messages } = await memory.recall({ threadId: 'thread-1', resourceId: 'user-42' })
console.log(messages.map((m) => m.role))
console.log(await memory.getWorkingMemory({ threadId: 'thread-1', resourceId: 'user-42' }))
I ran Steps 2 to 4 on current packages with a mock model (no real LLM). The mock wrote "Noted, Sam. Saving that.", called updateWorkingMemory and then wrote "Done.". The stored assistant message kept both text parts, including the text written before the tool call. The updateWorkingMemory tool call itself wasn't stored as a message part, and working memory held the updated profile. A new process reading thread-2 for the same resource got the same profile back.
So if you're on current versions and something still looks missing, check two things before filing a bug:
- Are you looking for the tool call itself? In my run the working memory tool call didn't appear among the stored message parts; the result went into working memory instead. The docs note one exception: with the experimental
useStateSignals: trueoption, the tool is registered assetWorkingMemoryand its tool-call parts are kept in the history. - Is the
resourcethe same? A differentresource, orscope: 'thread', means a separate working memory.
Step 5 (optional): Read-only working memory for sub-agents
Routing agents and sub-agents can read working memory without changing it:
await agent.generate('What do you know about me?', {
memory: { thread: 'thread-1', resource: 'user-42', options: { readOnly: true } },
})
Where MemorySync fits
Working memory is a single Markdown block (or a JSON object, if you use a schema) that the model rewrites. If you also want individual facts extracted and recalled per user from a hosted service, MemorySync's Mastra guide recommends running it next to Mastra's Memory: Memory for the transcript, MemorySync for what the agent should keep knowing. Its processors use processInput rather than a per-step hook, so a multi-step tool loop gets the recalled context once per generate/stream call. You don't need it to fix the issue above.
Troubleshooting
The workingMemory.use option has been removed. DeleteusefromworkingMemory. See Step 1.- Working memory is empty in a new thread. Pass the same
resource, and keep the defaultscope: 'resource'. Check that your storage adapter is on the supported list. - The agent never updates working memory. Add update rules to
instructions, per the docs. - Working memory is updated, but the user's message is missing. Check that
threadandresourceare passed on that call.
Common questions
Does Mastra still support workingMemory.use: 'tool-call'?
No. The option has been removed, and working memory always uses tool-call mode. Setting it throws when Memory is constructed, so delete the line.
How does a Mastra agent update working memory?
Through the updateWorkingMemory tool. You can also set it yourself when you create or update a thread (metadata.workingMemory), or call memory.updateWorkingMemory(...) directly (Mastra working memory docs).
Is Mastra working memory shared across threads?
By default, yes. It's resource-scoped, so every thread with the same resource sees it. Set scope: 'thread' to isolate it per conversation (Mastra working memory docs).
Can I use MemorySync with Mastra's working memory?
Yes. MemorySync's guide recommends running both: Mastra's Memory for conversation history and MemorySync's processors for durable per-user facts (MemorySync Mastra guide).
Sources
- GitHub issue mastra-ai/mastra#4221 and its comments (fix merged May 2025)
- Mastra docs: mastra.ai/docs/memory/working-memory
@mastra/core1.79.0 type definitions (dist/memory/types.d.ts:use?: never) and runtime error message- npm: @mastra/core 1.79.0, @mastra/memory 1.39.0, @mastra/libsql 1.27.0 (Node >= 22.13.0)
- MemorySync Mastra guide: docs.memorysync.io/guides/mastra
Try it
If you want to see a memory survive a full page reload before wiring up an agent, the MemorySync playground runs the save, reload and recall loop in your browser: memorysync.io/try.
About the author
- Building MemorySync (memorysync.io) — persistent memory infrastructure for AI agents, Cursor, and Claude Code.
Comments
Join the discussionNo comments yet. Be the first to comment.
