# Effects

Schedule work that runs outside a conversation turn

## What is an effect?

An effect is a function that a thread schedules to run later, outside tool execution. Use an effect for a delayed notification, a cleanup task, or any work that must not block a model step.

An effect differs from a [tool](/docs/core-concepts/tools) in three ways:

- An effect returns nothing to the model.
- An effect can run after a delay.
- An effect can run after the conversation turn that scheduled it has ended.

## Defining an effect

Create a file in `agents/effects/` and export a `defineEffect` call as the default export. The file name is the effect name: `agents/effects/send_reminder.ts` defines the effect `send_reminder`. Use snake_case names. Place effect files directly in `agents/effects/`.

An effect with arguments takes a description, a Zod schema, and a function:

```typescript
// agents/effects/send_reminder.ts
import { defineEffect } from '@standardagents/spec';
import { z } from 'zod';

export default defineEffect(
  'Add a reminder message to the thread',
  z.object({
    text: z.string().describe('The reminder text'),
  }),
  async (state, args) => {
    const message = await state.injectMessage({
      role: 'assistant',
      content: args.text,
    });
    state.emit('reminder_delivered', { messageId: message.id });
  }
);
```

An effect without arguments takes a description and a function:

```typescript
// agents/effects/clear_scratch_files.ts
import { defineEffect } from '@standardagents/spec';

export default defineEffect('Delete the files in /scratch', async (state) => {
  const { entries } = await state.readdirFile('/scratch');
  for (const entry of entries) {
    if (!entry.isDirectory) {
      await state.unlinkFile(entry.path);
    }
  }
});
```

The function receives a `ThreadState` for the thread that scheduled the effect. `state.execution` is `null`, because an effect does not run inside a conversation turn.

## Scheduling an effect

Call `state.scheduleEffect(name, args, delay?)` from a tool, a hook, a handler, or a thread endpoint.

```typescript
// agents/tools/remind_me.ts
import { defineTool } from '@standardagents/spec';
import { z } from 'zod';

export default defineTool({
  description: 'Schedule a reminder message for later in this conversation.',
  args: z.object({
    text: z.string().describe('The reminder text'),
    delayMinutes: z.number().positive().describe('Minutes to wait'),
  }),
  execute: async (state, args) => {
    const effectId = await state.scheduleEffect(
      'send_reminder',
      { text: args.text },
      args.delayMinutes * 60 * 1000
    );
    return { status: 'success', result: `Reminder scheduled (${effectId}).` };
  },
});
```

| Parameter | Type                      | Description                                  |
| --------- | ------------------------- | -------------------------------------------- |
| `name`    | `string`                  | The effect name                              |
| `args`    | `Record<string, unknown>` | The arguments stored for the effect function |
| `delay`   | `number`                  | Milliseconds to wait. The default is `0`     |

`scheduleEffect` returns the ID of the scheduled effect. It throws `Effect not found` when no effect has that name.

### Effects from packages

An installed package can export effects. Schedule one by its qualified name, `{packageId}/{effectName}`:

```typescript
await state.scheduleEffect('standardagent-sales/send_digest', { accountId: 'acct_123' }, 0);
```

Code inside a package schedules that package's own effects by their short names.

## Inspecting and canceling

```typescript
// Pending effects, with an optional name filter
const pending = await state.getScheduledEffects('send_reminder');

// Cancel one before it runs
const canceled = await state.removeScheduledEffect(pending[0].id);
```

`getScheduledEffects` returns the effects that have not run yet:

```typescript
interface ScheduledEffect {
  id: string;
  name: string;
  args: Record<string, unknown>;
  scheduledAt: number; // microseconds since epoch
  createdAt: number;   // microseconds since epoch
}
```

Effect inspection and cancellation inside a packed package are limited to that
package's effects.

`removeScheduledEffect` returns `true` when it canceled a pending effect and `false` when no pending effect has that ID. A canceled effect stays in the thread's effect history.

## Execution

Standard Agents Builder stores each scheduled effect in the thread's SQLite database and runs it from the thread's Durable Object alarm. A scheduled effect therefore survives a Worker restart.

- The stored `args` are passed to the effect function when it runs. When the effect has a schema, the runtime validates the arguments first. Arguments that fail validation fail the run.
- An effect that throws is marked `failed` with its error message. The runtime does not run a failed effect again.
- An effect that a restart interrupts returns to the queue and runs again, up to three attempts. Write effect functions so that a second run causes no harm.
- A failure does not change the conversation that scheduled the effect.
- Deleting a thread discards its scheduled effects.

The thread keeps completed and canceled effect runs for 30 days and failed runs for 24 hours. It keeps the 1,000 most recent finished runs.

## Observing effects

The Effects pane of a thread in the Standard Agents Builder interface lists pending, completed, failed, and canceled runs. From that pane you can run a pending effect now, replay a finished one, or cancel a pending one.

Each change to a scheduled effect sends an `event` frame with `eventType` `effect_changed` on the thread's [WebSocket stream](/docs/api-reference/websocket/stream). The frame carries the run's status and omits its `args`.

## Next steps

- [Utilities](/docs/core-concepts/utilities#scheduleeffect)

  See `scheduleEffect` beside the other state utilities

* [Tools](/docs/core-concepts/tools)

  Schedule effects from tool code

- [Hooks](/docs/core-concepts/hooks)

  Schedule effects at lifecycle points

* [ThreadState](/docs/core-concepts/flowstate)

  See the state object an effect receives
