# Signals

> **Experimental:** This feature is in alpha. Breaking changes may occur without a major version bump until the API is stable.

Signals are a way to interact with an agent through a thread. Instead of starting every interaction with `agent.stream()`, subscribe to a thread and send signals. Mastra either wakes the agent when the thread is idle or drops the signal into the running agent loop.

Signals are a context engineering tool for guiding the agent in real time as the agent loop progresses. Use them to add system-generated content from external event sources, such as incoming email notifications, GitHub pull request comments, background task notifications, and similar events.

## Quickstart

Subscribe to the thread before sending signals. The subscription receives the active stream when the signal wakes the agent or enters a running loop.

```typescript
const subscription = await agent.subscribeToThread({
  resourceId: 'user_123',
  threadId: 'thread_456',
})

agent.sendSignal(
  {
    type: 'user-message',
    contents: 'Compare that with the previous option.',
  },
  {
    resourceId: 'user_123',
    threadId: 'thread_456',
  },
)

for await (const chunk of subscription.stream) {
  console.log(chunk)
}
```

When the thread has a running agent stream, the signal becomes new input inside that agent loop. When the thread is idle, Mastra starts a stream with the signal as the first input.

## Control signal behavior

By default, Mastra delivers signals to active runs and wakes idle threads. Use `ifActive.behavior` and `ifIdle.behavior` to change that behavior.

```typescript
const result = agent.sendSignal(
  {
    type: 'user-message',
    contents: 'Store this for later, but do not wake the agent.',
  },
  {
    resourceId: 'user_123',
    threadId: 'thread_456',
    ifIdle: {
      behavior: 'persist',
    },
  },
)

await result.persisted
```

The behavior options are:

- `ifActive.behavior: 'deliver'`: Add the signal to the running agent loop. This is the default.
- `ifActive.behavior: 'persist'`: Save the signal to memory without adding it to the running loop.
- `ifActive.behavior: 'discard'`: Ignore the signal while the thread is active.
- `ifIdle.behavior: 'wake'`: Start a stream with the signal as the first input. This is the default.
- `ifIdle.behavior: 'persist'`: Save the signal to memory without starting a stream.
- `ifIdle.behavior: 'discard'`: Ignore the signal while the thread is idle.

Pass `ifIdle.streamOptions` when the idle wake-up stream needs options such as model settings, tools, or runtime context. You do not need to repeat `memory.resource` or `memory.thread`; Mastra uses the top-level `resourceId` and `threadId` for the thread.

```typescript
agent.sendSignal(
  {
    type: 'user-message',
    contents: 'Continue with the next step.',
  },
  {
    resourceId: 'user_123',
    threadId: 'thread_456',
    ifIdle: {
      behavior: 'wake',
      streamOptions: {
        maxSteps: 3,
      },
    },
  },
)
```

## Identify users with attributes

Use `attributes` to tag each signal with user identity. The signal type and attributes are rendered as XML so the model can distinguish who said what in a multi-user thread:

```typescript
agent.sendSignal(
  {
    type: 'user',
    contents: 'Can we simplify the API surface?',
    attributes: { name: 'Devin', from: 'slack' },
  },
  {
    resourceId: 'user_123',
    threadId: 'thread_456',
  },
)
```

The model receives:

```xml
<user name="Devin" from="slack">Can we simplify the API surface?</user>
```

The UI sees just the message contents but can also read `attributes` and `metadata` off the signal message for custom rendering (e.g. showing user names, avatars, or platform badges).

## Send external event context

Use custom signal types for system-generated context. Non-user signal types are rendered as XML-style user-role context so they can appear inside conversation history without looking like assistant output.

```typescript
agent.sendSignal(
  {
    type: 'system-reminder',
    contents: 'User X has left a new PR comment asking for a smaller API surface.',
    attributes: {
      source: 'github',
      pr: '123',
    },
  },
  {
    resourceId: 'user_123',
    threadId: 'thread_456',
  },
)
```

The model receives the custom signal as context like this:

```xml
<system-reminder source="github" pr="123">User X has left a new PR comment asking for a smaller API surface.</system-reminder>
```

Use XML-safe signal type names and attribute names. Signal type names and attribute names can contain letters, numbers, underscores, periods, and hyphens. They must start with a letter or underscore.

## Use the client SDK

The JavaScript client exposes the same thread signal APIs. Use `subscribeToThread()` before `sendSignal()` so the client can render the stream that wakes from, or receives, the signal.

```typescript
const agent = client.getAgent('supportAgent')

const subscription = await agent.subscribeToThread({
  resourceId: 'user_123',
  threadId: 'thread_456',
})

await agent.sendSignal({
  signal: {
    type: 'user-message',
    contents: 'Show the shorter version.',
  },
  resourceId: 'user_123',
  threadId: 'thread_456',
})

await subscription.processDataStream({
  onChunk: chunk => {
    console.log(chunk)
  },
})
```

## Related

- [`Agent.sendSignal()`](https://mastra.ai/reference/agents/agent)
- [`Agent.subscribeToThread()`](https://mastra.ai/reference/agents/agent)
- [`client.getAgent().sendSignal()`](https://mastra.ai/reference/client-js/agents)
- [`client.getAgent().subscribeToThread()`](https://mastra.ai/reference/client-js/agents)