Skip to content

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 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:

// 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:

// 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.

// 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}:

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

// 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:

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. The frame carries the run's status and omits its args.

Next steps