FluxyChat

Guides

Lifecycle Callbacks

Lifecycle callbacks let you run custom code at key points during `runAgentLoop`. Use them for logging, analytics, monitoring, metrics, or any application-specif

Lifecycle Callbacks

Lifecycle callbacks let you run custom code at key points during runAgentLoop. Use them for logging, analytics, monitoring, metrics, or any application-specific hook without modifying the loop internals.

Callbacks can be synchronous or asynchronous. If a callback throws, the error is caught internally and the loop continues.

Available Callbacks

CallbackWhenEvent
onStartOnce before any steps runLoopStartEvent
onStepStartBefore each stepStepStartEvent
onToolExecutionStartBefore each tool's execute functionToolExecutionStartEvent
onToolExecutionEndAfter each tool's execute completes or errorsToolExecutionEndEvent
onStepEndAfter each step completesStepEndEvent
onEndOnce when the loop finishesLoopEndEvent

Execution Order

A typical single-step loop with one tool call runs in this order:

  1. onStart
  2. onStepStart
  3. onToolExecutionStart
  4. onToolExecutionEnd
  5. onStepEnd
  6. onEnd

A multi-step loop with tool calls in each step repeats steps 2–5:

  1. onStart
  2. onStepStartonToolExecutionStartonToolExecutionEndonStepEnd
  3. onStepStartonToolExecutionStartonToolExecutionEndonStepEnd
  4. onEnd

Basic Usage

import { runAgentLoop } from '@fluxy-chat/agent';

const result = await runAgentLoop({
  model: someModel,
  tools: { /* ... */ },

  onStart({ callId }) {
    console.log('Loop started', { callId });
  },

  onStepEnd({ stepNumber, text, toolResults, finishReason }) {
    console.log(`Step ${stepNumber} finished`, {
      finishReason,
      toolCount: toolResults.length,
      textLength: text.length,
    });
  },

  onEnd({ text, usage, steps }) {
    console.log('Loop finished', {
      totalTokens: usage.totalTokens,
      stepCount: steps.length,
    });
  },
});

Request Logging

Use onStart and onEnd to record one application log for the beginning and end of the loop:

onStart({ callId, tools, maxSteps }) {
  logger.info('agent.loop.started', { callId, maxSteps, tools: Object.keys(tools ?? {}) });
},

onEnd({ callId, finishReason, usage, steps }) {
  logger.info('agent.loop.finished', {
    callId, finishReason,
    totalTokens: usage.totalTokens,
    stepCount: steps.length,
  });
},

Debugging Multi-Step Execution

Use onStepStart and onStepEnd to understand how many steps the loop took and which tools were called:

onStepStart({ stepNumber, state }) {
  console.log(`Step ${stepNumber}: ${state.toolResults.length} tools so far`);
},

onStepEnd({ stepNumber, toolCalls, toolResults }) {
  console.log(`Step ${stepNumber} tools:`, toolCalls.map(t => t.name));
  console.log(`Step ${stepNumber} results:`, toolResults.map(r => r.output));
},

Monitoring Tool Execution

Use onToolExecutionStart and onToolExecutionEnd to track tool usage, latency, and errors:

onToolExecutionStart({ toolCall }) {
  logger.info('agent.tool.started', {
    toolName: toolCall.name,
    input: toolCall.input,
  });
},

onToolExecutionEnd({ toolCall, toolExecutionMs, output, error }) {
  logger.info('agent.tool.finished', {
    toolName: toolCall.name,
    durationMs: toolExecutionMs,
    success: !error,
  });
},

onToolExecutionEnd always fires — for successful results, errors denied approvals, and timeouts. Check the error field to distinguish success from failure.

Event Data Reference

LoopStartEvent

FieldTypeDescription
callIdstringUnique ID for this loop execution (call_...)
toolsRecord<string, unknown> | undefinedAvailable tools
maxStepsnumberMaximum steps configured
runtimeunknownShared runtime context

StepStartEvent

FieldTypeDescription
callIdstringLoop call ID
stepNumbernumberZero-based step index
stateAgentLoopStateCurrent loop state (steps, toolResults, usage)

ToolExecutionStartEvent

FieldTypeDescription
callIdstringLoop call ID
stepNumbernumberCurrent step index
toolCallAIToolCallThe tool call about to execute (id, name, input)
toolContextunknown | undefinedTool-specific context from toolContexts

ToolExecutionEndEvent

FieldTypeDescription
callIdstringLoop call ID
stepNumbernumberCurrent step index
toolCallAIToolCallThe tool call that executed
toolContextunknown | undefinedTool-specific context
toolExecutionMsnumberExecution duration in ms
outputunknown | undefinedTool output (undefined on error)
errorunknown | undefinedTool error (undefined on success)
approvalToolApprovalRecord | undefinedApproval result if applicable

StepEndEvent

FieldTypeDescription
callIdstringLoop call ID
stepNumbernumberZero-based step index
textstringText generated in this step
toolCallsreadonly AIToolCall[]Tool calls in this step
toolResultsreadonly AIToolResult[]Tool results from this step
finishReasonAIFinishReasonHow the step finished
usageAIUsageToken usage so far
stateAgentLoopStateCurrent loop state

LoopEndEvent

FieldTypeDescription
callIdstringLoop call ID
textstringComplete generated text
toolCallsreadonly AIToolCall[]All tool calls across all steps
toolResultsreadonly AIToolResult[]All tool results across all steps
finishReasonAIFinishReasonFinal finish reason
usageAIUsageTotal token usage
stepsreadonly AIGenerationStep[]All step results
finalStateAgentLoopStateFinal loop state

Backward Compatibility

The deprecated onStepFinish callback still works. It fires at the same point as onStepEnd. Prefer onStepEnd for new code.

/** @deprecated Use onStepEnd instead */
onStepFinish?: (state: AgentLoopState, latest: AgentStepResult) => void | Promise<void>;

On this page