event

package
v0.2.9-alpha.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 24, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package event defines the event stream the agent emits while running.

The Event envelope is a discriminated union — every event has a Kind and exactly one non-nil typed payload field. This keeps consumer code type-safe (no interface{} assertions, no reflection) while still allowing one Sink to receive every kind of event the agent might emit.

State-change events from backing stores (task list, subagent panel, future notes/todos/...) all flow through a single KindStoreUpdate so adding a new panel never requires a new event kind. The store's domain identifier (see internal/observable.Change.Domain) selects how the consumer renders the row.

Sinks (see sink.go) are the consumer side. A TUI, a structured logger, and a JSON-over-websocket bridge can each implement Sink and subscribe independently of one another — the agent doesn't know about them.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ApprovalNeededPayload

type ApprovalNeededPayload struct {
	// RequestID is the Broker correlation key; the TUI passes it back to
	// Broker.Respond when forwarding the user's choice.
	RequestID string
	// ToolName is the wire name of the tool whose call is being gated.
	ToolName string
	// ToolInput is the raw JSON the LLM passed to the tool.
	ToolInput json.RawMessage
	// InputDescription is the model-supplied `description` field from
	// ToolInput; "" when the tool's input has no such field.
	InputDescription string
	// Mode is the permission mode active when the gate fired.
	Mode string
	// Reason is the gate's explanation for asking (e.g. "matches dangerous prefix").
	Reason string
	// RiskHint is non-empty for Bash (the classifier's risk label);
	// empty for other tools.
	RiskHint string
	// Matched is the rule fragment that triggered the prompt, if any.
	Matched string
	// PlanContent is non-empty only for ExitPlanMode — carries the
	// markdown plan body so the approval overlay can render it inline.
	PlanContent string
}

ApprovalNeededPayload is the wire shape of a pending permission prompt. The TUI receives one of these per blocked tool call. Carries every piece of context the user needs to decide: the tool name, the raw input (UI summarises), the active mode, and the gate's reason for asking.

RequestID is the Broker's correlation key — the TUI uses it when calling Broker.Respond. RiskHint is non-empty for Bash; other tools see "". PlanContent is non-empty only for ExitPlanMode — carries the markdown plan body so the approval overlay can render it inline.

type BgResultPayload

type BgResultPayload struct {
	// TaskID is the wire-stable id (e.g. "b4x9z1kp") the model received
	// from the original `bash run_in_background:true` call.
	TaskID string
	// Status is the terminal lifecycle state: completed / failed / killed.
	Status string
	// ExitCode is the process exit code (0 for completed; non-zero for
	// failed; -1 or os-defined for killed).
	ExitCode int
	// Output is the captured stdout+stderr, capped at the bg path's
	// output ceiling (~64 KiB).
	Output string
	// AgentID is the spawning agent — copied so subagent bubble-up can
	// label rows without inferring from event metadata.
	AgentID string
}

BgResultPayload reports one background bash task's terminal outcome. Emitted from the agent's signal pump regardless of loop idle/busy state — the conversation-side fold-in happens separately via KindDrainBackgroundTask. The TUI uses BgResultPayload to render the "task-xxx completed." transcript line and update the bg-tasks strip.

type BubbleUp

type BubbleUp struct {
	Parent   Sink
	ParentID string
}

BubbleUp wraps a parent's Sink so a subagent's events appear in the parent's stream with ParentID set to the parent's AgentID. The TUI uses this tagging to route nested events into a subagent panel.

BubbleUp does NOT change the subagent's own AgentID — only ParentID. The hierarchy is always exactly two layers (subagents cannot spawn subagents), so a single rewrite at the boundary is enough.

func (BubbleUp) Emit

func (b BubbleUp) Emit(e Event)

Emit rewrites the event's ParentID and forwards.

type CompactingEndPayload

type CompactingEndPayload struct {
	// Type matches the prior CompactingPayload.Type — "micro" or "full".
	Type string
	// OK reports success or failure; false means the TUI should swap
	// the spinner for an error line rather than removing it silently.
	OK bool
	// BriefTokens is the size of the full-compact brief; updated UIs use
	// this to replace the percent estimate they painted at start.
	BriefTokens int
	// Err is the failure reason when OK is false; empty otherwise.
	Err string
}

