controller

package
v2.11.4 Latest Latest
Warning

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

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

Documentation

Overview

Package controller implements the decision layer of a controller 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 controller 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 controller 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 controller settling, or reopening, a task.
	// The new status is carried on the event.
	EventTaskStatus = "task_status"
	// EventAskUser is a question the controller put to a person.
	EventAskUser = "ask_user"
	// EventRejected is a tool call the controller could not carry out.
	EventRejected = "rejected"
	// EventStalled is a turn where the model declined to act.
	EventStalled = "stalled"
)

Event kinds recorded on the controller'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 controller 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 core.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 controller 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 a controller may choose from, expressed as LLM function-calling tools.

func NewCatalog

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

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

func (*Catalog) Definitions

func (c *Catalog) Definitions() []exec.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 the single action the controller chose for this turn.

type DecisionKind

type DecisionKind int

DecisionKind classifies what the controller 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"`
	// 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 controller'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 controller's decision timeline. The timeline is what makes a controller 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 a controller node. Unreadable or absent state yields no events rather than an error.

type MaskFunc

type MaskFunc func([]exec.LLMMessage) []exec.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 controller 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 *core.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 controller'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 set while an action is in flight.
	Pending *PendingAction `json:"pending,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 controller
	// 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 controller's durable memory. It survives suspension because it is persisted on the controller node and carried into the resumed run.

func LoadState

func LoadState(raw json.RawMessage, messages []exec.LLMMessage, dag *core.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 *core.DAG) *State

NewState builds the initial state for a controller DAG.

func (*State) Append

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

Append adds a message to the conversation.

func (*State) CompactAllObservations added in v2.11.3

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 added in v2.11.3

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 added in v2.11.3

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 controller declared unachievable, with the reason it gave. A run with any of these has failed.

func (*State) FinalizeEvent

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

FinalizeEvent updates the most recent event for a step with the outcome it reached. An action that suspended was recorded as waiting, and only the run that resumes knows how it ended.

func (*State) LatestPromptTokens added in v2.11.3

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 controller node.

func (*State) Messages

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

Messages returns the conversation so far.

func (*State) OpenTaskNames

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

OpenTaskNames lists the tasks the controller has yet to settle.

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) 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 controller 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 controller gave for the current status.
	Reason string `json:"reason,omitempty"`

	// Done is the status this field used to carry. It is read when restoring a
	// run that was suspended by an earlier version, and never written.
	Done bool `json:"done,omitempty"`
}

TaskState tracks one goal across the lifetime of a controller run.

func TasksFromState

func TasksFromState(raw json.RawMessage) []TaskState

TasksFromState decodes the task progress persisted on a controller 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 controller 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