Ploy
Ploy
Agent SDK

Hooks

Lifecycle hooks for reacting to agent events with full env and tool access.

Hooks

Lifecycle hooks let you run custom logic when specific events occur during an agent run. Every hook receives a HookContext as its first argument, giving you access to the full environment, state, and the ability to call tools.

HookContext

All hooks receive this context:

interface HookContext {
	userId: string; // The user who triggered the event
	chatId: string; // The active chat ID
	env: PloyEnv; // Full Ploy environment bindings
	state: StateManager; // Key-value state (get/set/delete/getJSON/setJSON)
	execute: (toolName: string, args: Record<string, unknown>) => Promise<string>;
}

The execute function lets you call any registered tool (built-in or custom) from within a hook:

hooks: {
  async onMemorySet(ctx, key, secret) {
    // Call the artifact_create tool from within a hook
    await ctx.execute("artifact_create", {
      filename: "memory-log.txt",
      content: `Memory "${key}" was set (secret: ${secret})`,
    });
  },
}

Available Hooks

onMemorySet

Fires when the agent stores a memory entry.

onMemorySet?: (ctx: HookContext, key: string, secret: boolean) => Promise<void>
hooks: {
  async onMemorySet(ctx, key, secret) {
    if (secret) {
      await ctx.state.set(`audit:${ctx.userId}:${Date.now()}`, `secret stored: ${key}`);
    }
  },
}

onMemoryDelete

Fires when the agent deletes a memory entry.

onMemoryDelete?: (ctx: HookContext, key: string) => Promise<void>

onArtifactCreated

Fires when the agent creates a file artifact.

onArtifactCreated?: (ctx: HookContext, filename: string, content: string) => Promise<void>
hooks: {
  async onArtifactCreated(ctx, filename, content) {
    // Store metadata about created artifacts
    const artifacts = await ctx.state.getJSON<string[]>(`artifacts:${ctx.userId}`) ?? [];
    artifacts.push(filename);
    await ctx.state.setJSON(`artifacts:${ctx.userId}`, artifacts);
  },
}

onTaskScheduled

Fires when the agent schedules a future task.

onTaskScheduled?: (ctx: HookContext, task: ScheduledTask) => Promise<void>

The ScheduledTask contains:

interface ScheduledTask {
	id: string;
	description: string;
	scheduledTime: number;
	recurring: boolean;
	intervalMs?: number;
}

onRunComplete

Fires when the agent finishes a run with a response (no more tool calls).

onRunComplete?: (ctx: HookContext, response: string) => Promise<void>
hooks: {
  async onRunComplete(ctx, response) {
    // Log the response for analytics
    await ctx.state.set(
      `log:${ctx.userId}:${Date.now()}`,
      response.slice(0, 500),
    );
  },
}

onRunError

Fires when the agent run fails with an error.

onRunError?: (ctx: HookContext, error: Error) => Promise<void>
hooks: {
  async onRunError(ctx, error) {
    // Notify an external service
    await fetch("https://alerts.example.com/webhook", {
      method: "POST",
      body: JSON.stringify({
        userId: ctx.userId,
        error: error.message,
      }),
    });
  },
}

Hooks run inside the durable workflow, so they benefit from automatic retries on failure. Keep hooks lightweight to avoid slowing down the agent loop.

Next Steps

  • createAgent() -- Pass hooks via the hooks config option
  • Tools -- Tools that trigger hooks (memory, artifacts, scheduling)
  • Workflows -- Understand the durable tick loop that runs hooks

How is this guide?

Last updated on