CompactingEndPayload reports the outcome of a compaction the TUI was painting. OK=false marks the failure path so the transcript can swap the spinner block for a short error line instead of just removing it. BriefTokens carries the size of the full-compact brief so callers that already painted a percent can update the figure on completion.

type CompactingPayload

type CompactingPayload struct {
	// Type is "micro" (elide old tool results) or "full" (summarise).
	Type string
	// UsageRatio is the input/budget ratio that triggered the compaction.
	UsageRatio float64
}

CompactingPayload reports the start of a session compaction. Type is "micro" or "full"; UsageRatio is the input/output token ratio that triggered the compaction.

type DrainBackgroundTaskPayload

type DrainBackgroundTaskPayload struct {
	// TaskIDs are the ids drained on this iteration boundary, in
	// completion order.
	TaskIDs []string
}

DrainBackgroundTaskPayload reports the batch of bg-task ids the agent loop folded into the session as a synthetic user message on the current iteration boundary. Used by debug telemetry / log inspectors; the TUI does not render this directly (the per-result KindBgResult already rendered the chip transition).

type DrainMonitorEventsPayload

type DrainMonitorEventsPayload struct {
	// EventCount is the total number of monitor events folded into the
	// session on this iteration boundary.
	EventCount int
	// MonitorIDs are the unique monitor ids the drained events came
	// from, in first-occurrence order.
	MonitorIDs []string
}

DrainMonitorEventsPayload reports the batch of monitor events the agent loop folded into the session as a synthetic user message. EventCount is the total number of streamed lines drained (events from multiple monitors are interleaved by arrival time).

type ErrorPayload

type ErrorPayload struct {
	// Stage tags where in the loop the failure occurred:
	// "llm" | "tool:<name>" | "loop".
	Stage string
	// Err is the underlying Go error. Use Message for the rendered
	// string form when you only need text (covers the nil case too).
	Err error
	// Message is err.Error() captured at emit time, or "" when Err is nil.
	// Convenience for consumers that don't want to nil-check + stringify
	// (UIs, JSON serialisers).
	Message string
}

ErrorPayload reports a Go-level failure that aborted the loop. Tool errors surfaced as Result.IsError do NOT produce this event — they flow through ToolUseResult so the model can recover.

type Event

type Event struct {
	Kind     Kind
	AgentID  string
	ParentID string
	Time     time.Time

	RunStart       *RunStartPayload       `json:",omitempty"`
	RunResume      *RunResumePayload      `json:",omitempty"`
	RunEnd         *RunEndPayload         `json:",omitempty"`
	IterLimit      *IterLimitPayload      `json:",omitempty"`
	Turn           *TurnPayload           `json:",omitempty"`
	Thinking       *TextPayload           `json:",omitempty"`
	Text           *TextPayload           `json:",omitempty"`
	ToolUseStart   *ToolUseStartPayload   `json:",omitempty"`
	ToolUseResult  *ToolUseResultPayload  `json:",omitempty"`
	ApprovalNeeded *ApprovalNeededPayload `json:",omitempty"`
	QuestionNeeded *QuestionNeededPayload `json:",omitempty"`
	Error          *ErrorPayload          `json:",omitempty"`
	StoreUpdate    *StoreUpdatePayload    `json:",omitempty"`
	Usage          *UsagePayload          `json:",omitempty"`
	Compacting     *CompactingPayload     `json:",omitempty"`
	CompactingEnd  *CompactingEndPayload  `json:",omitempty"`
	ModeChanged    *ModeChangedPayload    `json:",omitempty"`

	BgResult            *BgResultPayload            `json:",omitempty"`
	MonitorEvent        *MonitorEventPayload        `json:",omitempty"`
	DrainBackgroundTask *DrainBackgroundTaskPayload `json:",omitempty"`
	DrainMonitorEvents  *DrainMonitorEventsPayload  `json:",omitempty"`
}

Event is the envelope. Exactly one of the *Payload fields is non-nil per event, matched to Kind. Consumers should switch on Kind and read the corresponding field directly — type-safe access, no reflection.

AgentID identifies the emitter. ParentID is empty for the root agent and equal to the root's AgentID for subagent events (the hierarchy is always exactly two layers — subagents cannot spawn subagents).

func (Event) Payload

func (e Event) Payload() any

