NylorunDocsBeta
BuildRunDeployReferenceMore

Hooks

Scoped before and after hooks replace beforeModelCall and afterModelCall.

Hooks intercept a turn or a model step. Register them on the builder or inside a capability. There are no compatibility aliases for the old names.

import { Agent } from "@nylorun/agents";

export const support = Agent({
  id: "support",
  instructions: "Help with orders.",
})
  .before("turn", ({ info }) => ({
    instructions: [`Tenant ${info?.tenantId ?? "unknown"}`],
  }))
  .after("step", ({ text, toolCalls, attempt }) => {
    if (toolCalls.length === 0 && !text) {
      return attempt < 2 ? { retry: "Answer or call a tool." } : { block: "Empty step." };
    }
    return {};
  })
  .after("turn", ({ text, attempt }) => {
    if (!text?.trim()) {
      return attempt < 2 ? { retry: "Give a short final answer." } : {};
    }
    return {};
  })
  .build();
RegistrationWhen it runsReturn
.before("turn", fn)Once per turn; the Patch applies to every model call in that turnPatch
.before("step", fn)Every model callPatch
.after("step", fn)After every model call, before tools or text take effectDecision
.after("turn", fn)After the final answerTurnDecision

before("turn") is issued only in a turn's first segment. In a Runtime, every capability registered at one hook point runs in a single executor action, so a hook point costs one round trip per turn or per model call.

Hooks may run more than once when a delivery is retried: an expired hook claim is offered again instead of becoming uncertain. Keep side effects in tools.

Patch, Decision, and TurnDecision

A Patch can add instructions, toggle capabilities or tools, merge state, or block the model call. It cannot set model.

A Decision can replace text, deny or approve proposed tool calls, retry with feedback, or block.

A TurnDecision can replace final text (plain-text agents) or output (agents with an outputSchema), retry, or block.

retry now retries. From after("step") it denies the proposed tool calls with the feedback, or sends a text answer back with the feedback as a message; from after("turn") it sends the final answer back. The engine does not cap retries: bound them with attempt, for example attempt < 2 ? { retry: "…" } : { block: "…" }.

Capability form:

capability({
  id: "policy",
  before: { turn: ({ info }) => ({ instructions: [`Tenant ${info?.tenantId}`] }) },
  after: { step: ({ text }) => ({}), turn: ({ text }) => ({}) },
})

The published manifest lists each hook as { at, scope } on capabilities[].hooks. Action kind is hook with { at, scope, capabilityIds }. Rebuild agents after upgrading; Runtime cancels leftover old hook actions and fails in-flight schema 3 turns. Start new sessions.

See Capabilities and the 0.15 → 0.17 migration.

Next step

On this page