Skip to content

[BUG] Client-side tool results lost when Observational Memory is enabled #15244

@Onweekendd

Description

@Onweekendd

Describe the Bug

When Observational Memory (OM) is enabled, client-side tool (tool without execute function) execution results are lost on the next request. The agent cannot see the tool result and acts as if it never arrived.

OM's filterObservedMessages removes messages that have been "observed", but has no awareness of tool-call / tool-result pairing. On the second request (when the client sends back the tool result), OM removes the tool-call message because its ID is in observedMessageIds, leaving the tool-result orphaned — the LLM sees a result without the corresponding call.

This differs from #14282 which only fixed provider-executed tools (e.g. Anthropic web_search) by deferring observation when state: 'call' is detected mid-stream. Client-side tools return in a separate HTTP request where the call already has state: 'result', so that guard doesn't help.

Steps To Reproduce

  1. Create an agent with OM enabled and a client-side tool:
const agent = new Agent({
  name: 'test-agent',
  instructions: 'You are a helpful assistant.',
  model: openai('gpt-4o'),
  tools: {
    myTool: createTool({
      id: 'my-tool',
      description: 'A client-side tool',
      inputSchema: z.object({ query: z.string() }),
      // no execute → client-side tool
    }),
  },
  memory: new Memory({
    options: { observationalMemory: { enabled: true } },
  }),
});
  1. Start a conversation. The agent generates a tool call (state: 'call') and returns it to the client.

  2. Client executes the tool locally and sends the result back in a new request (messages include the tool-call + tool-result).

  3. Expected: Agent sees the tool result and continues the conversation.

  4. Actual: Agent does not see the tool result — it's effectively lost due to OM filtering.

Link to Minimal Reproducible Example

/

Expected Behavior

The agent should see the client-side tool result and continue the conversation normally. filterObservedMessages (packages/memory/src/processors/observational-memory/message-utils.ts) should check for tool-call/tool-result pair integrity before removing messages. If a message containing a tool-invocation part is about to be removed, the filter should verify that no remaining message has a tool-result part referencing the same toolCallId. If an orphaned result would be created, either keep the tool-call message or remove both.

Workaround: Converting client-side tools to server-side tools (adding an execute function) avoids this, since results are resolved within the same request's agentic loop before OM's cross-request filtering kicks in.

Environment Information

- `@mastra/core`: >= 1.22.0
- `@mastra/memory`: >= 1.13.0

Verification

  • I have searched the existing issues to make sure this is not a duplicate
  • I have included sufficient information for the team to reproduce and understand the issue

Metadata

Metadata

Assignees

No one assigned

    Labels

    MemoryIssues with Mastra's Memory primitiveToolsIssues with user made tools for Agent tool callingbugSomething isn't workingeffort:mediumimpact:hightrio-tb

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions