Seshat AIDocumentation

Documentation / SDK and API

Streaming, events and hooks

Show progress while the agent works, answer permission questions from your own interface, and plug code into the lifecycle.

SubmitMessage waits for the whole turn. To build an interface, you listen while it runs. The SDK gives you three hooks into a running agent: callbacks, a prompt bridge and lifecycle hooks.

Where each signal falls

During one turn, the loop and the session emit hooks and events at fixed points:

A turn is requestedThe loop startsAn iteration beginsrepeats while the model asks for toolsCompaction is checkedbefore every model callThe model answersstreamedTools are about to runEach tool call, beforeThe tool runsthe call itselfEach tool call, afterThe tools are doneThe iteration endsThe turn endsturn_startcan refuse the turnquery_startobserve onlyiteration_startcan stop the turnpre_compacta deny skips this compaction;post_compact only observestool_uses_startcan stop the turnpre_tool_usecan stop the turnor rewrite the tool inputshell hook before the callcan deny, rewrite or halt the turnpost_tool_usecan stop the turntool_uses_completeobserve onlyiteration_completeobserve onlyquery_complete, turn_endobserve onlyturn.startedturn.stage while the turnis being preparedresponse.chunkone event per piece of the answertool.permission_requiredwhen a tool needs your approvaltool.progressthe stages of the callturn.completedor turn.failed; thencontext.compacted if it compactedLIFECYCLE HOOKSRUNTIME EVENTS
Hooks (left) run your code. The ones marked can stop the turn, the others observe. Events (right) only let you observe. Time flows downwards.

Hooks run your code and can stop the action. Events are for observing and are sent to your callbacks. Streaming text, tool progress and runtime events are described below, then lifecycle hooks.

Streaming text

Set a callback on the client or on a session. It receives each piece of the answer as it arrives.

session.SetResponseChunkFn(func(c sdk.ResponseChunk) {
    fmt.Print(c.Delta)
})

resp, err := session.SubmitMessage(ctx, "Explain this repository")

ResponseChunk has a Type (content_block_start, content_block_delta, content_block_stop, message_delta, message_stop, error) and a Delta with the text. Thinking and tool input arrive as other delta types, described by DeltaType and PartialJSON.

Tool progress

session.SetProgressFn(func(p sdk.ToolProgress) {
    fmt.Println("tool:", p)
})

Use it to show “running bash…” lines or a progress bar while a tool works.

Runtime events

One callback receives structured events for the whole turn: when it starts and ends, chunks, tool progress, permission questions, plans, tasks, goals, sub-agents and compaction.

session.SetRuntimeEventFn(func(e sdk.RuntimeEvent) {
    switch e.Type {
    case sdk.RuntimeEventTypeTurnCompleted:
        fmt.Println("done after", e.TurnNumber, "turns")
    case sdk.RuntimeEventTypeCompaction:
        // the context was compacted during this turn
    }
})

The runtime emits these events:

EventWhen
turn.started, turn.completed, turn.failedA turn begins and ends
response.chunkEach piece of the answer (the same data as the streaming callback)
turn.stageA phase of the turn setup, before the first chunk (preparing_turn)
tool.progressThe stages of a tool call
tool.permission_requiredA tool needs approval, just before your prompt function is called
prompt.requestThe agent asks you a question, just before your prompt function is called
context.compactedAfter a turn that compacted the context
plan.submitted, execution_mode.changedPlan mode
task.changed, goal.updatedTasks and goals
agent.spawn.*, agent.wait.*, agent.interaction.*, agent.close.*Sub-agents
browser.*The browser tools

Events from a sub-agent carry AgentToolUseID, which identifies the tool call that started it.

Answering permission questions

When a tool needs approval and the mode is onRequest, the runtime calls your prompt function. Without one, the action is not approved.

client.SetPromptFn(func(ctx context.Context, req sdk.PromptRequest) (sdk.PromptResponse, error) {
    if req.Type == "confirm" {
        tool, _ := req.Metadata["tool_name"].(string)
        ok := askUser("Allow " + tool + "?")   // your own UI
        return sdk.PromptResponse{Value: ok}, nil
    }
    return sdk.PromptResponse{Cancelled: true}, nil
})

For a permission question, return Value: true to allow it once, Value: "always" to allow that tool for the session, or Cancelled: true to refuse. Metadata contains tool_name, tool_input, tool_use_id and working_directory.

Lifecycle hooks

Three kinds of hooks run your code inside the runtime.

Loop hooks

RegisterHook attaches a handler to one event of the loop. The handler receives the session and turn identifiers and event data in Data.

id, err := client.RegisterHook(sdk.HookEventIterationStart,
    func(ctx context.Context, p sdk.HookProgress) (*sdk.HookResult, error) {
        if overBudget(p.Data) {
            return &sdk.HookResult{Action: "stop", Message: "budget reached"}, nil
        }
        return nil, nil // continue
    })
if err != nil {
    log.Fatal(err)
}

client.HookRegistry().Remove(id)

Returning nil, nil means continue. RegisterHook returns an error for an event the runtime never emits, so a handler that could never run is refused instead of being accepted in silence.

The runtime emits 19 events:

EventWhenA stop or deny
session_start, session_endA session is created, and closedObserve only
turn_startA turn is requested, before your message is acceptedRefuses the turn, with an error
query_startThe loop startsObserve only
iteration_startEach iteration, before the model callEnds the turn
pre_compactBefore an automatic compactionSkips that compaction, the turn goes on
post_compactAfter a compactionObserve only
tool_uses_startThe model asked for tools, before they runEnds the turn
pre_tool_useBefore each tool callEnds the turn. A modify result rewrites the tool input (UpdatedInput)
post_tool_useAfter each tool call, with its success or errorEnds the turn (the tools already ran)
tool_uses_completeThe tool results were addedObserve only
iteration_stop, iteration_continue, iteration_completeAn iteration endsObserve only
query_complete, turn_endA turn endsObserve only
subagent_start, subagent_stop, on_errorAround a sub-agentObserve only

Where a stop is not possible, the handler still runs, so use those events to observe and log. A retry result has no effect: the turn goes on as if the handler had returned continue. The SDK defines more event names than these 19, but RegisterHook refuses them because the runtime never emits them.

Shell hooks before a tool call

To check or rewrite a tool call before it runs, set PreToolHooks in the config. Each entry has a Matcher (a regular expression on the tool name), a Command and a Timeout. The command receives the tool input as JSON and answers with its exit code:

ResultEffect
Exit 0The call goes ahead
Exit 0 with {"decision":"allow"}The call goes ahead and skips the permission question
Exit 0 with {"updatedInput": ...}The tool input is rewritten
Exit 2, message on stderrThe call is denied, with that reason
Exit 49The whole turn is halted

Stop hooks

A stop hook runs when the model is about to stop. It can add messages and ask for one more cycle of the loop. Set them with StopHooks in ClientConfig.

Updated on 2026-10-13