Payload returns the payload pointer matching e.Kind, or nil when the event Kind has no associated payload (KindIdle, KindRunCancelled, KindTurnStart/End in some emitters, etc.). Consumers can switch on the returned type instead of remembering which of the 20+ pointer fields on Event corresponds to each Kind:

switch p := e.Payload().(type) {
case *event.TextPayload:
    render(p.Text)
case *event.ToolUseStartPayload:
    renderToolCall(p.Name, p.Input)
}

The direct field access (e.Text, e.ToolUseStart, …) stays available for callers that already do a Kind switch — Payload is purely an ergonomics layer.

Example

ExampleEvent_Payload demonstrates the Phase 19a Payload() helper — type-switch on the returned pointer instead of remembering which Event.* field goes with each Kind.

package main

import (
	"fmt"

	"github.com/johnny1110/evva/pkg/event"
)

func main() {
	events := []event.Event{
		{Kind: event.KindText, Text: &event.TextPayload{Text: "done"}},
		{Kind: event.KindToolUseStart, ToolUseStart: &event.ToolUseStartPayload{Name: "read"}},
		{Kind: event.KindRunEnd, RunEnd: &event.RunEndPayload{Iters: 3}},
	}
	for _, e := range events {
		switch p := e.Payload().(type) {
		case *event.TextPayload:
			fmt.Println("text:", p.Text)
		case *event.ToolUseStartPayload:
			fmt.Println("tool:", p.Name)
		case *event.RunEndPayload:
			fmt.Println("done in", p.Iters, "iters")
		}
	}
}
Output:
text: done
tool: read
done in 3 iters

type IterLimitPayload

type IterLimitPayload struct {
	// Iters is the iteration count the loop hit before pausing — matches
	// RunEndPayload.Iters naming so callers can read iteration counts
	// from either payload without remembering two field names.
	Iters int
}

IterLimitPayload is emitted when the loop hits Agent.maxIters. The UI should prompt the user (e.g. "press Enter to keep going") and call Agent.Continue to resume; the loop is paused, not failed.

type Kind

type Kind string

Kind tags every event. New kinds are added by extending this list and the matching payload field on Event.

const (
	// KindIdle marks the agent as inactive — no Run in flight. Useful for
	// status-bar widgets that want a "ready" indicator distinct from "running".
	KindIdle Kind = "idle"
	// KindRunStart fires once at the top of every Agent.Run invocation;
	// payload carries the user prompt that kicked off the run.
	KindRunStart Kind = "run_start"
	// KindRunResume fires when Agent.Continue resumes after an iter-limit
	// pause; payload carries the message index the resume picks up from.
	KindRunResume Kind = "run_resume"
	// KindRunEnd fires once per Run — terminal, win or lose; payload
	// carries the final iteration count and assistant text/thinking.
	KindRunEnd Kind = "run_end"
	// KindRunCancelled fires when context cancellation tore the run down
	// mid-flight (Ctrl-C, deadline, etc.). No payload.
	KindRunCancelled Kind = "run_cancelled"
	// KindIterLimit fires when the loop hits Agent.maxIters and pauses
	// without finishing. The caller may invoke Agent.Continue to resume.
	KindIterLimit Kind = "iter_limit"

	// KindTurnStart / KindTurnEnd bracket one iteration of the loop. Payload
	// carries the iteration index so subscribers can scope sub-events to a turn.
	KindTurnStart Kind = "turn_start"
	KindTurnEnd   Kind = "turn_end"

	// KindDrainingInfo signals the agent is folding deferred information
	// from subagents or background bash into the parent context. Cosmetic
	// — useful for a status bar "draining…" hint.
	KindDrainingInfo = "draining_info"
	// KindThinking carries the assistant's reasoning text as one full
	// block (buffered providers). Streaming providers emit KindThinkingChunk
	// deltas instead, then skip the final KindThinking to avoid duplication.
	KindThinking Kind = "thinking"
	// KindText carries the assistant's final text as one full block
	// (buffered providers). Streaming providers emit KindTextChunk deltas
	// instead, then skip the final KindText.
	KindText Kind = "text"

	// KindTextChunk and KindThinkingChunk are emitted by the streaming
	// path. Each carries an incremental delta in TextPayload.Text; the
	// UI accumulates consecutive chunks of the same kind into one logical
	// block. Reset on KindTurnEnd. Streaming agents emit chunks only —
	// the final KindText / KindThinking is skipped to avoid duplication.
	KindTextChunk     Kind = "text_chunk"
	KindThinkingChunk Kind = "thinking_chunk"

	// KindToolUseStart fires at tool-dispatch time, carrying the tool name
	// + raw JSON input. The matching KindToolUseResult follows when the
	// tool returns.
	KindToolUseStart Kind = "tool_use_start"
	// KindToolUseResult fires when a tool returns. Pairs with the prior
	// KindToolUseStart by ToolID.
	KindToolUseResult Kind = "tool_use_result"

	// KindApprovalNeeded is emitted when the permission gate decides a tool
	// call must be approved by the user. The TUI subscribes, opens an
	// approval overlay, and calls Broker.Respond with the user's decision.
	// The blocked tool goroutine sleeps in Broker.Request until the answer
	// arrives (or the context is cancelled).
	KindApprovalNeeded Kind = "approval_needed"

	// KindQuestionNeeded is emitted when the AskUserQuestion tool is invoked.
	// The TUI subscribes, opens a question overlay, and calls
	// Controller.RespondQuestion with the user's answers. The blocked tool
	// goroutine sleeps in question.Broker.Request until the answer arrives.
	KindQuestionNeeded Kind = "question_needed"

	// KindCompacting fires when the agent starts a session compaction
	// (micro or full). Pairs with KindCompactingEnd.
	KindCompacting Kind = "compacting"
	// KindCompactingEnd pairs with KindCompacting; OK reports success or
	// failure so the TUI can swap the spinner for the right final block.
	KindCompactingEnd Kind = "compacting_end"

	// KindError reports a Go-level failure that aborted the loop. Tool
	// errors surfaced via Result.IsError do NOT produce this event —
	// they flow through KindToolUseResult so the model can recover.
	KindError Kind = "error"

	// KindStoreUpdate carries every state change emitted by an
	// observable.Store registered on the agent's ToolState. The consumer
	// switches on StoreUpdatePayload.Domain to decide how to render.
	KindStoreUpdate Kind = "store_update"

	// KindUsage reports per-turn token usage plus the running session
	// total after the turn is folded in.
	KindUsage Kind = "usage"

	// KindModeChanged fires whenever the agent's permission mode changes
	// — Shift+Tab cycle, EnterPlanMode / ExitPlanMode tool calls, or a
	// SwitchProfile that resets the mode. Lets the TUI sync the status-
	// bar indicator without having to poll Agent.PermissionMode each
	// render. Emitted only by the root agent; subagent mode changes
	// stay internal.
	KindModeChanged Kind = "mode_changed"

	// KindBgResult fires when a background bash task transitions to a
	// terminal state (completed / failed / killed). Emitted whether the
	// agent loop is busy or idle — the TUI uses this to render the
	// "task-xxx completed." transcript line. Loop-side drain folds the
	// result into the conversation separately via KindDrainBackgroundTask.
	KindBgResult Kind = "bg_result"

	// KindMonitorEvent fires for every stdout line a running MonitorTool
	// streams plus the closing event when the monitor stops. The TUI
	// renders these inline in the transcript; loop-side drain folds the
	// queued events into the conversation via KindDrainMonitorEvents.
	KindMonitorEvent Kind = "monitor_event"

	// KindDrainBackgroundTask fires at the moment the agent loop folds
	// drained background-task results into the session as a synthetic
	// user message. Payload carries the task ids that were folded in.
	KindDrainBackgroundTask Kind = "drain_background_task"

	// KindDrainMonitorEvents fires at the moment the agent loop folds
	// queued monitor events into the session. Payload carries the line
	// count drained (events from multiple monitors are interleaved).
	KindDrainMonitorEvents Kind = "drain_monitor_events"
)

type ModeChangedPayload

type ModeChangedPayload struct {
	// PrevMode is the mode that was active before the change. Empty on the
	// very first initialization.
	PrevMode string
	// Mode is the new mode the agent has transitioned into.
	Mode string
}

ModeChangedPayload reports a permission-mode transition. PrevMode is the mode that was active before the change (empty on the very first initialization); Mode is the new mode. Both are the wire string form (permission.Mode is type-aliased to string for the same reason).

type MonitorEventPayload deprecated

