Your Mastra Agent Remembers the Thread. Your Scheduled Runs Keep Starting New Ones.
Your Mastra Agent Remembers the Thread. Your Scheduled Runs Keep Starting New Ones. Your Mastra agent ran at 2 AM. It pulled the overnight support tickets, drafted replies, escalated the three it could not resolve, and shut down. At 2 AM the next night it ran again, and it asked for the escalation policy it had already been told about the night before, re-drafted replies to tickets it had already answered, and flagged a ticket as brand new that it had escalated yesterday. Nothing crashed. The
Your Mastra Agent Remembers the Thread. Your Scheduled Runs Keep Starting New Ones.
Your Mastra agent ran at 2 AM. It pulled the overnight support tickets, drafted replies, escalated the three it could not resolve, and shut down. At 2 AM the next night it ran again, and it asked for the escalation policy it had already been told about the night before, re-drafted replies to tickets it had already answered, and flagged a ticket as brand new that it had escalated yesterday.
Nothing crashed. The workflow did not change. The agent simply had no idea that yesterday happened. Mastra ships one of the best-documented memory systems of any agent framework, which makes this feel like a bug. It is not a bug. It is a detail most scheduled setups walk right past: Mastra's memory is organized around conversation threads, and your cron job hands the agent a new thread every run.
What the agent actually keeps
Mastra's Memory class is four layers stacked on a single message store, and which layer your scheduled run gets depends on two identifiers passed with every generation call: resourceId and threadId.
The base layer is conversation history. Configure lastMessages and the agent reads the recent turns of its current thread before answering. On top of that sits working memory, a small structured scratchpad you can type with a Zod schema, holding slots like the current stage, open questions, assumptions, and risks. Semantic recall runs vector search across past messages so the agent can pull in a relevant earlier turn. And observational memory keeps a running log of decisions and actions, built for agents that run over weeks of conversation.
The scoping rules decide what survives. Working memory and semantic recall default to resource scope, so two different threads under the same resourceId share them. Message history is thread-scoped: only the thread that wrote it can read it. When agents call each other through the delegation flow, memory travels with the delegation. When you call agents directly, you control sharing yourself by passing matching identifiers.
Inside one long-lived thread, this is genuinely good memory. The agent remembers the conversation, the scratchpad, and relevant turns from earlier. That is the chat-assistant case, and Mastra handles it well.
Where the scheduled run loses it
A scheduled run is not a long-lived thread. That single fact is the whole failure.
The trigger fires, your code calls agent.generate with a fresh thread ID (or no thread at all), the agent does its work, and the process exits. Tomorrow night it happens again with a different thread. Each thread is a sealed room, and the support tickets triaged on Monday are invisible on Tuesday. The agent re-asks for the escalation policy, re-drafts the same replies, and rebuilds context it already built, because from inside the new thread, last night never existed.
Resource-scoped memory can carry state across the boundary, but the run has to ask for it. That means passing the same resourceId on every invocation, enabling working memory with a schema that captures what the job actually needs, and running a store that survives between executions. The default is LibSQL as a local file. On ephemeral infrastructure, that file dies with the container and takes every layer with it. Moving to Postgres or a hosted store fixes durability, and turns your memory into a database you operate: backups, uptime, migrations.
Even with all of that wired up, there is a quieter way it breaks. Working memory is a small structured blob, not a transcript. It holds what the schema says to hold. If the triage agent's schema tracks the current stage and open questions but has no slot for which tickets were answered and which replies were already sent, Tuesday's run repeats Monday's work anyway. Semantic recall can rescue old turns, but it needs embeddings configured and a shared resource ID linking the threads. One missing piece and the agent is functionally amnesic on top of excellent framework memory.
Teaching the scheduled run to remember
If you keep the memory inside Mastra, the fix has three parts.
Start by giving the recurring job a stable identity. The simplest version is one thread per job: the cron handler always generates into the same thread ID, so history accumulates across runs the way it does in a chat session. When several agents or workflows share the job, give them all one resourceId and keep working memory and semantic recall at resource scope, so the layers are shared even while each keeps its own thread.
Then write the working memory schema for the job, not for a chat. A triage agent needs slots for tickets answered, tickets escalated, and policy answers it was given. A reporting agent needs slots for sources already pulled and numbers already computed. The schema is what separates a scratchpad that carries the job forward from one that fills with noise.
Finally, put the store somewhere that outlives the runner, and prove it. Kill the job mid-run, then check that the next run still sees yesterday's state. If it does not, nothing else you configured matters.
When one framework's memory is not enough
There is an honest ceiling here. Mastra's memory belongs to one application: your Mastra app. The moment the same operator runs the triage agent in Mastra, a scoring workflow in n8n, and a reporting script on a plain cron box, the memories live in three places and none of them can see the others. Framework memory is also infrastructure you own: the store, the embeddings, and the schema migrations are yours to keep healthy.
That gap is exactly what Vilix AI was built for. It is a cloud-hosted memory layer, so there is nothing to run and no volume to keep alive. Your agents connect over MCP, which means the same memory follows the agent whether it runs in Mastra, in a workflow tool, or in a scheduled script: one memory, every tool, every run. It keeps full conversation history, not just extracted facts, so Tuesday's run can look at what actually happened on Monday instead of working from a summary of a summary. The free plan is free forever, the 7-day Pro trial never asks for a credit card, and you can export or delete everything at any time in a portable format. Your data stays yours, including the decision to leave with it.
The thread is the memory's address
The short version: in Mastra, memory has an address, and the address is the thread. Reuse a stable thread or resource ID and your scheduled agent accumulates memory like a chat does. Let every run mint a new thread and the agent wakes up a stranger to its own yesterday, however good the memory system underneath is.
If your scheduled agents live in more than one tool, or you would rather not babysit another database, give them one shared memory instead of one per framework. Then the 2 AM run stops asking questions it already asked, and you stop re-briefing an agent that should have known.