Open Agent Loops
API Reference

Hooks

@open-agent-loops/core


Defined in: primitives/loop.ts:105

Lifecycle hooks for guardrails and context shaping.

Remarks

Every hook is optional and may be sync or async. They run at fixed points in a turn: transformContext just before the model call, gateToolCalls once per turn ahead of execution — the single point that admits or blocks calls — then afterToolCall after each individual call runs.

Methods

afterToolCall()?

optional afterToolCall(info): 
  | void
  | ToolResultOverride
| Promise<void | ToolResultOverride>;

Defined in: primitives/loop.ts:148

Inspect/override a tool result after it executes.

Parameters

ParameterTypeDescription
info{ args: ToolArguments; isError: boolean; result: ToolResult; toolCall: ToolCall; }The call, its args, the produced result, and isError.
info.argsToolArguments-
info.isErrorboolean-
info.resultToolResult-
info.toolCallToolCall-

Returns

| void | ToolResultOverride | Promise<void | ToolResultOverride>

Nothing to keep the result, or a ToolResultOverride.


drainFollowUp()?

optional drainFollowUp(): 
  | Message[]
| Promise<Message[]>;

Defined in: primitives/loop.ts:192

Pull queued follow-up messages when the run would otherwise stop at a natural final answer (a turn with no tool calls).

Returns

| Message[] | Promise<Message[]>

Messages to inject now, or an empty array to let the run end.

Remarks

Returning a non-empty array continues the run in place — the messages are appended (and emitted as message_injected) and another turn runs — instead of ending. Keeping it one continuous run (rather than a fresh runAgent call) is what keeps the trace a single agent_start → agent_end with monotonic steps. Like drainSteering, it is not consulted once the maxSteps cap is reached.


drainSteering()?

optional drainSteering(): 
  | Message[]
| Promise<Message[]>;

Defined in: primitives/loop.ts:175

Pull queued steering messages to inject before the next turn.

Returns

| Message[] | Promise<Message[]>

Messages to inject now, or an empty array to leave the run as-is.

Remarks

The loop calls this once per turn, right after the turn's tool results are recorded — so tool_use/tool_result pairing is always intact and the next turn sees [assistant tool_calls][tool results][steering]. Returning a non-empty array redirects the run: the messages are appended (and emitted as message_injected), and the run takes another turn even if a tool asked to terminate or a stopWhen would have fired. It never overrides the maxSteps cap — neither queue is drained once the cap is reached, so queued messages stay for a later run.

The caller owns the queue; the loop only pulls. Returning how many messages (one vs all queued) is the caller's "one-at-a-time" vs "all" policy. This is the seam steering only matters across — input that arrives while a run is in flight — so the feeding source must be non-blocking.


gateToolCalls()?

optional gateToolCalls(batch): 
  | GateDecision[]
| Promise<GateDecision[]>;

Defined in: primitives/loop.ts:141

Admit or block tool calls as a batch, before any of them execute.

Parameters

ParameterTypeDescription
batchToolGateRequest[]The turn's well-formed tool calls with parsed arguments.

Returns

| GateDecision[] | Promise<GateDecision[]>

One GateDecision per request, index-aligned with batch.

Remarks

The whole turn's calls arrive together, so this runs once per turn — serially, ahead of the parallel execution phase — which makes it the right place to prompt for permission without racing concurrent prompts. Only well-formed calls (known tool + valid args) are presented; unknown/invalid calls skip the gate and surface as the usual error results. See ../permissions for an allow/deny/ask implementation.

See


transformContext()?

optional transformContext(messages): 
  | Message[]
| Promise<Message[]>;

Defined in: primitives/loop.ts:124

Reshape history right before it's sent to the model.

Parameters

ParameterTypeDescription
messagesMessage[]The current working history about to be sent.

Returns

| Message[] | Promise<Message[]>

The (possibly reshaped) messages to send to the model.

Remarks

This is the seam for long-horizon context management — compaction (summarize, then restart from the summary), structured note-taking, and tool-result clearing — that keeps a long-running agent inside its context window.

Sources of truth:

On this page