type MonitorEventPayload struct {
	// MonitorID is the wire-stable id (e.g. "m4x9z1kp") the model
	// received from the original Monitor call.
	MonitorID string
	// Line is one stdout line from the monitored command (newline
	// stripped). Empty when Closing is true.
	Line string
	// Closing reports whether this is the last event for the monitor
	// (process exited or daemon_stop fired).
	Closing bool
	// AgentID is the spawning agent.
	AgentID string
}

MonitorEventPayload reports one streamed line from a running Monitor. Closing=true marks the final event when the underlying process exits or daemon_stop is called; the TUI strip flips the monitor chip to Stopped on the closing event.

Deprecated: monitor lifecycle now flows through KindStoreUpdate on the daemon Observable. Kept for transcript renderer back-compat until that surface is rewritten.

type Multi

type Multi struct {
	Sinks []Sink
}

Multi fans out one event to many sinks in declared order. A slow sink blocks subsequent ones (and the agent loop). This is intentional — backpressure beats event loss.

Example

ExampleMulti shows how to fan one event to several sinks — useful for wiring a TUI plus a structured log at the same time.

package main

import (
	"fmt"

	"github.com/johnny1110/evva/pkg/event"
)

func main() {
	var tuiTexts, logTexts []string
	tui := event.SinkFunc(func(e event.Event) {
		if e.Text != nil {
			tuiTexts = append(tuiTexts, e.Text.Text)
		}
	})
	logger := event.SinkFunc(func(e event.Event) {
		if e.Text != nil {
			logTexts = append(logTexts, e.Text.Text)
		}
	})

	fanout := event.Multi{Sinks: []event.Sink{tui, logger}}
	fanout.Emit(event.Event{Kind: event.KindText, Text: &event.TextPayload{Text: "broadcast"}})

	fmt.Println("tui:", tuiTexts)
	fmt.Println("log:", logTexts)
}
Output:
tui: [broadcast]
log: [broadcast]

func (Multi) Emit

func (m Multi) Emit(e Event)

Emit forwards to every contained sink, skipping nil entries.

type QuestionItem

type QuestionItem struct {
	// Question is the prompt body.
	Question string
	// Header is the short chip label (max 12 chars in the canonical UI).
	Header string
	// MultiSelect controls whether the user may pick more than one option.
	MultiSelect bool
	// Options are the offered choices.
	Options []QuestionOption
}

QuestionItem mirrors question.Question for the event layer so event.go does not import internal/question (which would create a cycle through toolset → tools/ux → question → event).

type QuestionNeededPayload

type QuestionNeededPayload struct {
	// RequestID is the question.Broker correlation key; the TUI passes
	// it back via Controller.RespondQuestion.
	RequestID string
	// AgentID is the agent that invoked AskUserQuestion (relevant when
	// subagent question routing lands).
	AgentID string
	// Questions are the items rendered in the overlay.
	Questions []QuestionItem
}

QuestionNeededPayload is the wire shape of a pending question prompt. The TUI receives one of these when AskUserQuestion is invoked. RequestID is the question.Broker's correlation key used when calling Controller.RespondQuestion.

type QuestionOption

type QuestionOption struct {
	// Label is the choice text shown to the user.
	Label string
	// Description is the optional explanation rendered alongside.
	Description string
	// Preview is the optional code/diagram block shown in side-by-side mode.
	Preview string
}

QuestionOption mirrors question.Option for the same reason.

type RunEndPayload

type RunEndPayload struct {
	// Iters is the number of iterations the loop consumed before ending.
	Iters int
	// Content is the assistant's final text for the Run.
	Content string
	// Thinking is the assistant's reasoning text (if any).
	Thinking string
}

RunEndPayload carries the final state of a completed Run.

type RunResumePayload

type RunResumePayload struct {
	// FromMessageIndex is the position in the session transcript where
	// Continue picked up.
	FromMessageIndex int
}

RunResumePayload carries the message index Agent.Continue resumed from after an iter-limit pause.

type RunStartPayload

type RunStartPayload struct {
	// Prompt is the user message that opened this Run.
	Prompt string
}

RunStartPayload carries the user prompt that kicked off a Run.

type Sink

type Sink interface {
	Emit(Event)
}

Sink consumes events emitted by an agent's run loop.

Concurrency: an agent serializes calls into Sink.Emit internally — even when tools dispatch in parallel, individual Emit calls are mutex-guarded so events from one agent arrive one at a time. Sinks shared across multiple agents (e.g. a global logger, or a parent sink reached through BubbleUp) must still handle concurrent Emit calls themselves.

