Callbacks

July 10, 2026 · View on GitHub

Callbacks let you observe or intercept the agent loop. Each callback runs as a local Conductor worker, so the function executes in your JVM while the workflow drives it. A callback returns:

  • an empty map (or null) to pass through unchanged, or
  • a non-empty map to override the value at that point in the loop.

There are two styles: composable CallbackHandler instances (recommended) and single-function builder callbacks.

CallbackHandler

Subclass CallbackHandler and override only the hooks you need. Register one or more handlers with .callbacks(...); they run in list order and the first non-empty return short-circuits.

import org.conductoross.conductor.ai.CallbackHandler;
import java.util.Map;

public class LoggingHandler extends CallbackHandler {
    @Override
    public Map<String,Object> onModelStart(Map<String,Object> kwargs) {
        System.out.println("→ LLM call: " + kwargs.get("messages"));
        return Map.of();          // pass through
    }

    @Override
    public Map<String,Object> onToolStart(Map<String,Object> kwargs) {
        System.out.println("→ tool: " + kwargs);
        return Map.of();
    }
}

Agent agent = Agent.builder()
    .name("observed_agent")
    .model("anthropic/claude-sonnet-4-6")
    .callbacks(new LoggingHandler())
    .build();

Hooks

MethodFiresWorker task
onAgentStart(Map)Before the agent's execution begins{name}_before_agent
onAgentEnd(Map)After the agent's execution finishes{name}_after_agent
onModelStart(Map)Before each LLM call{name}_before_model
onModelEnd(Map)After each LLM call{name}_after_model
onToolStart(Map)Before each tool call{name}_before_tool
onToolEnd(Map)After each tool call{name}_after_tool

Each method takes a Map<String,Object> and returns a Map<String,Object>. Only overridden methods are registered as workers.

Function-style callbacks

For one-off hooks without a class, use the function-typed builder methods. Each takes a Function<Map<String,Object>, Map<String,Object>>:

Agent agent = Agent.builder()
    .name("observed_agent")
    .model("anthropic/claude-sonnet-4-6")
    .beforeModelCallback(ctx -> { System.out.println("calling LLM: " + ctx.get("messages")); return ctx; })
    .afterModelCallback(ctx -> { System.out.println("LLM replied: " + ctx.get("output")); return ctx; })
    .beforeAgentCallback(ctx -> ctx)
    .afterAgentCallback(ctx -> ctx)
    .build();

Both styles serialize into a single callbacks list on the wire — the function objects are never sent; each becomes a Conductor task reference and the runtime registers your function as a local worker.