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();| Registration | When it runs | Return |
|---|---|---|
.before("turn", fn) | Once per turn; the Patch applies to every model call in that turn | Patch |
.before("step", fn) | Every model call | Patch |
.after("step", fn) | After every model call, before tools or text take effect | Decision |
.after("turn", fn) | After the final answer | TurnDecision |
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.