Emit should be fast — slow sinks block the agent loop. Sinks needing network or disk I/O should buffer internally (channel, ring buffer) and process asynchronously.

var Discard Sink = discard{}

Discard is the no-op sink. Use as the default for tests / silent CLI runs where the caller doesn't subscribe to events.

type SinkFunc

type SinkFunc func(Event)

SinkFunc adapts an ordinary function to the Sink interface — convenient for one-off consumers (tests, quick CLI prints).

Example

ExampleSinkFunc shows the function-shaped sink pattern. The smallest possible implementation of event.Sink — wrap any func(Event) and pass it to agent.WithSink.

package main

import (
	"fmt"

	"github.com/johnny1110/evva/pkg/event"
)

func main() {
	sink := event.SinkFunc(func(e event.Event) {
		if e.Kind == event.KindText && e.Text != nil {
			fmt.Println("assistant:", e.Text.Text)
		}
	})
	sink.Emit(event.Event{Kind: event.KindText, Text: &event.TextPayload{Text: "hi"}})
}
Output:
assistant: hi

func (SinkFunc) Emit

func (f SinkFunc) Emit(e Event)

Emit calls the wrapped function.

type StoreUpdatePayload

type StoreUpdatePayload struct {
	// Domain identifies the emitting store ("task", "subagent", …).
	Domain string
	// Op is the verb: "created" / "updated" / "removed" / "phase" / "done" / "crushed".
	Op string
	// ID is the store-local identifier (task ID, subagent ID, …).
	ID string
	// Payload is the store's domain-typed snapshot; consumers type-assert
	// based on Domain.
	Payload any
	// Time is the emit timestamp.
	Time time.Time
}

StoreUpdatePayload is the bridge between observable.Change and the event stream. Domain names the emitting store ("task", "subagent", ...); Op is the verb ("created" / "updated" / "removed" / "phase" / "done" / "crushed"); Payload is the store's domain-typed snapshot, switched on by Domain at the consumer.

type TextPayload

type TextPayload struct {
	// Text is the assistant text content (a full block, or one streaming
	// delta when the event Kind is KindTextChunk / KindThinkingChunk).
	Text string
}

TextPayload carries an opaque text chunk — used for both Thinking and Text events. With streaming completions this becomes a stream of chunks; today it carries the full block.

type ToolUseResultPayload

type ToolUseResultPayload struct {
	// ToolID correlates this result with the prior ToolUseStart.
	ToolID string
	// Content is the LLM-facing text summary the tool produced.
	Content string
	// IsError is true when the tool itself returned an error result.
	// Distinct from a Go-level failure (which surfaces via KindError).
	IsError bool
	// Metadata is an optional tool-specific structured payload (e.g. a
	// *fs.FileDiff for write/edit). UIs type-assert to render rich views.
	Metadata any
	// ContentBlocks carries multimodal output (text + images). Empty for
	// text-only tool results.
	ContentBlocks []tools.ContentBlock
}

ToolUseResultPayload reports the outcome of a single tool call.

Metadata is an optional tool-specific structured payload (e.g. a *fs.FileDiff for write_file / edit_file). Carried opaquely through this layer; the UI type-asserts. Never sent to the LLM — Content alone is the model-facing summary.

type ToolUseStartPayload

type ToolUseStartPayload struct {
	// Name is the tool's wire name (matches tools.ToolName).
	Name string
	// Input is the raw JSON the LLM passed to the tool — UIs typically
	// summarise one field rather than dumping the whole blob.
	Input json.RawMessage
	// ToolID correlates this dispatch with its eventual ToolUseResult.
	ToolID string
}

ToolUseStartPayload reports a tool dispatch.

type TurnPayload

type TurnPayload struct {
	// Iteration is the zero-based loop iteration index.
	Iteration int
}

TurnPayload carries the iteration index a TurnStart/TurnEnd event refers to.

type UsagePayload

type UsagePayload struct {
	// Turn is usage for the just-completed LLM call.
	Turn llm.Usage
	// Cumulative is the running session total after Turn is folded in.
	Cumulative llm.Usage
}

UsagePayload reports token usage for the LLM call that just completed. Turn is the just-completed call; Cumulative is the running session total after Turn has been folded in. The TUI typically shows both — Turn for the latest cost spike, Cumulative for the session budget.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL