Theo Docs
TheoKitTheoKit-SDKTheoUITheo PaaS
Concepts

Hooks

Lifecycle hooks for plugins — pre_tool_call veto, post_assistant_reply, and 6 others.

Canonical contract: packages/sdk/docs.mdHookName enum, Plugin discriminated union.

Hooks are typed extension points in the agent loop. Plugins register handlers; the loop invokes them at fixed lifecycle moments. The list of hook names is closed (8 fixed hooks, ADR D100) — adding a new one is a SDK release, not a runtime concern.

The 8 hooks

HookWhenCan veto?
pre_user_sendBefore user prompt added to historyNo
post_user_sendAfter user prompt addedNo
pre_tool_callBefore agent invokes a toolYes (return { block: true, message? })
post_tool_callAfter tool returnsNo
pre_assistant_replyBefore model generates replyNo
post_assistant_replyAfter model emits final assistant messageNo
pre_disposeBefore agent disposesNo
post_disposeAfter agent disposedNo

Veto pattern

pre_tool_call is the only veto hook. Returning { block: true, message: "reason" } aborts the tool call — the agent receives a synthetic tool_result isError: true and continues. Never throw from a veto hook (ADR D101).

import { definePlugin } from "@theokit/sdk";

const noShellInProd = definePlugin({
  kind: "hook",
  name: "no-shell-in-prod",
  hooks: {
    pre_tool_call: (ctx) => {
      if (ctx.toolName === "shell" && process.env.NODE_ENV === "production") {
        return { block: true, message: "Shell tool disabled in production." };
      }
    },
  },
});

const agent = await Agent.create({ apiKey, model, plugins: [noShellInProd] });

Plugin context

Every hook receives a typed context object. The shape depends on the hook. In dev mode the context is sealed via Proxy (ADR D99) — assigning to it throws, helping you catch accidental mutation.

Example PreToolCallContext:

type PreToolCallContext = {
  toolName: string;
  toolArgs: unknown;
  agentId: string;
  runId: string;
  turnIndex: number;
  // ...
};

See the Plugins concept for the full Plugin contract.

API reference

On this page