agentloop

package
v2.19.2 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: GPL-3.0 Imports: 14 Imported by: 0

Documentation

Overview

Package agentloop implements the decision layer of an agent DAG: the catalog of actions offered to the LLM, the goal state it works against, and the planner that turns a conversation into the next action.

Index

Constants

View Source
const (
	// SetTaskStatusTool is the name of the tool the agent calls to record
	// where a task stands. It is reserved and cannot name a step.
	SetTaskStatusTool = "set_task_status"

	// AskUserTool is the name of the tool the agent calls to put a question
	// to a person. It is reserved and cannot name a step.
	AskUserTool = "ask_user"
)
View Source
const (
	// EventAction is one run of a declared step.
	EventAction = "action"
	// EventTaskStatus records the agent settling, or reopening, a task.
	// The new status is carried on the event.
	EventTaskStatus = "task_status"
	// EventAskUser is a question the agent put to a person.
	EventAskUser = "ask_user"
	// EventRejected is a tool call the agent could not carry out.
	EventRejected = "rejected"
	// EventStalled is a turn where the model declined to act.
	EventStalled = "stalled"
)

Event kinds recorded on the agent's decision timeline.

Variables

This section is empty.

Functions

func MergeParams

func MergeParams(stepParams string, args map[string]any, pinned map[string]struct{}) string

MergeParams combines the parameters a step supplies itself with the arguments the agent chose, rendering both as the "key=value" string a child DAG run expects. A pinned parameter is passed through exactly as the step wrote it, and an argument naming one is dropped: it was never offered, so a model that invented it does not get to override the author.

func ParamString

func ParamString(args map[string]any) string

ParamString renders tool-call arguments as the "key=value" parameter string a child DAG run expects. Keys are sorted so the same arguments always produce the same child run ID.

func PinnedParams

func PinnedParams(step ir.Step) map[string]struct{}

PinnedParams lists the parameter names a step supplies itself.

func ValidTaskStatus

func ValidTaskStatus(value string) bool

ValidTaskStatus reports whether a value names a task status the agent may set. "open" is included: settling a task can be undone.

Types

type Catalog

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

Catalog is the set of actions an agent may choose from, expressed as LLM function-calling tools.

func NewCatalog

func NewCatalog(ctx context.Context, dag *ir.DAG) (*Catalog, error)

NewCatalog builds the tool catalog from an agent DAG's declared steps. A step that runs a child DAG advertises that DAG's parameters, so the agent can pass arguments through.

func (*Catalog) Definitions

func (c *Catalog) Definitions() []ir.ToolDefinition

Definitions returns the catalog in the form persisted for UI visibility.

func (*Catalog) StepFor

func (c *Catalog) StepFor(tool string) (string, bool)

StepFor resolves a tool name back to the step it runs. It returns false for CompleteTaskTool and for unknown names.

func (*Catalog) ToolNames

func (c *Catalog) ToolNames() []string

ToolNames lists every advertised tool name.

func (*Catalog) Tools

func (c *Catalog) Tools() []llmpkg.Tool

Tools returns the tool definitions to send to the model.

type Decision

type Decision struct {
	Kind       DecisionKind
	ToolCallID string
	ToolName   string
	// Step is the declared step to run, set when Kind is DecideRunStep.
	Step string
	// Args are the child DAG parameters, set when Kind is DecideRunStep.
	Args map[string]any
	// Task, TaskStatus and Reason are set when Kind is DecideSetTaskStatus.
	Task       string
	TaskStatus TaskStatus
	Reason     string
	// Question is set when Kind is DecideAskUser.
	Question string
	// Content is the model's prose, set when Kind is DecideStop.
	Content string
	// Problem describes why the decision was rejected, set when Kind is DecideInvalid.
	Problem string
}

Decision is one tool call the agent chose for this turn.

type DecisionKind

type DecisionKind int

DecisionKind classifies what the agent chose to do this turn.

const (
	// DecideRunStep runs one declared step.
	DecideRunStep DecisionKind = iota
	// DecideSetTaskStatus records where a task stands.
	DecideSetTaskStatus
	// DecideAskUser puts a question to a person and waits for the answer.
	DecideAskUser
	// DecideStop is returned when the model answers without calling a tool.
	DecideStop
	// DecideInvalid is returned when the model calls a tool that does not exist
	// or passes arguments that cannot be decoded. The caller reports the problem
	// back to the model and continues.
	DecideInvalid
)

type Event

