Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/clear-buses-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@mastra/core': patch
---

Fixed signal providers losing memory access when harness forks agents per mode. Harness now propagates runtime services (memory, storage, etc.) to the base config agent during init so signal providers connected to it can deliver notifications.
56 changes: 56 additions & 0 deletions packages/core/src/harness/fork-clone-metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest';

import { Agent } from '../agent';
import { RequestContext } from '../request-context';
import { SignalProvider } from '../signals/signal-provider';

import { Harness } from './harness';
import type * as Tools from './tools';
Expand Down Expand Up @@ -199,4 +200,59 @@ describe('Harness fork clone metadata wiring', () => {
expect(builtIn.subagent).toBeDefined();
expect(builtIn.ask_user).toBeDefined();
});

it('propagates memory to the base config.agent so signal providers have access', async () => {
class TestSignalProvider extends SignalProvider<'test-signals'> {
readonly id = 'test-signals' as const;
getConnectedAgent() {
return this.agent;
}
}

const signalProvider = new TestSignalProvider();
const memoryFactory = vi.fn().mockResolvedValue({});

const baseAgent = new Agent({
name: 'base',
instructions: 'base agent',
model: { provider: 'openai', name: 'gpt-4o', toolChoice: 'auto' },
signals: [signalProvider],
Comment on lines +509 to +513

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

potential_issue: Replace newly added literal model IDs with registered placeholder tokens.

This test adds concrete model IDs (gpt-4o, openai/gpt-4o) instead of the tokenized placeholders required by repo policy.

As per coding guidelines, “When adding model names or IDs to examples, changesets, tests, or comments, use placeholder tokens from docs/src/plugins/remark-model-tokens/models.ts”.

Also applies to: 238-239

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/harness/fork-clone-metadata.test.ts` around lines 215 -
219, The test file packages/core/src/harness/fork-clone-metadata.test.ts uses
concrete model IDs like 'gpt-4o' and 'openai/gpt-4o' instead of registered
placeholder tokens. Replace all literal model IDs in the baseAgent creation
(where model provider is set to 'openai' with name 'gpt-4o') and at the other
affected location (line 238-239) with the appropriate placeholder tokens defined
in docs/src/plugins/remark-model-tokens/models.ts according to the repo's coding
guidelines for model names in tests and examples.

Source: Coding guidelines

});

// Signal provider is connected to the base agent
expect(signalProvider.isConnected).toBe(true);
expect(signalProvider.getConnectedAgent()).toBe(baseAgent);

// Before harness init, base agent has no memory
expect(baseAgent.hasOwnMemory()).toBe(false);

const harness = new Harness({
id: 'test',
resourceId: 'test-resource',
memory: memoryFactory as unknown as never,
modes: [
{
id: 'build',
name: 'Build',
default: true,
defaultModelId: 'openai/gpt-4o',
instructions: 'Build things.',
},
{
id: 'plan',
name: 'Plan',
defaultModelId: 'openai/gpt-4o',
instructions: 'Plan things.',
},
],
agent: baseAgent,
});

await harness.init();

// After init, signal provider's connected agent (the base agent) should have memory
const connectedAgent = signalProvider.getConnectedAgent()!;
expect(connectedAgent).toBe(baseAgent);
expect(connectedAgent.hasOwnMemory()).toBe(true);
});
});
9 changes: 9 additions & 0 deletions packages/core/src/harness/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,15 @@ export class Harness<TState = {}> {
this.propagateRuntimeServicesToAgent(agent);
}

// Also propagate runtime services to the base config agent so that signal
// providers connected to it during construction have access to memory and
// storage. Without this, signal providers (e.g. GitHubSignals) that were
// `connect()`-ed to the base agent before forking remain pointed at a
// memoryless instance and fail when attempting notification delivery.
if (this.config.agent) {
this.propagateRuntimeServicesToAgent(this.config.agent);
}

this.startHeartbeats();
}

Expand Down
Loading