runtrace

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package runtrace persists a case-scoped agent run as JobRunLog + JobRunEvent records so the case agent page can list it and show its per-call timeline.

It is the single home for the gollem trace.Handler that turns LLM / tool call boundaries into JobRunEvent rows, shared by the Job runner (pkg/usecase/job) and the mention hosts (pkg/usecase/agent/casebound and .../threadcase). The Job runner drives its own JobRunLog lifecycle (lease, suspend/resume, reflection) and uses only Handler + Sequencer here; the mention hosts, which have no such lifecycle, use Recorder for the whole open/close.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Truncate

func Truncate(s string, max int) string

Truncate returns s capped at max bytes, snapped back to a UTF-8 rune boundary so the result never contains a partial multi-byte character. Firestore rejects strings that are not valid UTF-8, and a blind byte-slice can leave a trailing fragment of a JP/CJK rune which would make the doc unwritable.

Types

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler is a gollem trace.Handler that appends one JobRunEvent per LLM call (LLM_REQUEST + LLM_RESPONSE) and one per tool execution (TOOL_CALL). It is wired once per run via gollem.WithTrace(handler) (or planexec's RunRequest.TraceHandler) and shares its Sequence allocator with the run's owner so that RUN_ERROR emits ordering-consistent with the per-call events.

func NewHandler

func NewHandler(
	eventRepo interfaces.JobRunEventRepository,
	routing Routing,
	seq *Sequencer,
	clock func() time.Time,
) *Handler

NewHandler constructs a Handler bound to the given run. clock defaults to time.Now().UTC() when nil.

func (*Handler) AddEvent

func (h *Handler) AddEvent(ctx context.Context, kind string, data any)

AddEvent is a no-op; reserved for future debug-event payloads.

func (*Handler) EmitRunError

func (h *Handler) EmitRunError(ctx context.Context, stage, message string) error

EmitRunError appends a RUN_ERROR event using the shared sequencer. Called by the run's owner on lifecycle failures (prepare / execute / finish stages).

func (*Handler) EndAgentExecute

func (h *Handler) EndAgentExecute(ctx context.Context, err error)

EndAgentExecute is a no-op for the same reason.

func (*Handler) EndChildAgent

func (h *Handler) EndChildAgent(ctx context.Context, err error)

EndChildAgent mirrors EndSubAgent.

func (*Handler) EndLLMCall

func (h *Handler) EndLLMCall(ctx context.Context, data *trace.LLMCallData, err error)

EndLLMCall appends two events: LLM_REQUEST built from data.Request + data.Model, then LLM_RESPONSE built from data.Response + tokens + elapsed wall-clock. If err is non-nil the response event still captures whatever partial data was returned; the surrounding run failure is recorded separately via EmitRunError.

func (*Handler) EndSubAgent

func (h *Handler) EndSubAgent(ctx context.Context, err error)

EndSubAgent clears the AgentLabel back to empty.

func (*Handler) EndToolExec

func (h *Handler) EndToolExec(ctx context.Context, result map[string]any, err error)

EndToolExec appends a single TOOL_CALL event with ParentSequence pointing at the most recent LLM_RESPONSE seen by this handler.

func (*Handler) EnterReflectionPhase

func (h *Handler) EnterReflectionPhase()

EnterReflectionPhase relabels subsequent events as the reflection phase. The Job runner calls it once, after the executor returns and before invoking the reflector, so the reflection agent's LLM / tool events are attributed to "reflection". Safe because the run is single-threaded at that point.

func (*Handler) Finish

func (h *Handler) Finish(ctx context.Context) error

Finish is a no-op: events have been appended per-call. The hook is kept for trace.Handler contract compatibility.

func (*Handler) StartAgentExecute

func (h *Handler) StartAgentExecute(ctx context.Context) context.Context

StartAgentExecute is a no-op: run lifecycle is tracked by JobRunLog, not the per-event timeline.

func (*Handler) StartChildAgent

func (h *Handler) StartChildAgent(ctx context.Context, name string) context.Context

StartChildAgent mirrors StartSubAgent for the child-agent variant.

func (*Handler) StartLLMCall

func (h *Handler) StartLLMCall(ctx context.Context) context.Context

StartLLMCall records the start time in a context-scoped span. No event is appended yet; we have no LLMCallData until End.

func (*Handler) StartSubAgent

func (h *Handler) StartSubAgent(ctx context.Context, name string) context.Context

StartSubAgent flags the handler so subsequent events carry the named AgentLabel. The single-loop path never triggers this; planexec sub-agents do.

func (*Handler) StartToolExec

func (h *Handler) StartToolExec(ctx context.Context, toolName string, args map[string]any) context.Context

StartToolExec caches the tool name + args + start timestamp on a context-scoped span. No event is appended yet; we need the result before we can emit a complete TOOL_CALL.

type OpenParams

type OpenParams struct {
	Repo         interfaces.Repository
	WorkspaceID  string
	CaseID       int64
	JobID        string // fresh per-turn id for a mention run; a config id for a Job
	RunID        string
	TraceID      string
	EventType    string // provenance, e.g. model.EventTypeMention
	ExecutorKind string // model.ExecutorKindSingleLoop / ExecutorKindPlanexec
	SystemPrompt string
	StartedAt    time.Time
	// Clock supplies wall-clock time for the per-event handler and the Close
	// timestamp. nil → time.Now().UTC(). Tests inject a fixed clock.
	Clock func() time.Time
}

OpenParams collects the inputs Open needs. The caller owns RunID / TraceID so it can align the TraceID with its own durable trace recorder (the Cloud Storage archive), keeping both trace sinks correlated.

type Recorder

type Recorder struct {
	// contains filtered or unexported fields
}

Recorder manages the JobRunLog lifecycle for a case-scoped agent run that does NOT go through the Job runner — a Slack mention handled by the casebound / threadcase hosts. It is the mention-side counterpart to the lifecycle the Job runner drives inline: Open creates the RUNNING JobRunLog and the per-event Handler, and Finish transitions the log to its terminal stage and materialises the JobRun summary doc (so ListByCase surfaces the run on the case agent page).

The parent JobRun doc is materialised at Finish (via RecordRun), not at Open: the mention hosts serialise concurrent turns through their own per-thread session lock, so Recorder must not take the Job lease (that would falsely exclude a concurrent mention on a different thread of the same case). A run therefore surfaces in the list at Finish.

Because each mention turn uses its OWN fresh JobID, its parent JobRun doc exists only once that turn Finishes. A hard process kill between Open and Finish therefore leaves a RUNNING log whose parent JobRun doc was never written, so ListByCase never returns it and the orphan stays invisible — no perpetual-RUNNING row pollutes the list. This is an accepted edge case: the deferred Finish runs on normal returns and on panics recovered by async.Dispatch, so only an abrupt process death (SIGKILL / OOM) can reach it.

func Open

func Open(ctx context.Context, p OpenParams) (*Recorder, error)

Open creates the RUNNING JobRunLog and returns a Recorder whose Handler the caller wires into gollem (or planexec's RunRequest.TraceHandler). A failure here is returned to the caller, which treats it as non-fatal (the turn still runs, just untraced) — creating the log is observability, not part of the turn's success contract.

func (*Recorder) Finish

func (r *Recorder) Finish(ctx context.Context, execErr error)

Finish transitions the run's JobRunLog to its terminal stage and materialises the JobRun summary doc so the run surfaces in ListByCase. execErr nil → SUCCESS; non-nil → FAILED (and a RUN_ERROR event is appended). Every persistence failure is non-fatal (errutil.Handle): the trace is observability, so a trace write failing must not fail the caller's turn.

It is deliberately named Finish, not Close: this ends a run record, it does not close an io.Closer resource (safe.Close is for those).

func (*Recorder) Handler

func (r *Recorder) Handler() *Handler

Handler returns the per-event trace handler. The caller wires it into the gollem agent (casebound) or planexec (threadcase) so LLM / tool calls stream into the JobRunEvent timeline.

type Routing

type Routing struct {
	WorkspaceID string
	CaseID      int64
	JobID       string
	RunID       string
	TraceID     string
}

Routing carries the immutable identifiers stamped on every JobRunEvent emitted by a Handler instance. Captured at construction time so individual hook calls do not need to thread them through.

type Sequencer

type Sequencer struct {
	// contains filtered or unexported fields
}

Sequencer hands out monotonically increasing JobRunEvent.Sequence values for a single run. The run's owner shares the SAME pointer with the Handler so that RUN_ERROR appends emitted by the owner and per-call appends emitted by the handler never collide on Sequence.

func NewSequencer

func NewSequencer() *Sequencer

NewSequencer returns a sequencer whose first Next() returns 1.

func NewSequencerStartingAt

func NewSequencerStartingAt(start int64) *Sequencer

NewSequencerStartingAt returns a sequencer whose first Next() returns start. Used when resuming a suspended run so the resumed turn's events continue past the suspended turn's events (which share the same RunID event space) instead of colliding on Sequence.

func (*Sequencer) Next

func (s *Sequencer) Next() int64

Next returns the next Sequence and advances the counter.

Jump to

Keyboard shortcuts

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