type Event struct {
	Turn int    `json:"turn"`
	Kind string `json:"kind"`
	// ToolCallID ties this event to its observation when a turn contains
	// multiple action calls.
	ToolCallID string `json:"toolCallId,omitempty"`
	// Name is the step or task the event concerns.
	Name string `json:"name,omitempty"`
	// Status is the resulting node status, for action events.
	Status string `json:"status,omitempty"`
	// Attempt counts this run of the step, starting at 1.
	Attempt int `json:"attempt,omitempty"`
	// Reason carries the agent's justification, or why a call was rejected.
	Reason     string `json:"reason,omitempty"`
	StartedAt  string `json:"startedAt,omitempty"`
	FinishedAt string `json:"finishedAt,omitempty"`
	// ChildDAGRunID identifies the child run this action produced, so the
	// timeline can link straight to it. Empty for steps that run no child DAG.
	ChildDAGRunID string `json:"childDagRunId,omitempty"`
	// ChildDAGName is the child DAG that ran.
	ChildDAGName string `json:"childDagName,omitempty"`
}

Event is one entry on the agent's decision timeline. The timeline is what makes an agent run legible: it records what ran, in what order, and when each task was satisfied, none of which a dependency graph can express.

func EventsFromState

func EventsFromState(raw json.RawMessage) []Event

EventsFromState decodes the decision timeline persisted on an agent node. Unreadable or absent state yields no events rather than an error.

type MaskFunc

type MaskFunc func([]ir.LLMMessage) []ir.LLMMessage

Planner asks the model which action to take next. MaskFunc hides secret values in the copy of a conversation that leaves for an external model.

type PendingAction

type PendingAction struct {
	ToolCallID string `json:"toolCallId"`
	ToolName   string `json:"toolName"`
	Step       string `json:"step"`
	// Question is the text put to a person, set when the pending action is a
	// question the agent asked.
	Question string `json:"question,omitempty"`
}

PendingAction records the tool call whose observation has not been reported back to the LLM yet. It is set while a chosen step runs so that a run which suspends mid-action can report the outcome after it resumes.

type Planner

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

func NewPlanner

func NewPlanner(
	provider llmpkg.Provider,
	cfg *ir.LLMConfig,
	catalog *Catalog,
	system string,
	mask MaskFunc,
) *Planner

NewPlanner builds a planner over a provider and an action catalog. The system prompt is prepended to the agent's own framing; callers pass it already resolved, so a workflow can parameterise its instructions.

func (*Planner) Next

func (p *Planner) Next(ctx context.Context, st *State) ([]Decision, error)

Next runs one turn of the decision loop. The conversation in st is extended with the model's reply; the caller is responsible for appending the tool result once the decision has been carried out.

type State

type State struct {
	Tasks []TaskState `json:"tasks"`
	// Events is the decision timeline, in the order decisions were made.
	Events []Event `json:"events,omitempty"`
	// StepRuns counts how many times each step has been started.
	StepRuns map[string]int `json:"stepRuns,omitempty"`
	// Turns counts LLM decisions made so far.
	Turns int `json:"turns,omitempty"`
	// Pending is the legacy single action awaiting an observation. It is retained
	// for runs persisted before action batches were supported.
	Pending *PendingAction `json:"pending,omitempty"`
	// PendingActions is the ordered action batch whose observations have not
	// been reported to the model yet.
	PendingActions []PendingAction `json:"pendingActions,omitempty"`
	// Nudges counts consecutive turns where the LLM declined to act while tasks
	// were still open.
	Nudges int `json:"nudges,omitempty"`
	// Answers records what a person replied to each question, so the agent
	// is held to an answer it already has.
	Answers map[string]string `json:"answers,omitempty"`
	// ObservationAging records that old tool results must remain compacted for
	// the rest of this run.
	ObservationAging bool `json:"observationAging,omitempty"`
	// contains filtered or unexported fields
}

State is the agent's durable memory. It survives suspension because it is persisted on the agent node and carried into the resumed run.

func LoadState

func LoadState(raw json.RawMessage, messages []ir.LLMMessage, dag *ir.DAG) (*State, error)

LoadState restores state persisted by an earlier attempt of the same run and reconciles it with the DAG, so that editing the task list between attempts neither drops progress nor resurrects removed tasks.

func NewState

func NewState(dag *ir.DAG) *State

NewState builds the initial state for an agent DAG.

func (*State) Append

func (s *State) Append(msgs ...ir.LLMMessage)

Append adds a message to the conversation.

func (*State) ClearPendingBatch added in v2.15.0

func (s *State) ClearPendingBatch()

ClearPendingBatch removes every pending-action representation.

func (*State) CompactAllObservations

func (s *State) CompactAllObservations(maxBytes int) int

CompactAllObservations replaces each tool result whose deterministic summary is smaller. It returns the number replaced so callers can avoid retrying an unchanged request.

func (*State) CompactObservations

func (s *State) CompactObservations(keepRecent, maxBytes int) int

CompactObservations replaces all but the newest keepRecent tool results with deterministic one-line summaries. Positive maxBytes values also bound each summary. Assistant tool calls and result IDs remain unchanged so the conversation continues to satisfy provider tool protocols. Zero keepRecent disables compaction.

func (*State) EnableObservationAging

func (s *State) EnableObservationAging()

EnableObservationAging keeps old observations compacted for subsequent decisions and across suspension or retry.

func (*State) FailedTasks

func (s *State) FailedTasks() []TaskState

FailedTasks lists the tasks the agent declared unachievable, with the reason it gave. A run with any of these has failed.

func (*State) FinalizeEvent

func (s *State) FinalizeEvent(toolCallID, step, status, finishedAt, reason string)

FinalizeEvent updates the event for a suspended tool call with the outcome it reached. The step name supports state written before tool-call IDs were kept.

func (*State) LatestPromptTokens

func (s *State) LatestPromptTokens() int

LatestPromptTokens returns the most recent prompt size reported by the provider. Zero means no decision reported usage.

func (*State) Marshal

func (s *State) Marshal() (json.RawMessage, error)

Marshal serializes the state for persistence on the agent node.

func (*State) Messages

func (s *State) Messages() []ir.LLMMessage

Messages returns the conversation so far.

func (*State) OpenTaskNames

func (s *State) OpenTaskNames() []string

OpenTaskNames lists the tasks the agent has yet to settle.

func (*State) PendingBatch added in v2.15.0

func (s *State) PendingBatch() []PendingAction

PendingBatch returns the ordered tool calls awaiting observations.

func (*State) PriorAnswer

func (s *State) PriorAnswer(question string) (string, bool)

PriorAnswer returns what a person already said to this exact question.

func (*State) QuestionCount

func (s *State) QuestionCount() int

QuestionCount reports how many questions have been answered so far.

func (*State) RecordAnswer

func (s *State) RecordAnswer(question, answer string)

RecordAnswer stores a reply so the same question is not put to a person twice.

func (*State) RecordEvent

func (s *State) RecordEvent(e Event)

RecordEvent appends an entry to the decision timeline, stamping it with the turn it belongs to.

func (*State) RecordStepRun

func (s *State) RecordStepRun(step string) int

RecordStepRun counts a step start and returns the new total.

func (*State) SetPendingBatch added in v2.15.0

func (s *State) SetPendingBatch(actions []PendingAction)

SetPendingBatch records an ordered batch awaiting observations.

func (*State) SetTaskStatus

func (s *State) SetTaskStatus(name string, status TaskStatus, reason string) error

SetTaskStatus records where a task now stands. Naming an unknown task, or restating the status a task already has, is reported back to the agent as a tool error rather than failing the run.

func (*State) Settled

func (s *State) Settled() bool

Settled reports whether no task is still open, which is what ends the run.

func (*State) StepRunCount

func (s *State) StepRunCount(step string) int

StepRunCount reports how many times a step has been started in this run.

type TaskState

type TaskState struct {
	Name        string     `json:"name"`
	Description string     `json:"description,omitempty"`
	Status      TaskStatus `json:"status,omitempty"`
	// Reason is the justification the agent gave for the current status.
	Reason string `json:"reason,omitempty"`
}

TaskState tracks one goal across the lifetime of an agent run.

func TasksFromState

func TasksFromState(raw json.RawMessage) []TaskState

TasksFromState decodes the task progress persisted on an agent node. Unreadable or absent state yields no tasks rather than an error, so a display surface never fails on it.

type TaskStatus

type TaskStatus string

TaskStatus is where a goal stands. A run ends once no task is open.

const (
	// TaskOpen is a goal still to be settled. Tasks start here.
	TaskOpen TaskStatus = "open"
	// TaskCompleted is a goal that was achieved.
	TaskCompleted TaskStatus = "completed"
	// TaskSkipped is a goal the agent judged unnecessary. It does not fail
	// the run: nothing went wrong, there was simply nothing to do.
	TaskSkipped TaskStatus = "skipped"
	// TaskFailed is a goal that cannot be achieved. It fails the run, because
	// the goal was neither met nor waived.
	TaskFailed TaskStatus = "failed"
)

Jump to

Keyboard shortcuts

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