agent

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package agent is the runtime: one entry point for running the agent. Everything after Fire — tool composition, system-prompt rendering, the model call, listener fan-out, cancellation — happens in here. A surface builds a Request and hands it over; it never touches the tools provider, the model, or the memory store directly.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Agent

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

Agent is the runtime entry point. Fire starts a run and Cancel stops one; models, memory, tools, and observers are supplied by the embedding application.

The loop is explicit instead of using a higher-level agent runner so this package owns the runtime semantics: accumulated content callbacks, cancellation, tool interception, turn-ending tools, and message store.

func New

New constructs an Agent and starts accepting runs.

func (*Agent) Accepting

func (a *Agent) Accepting() bool

Accepting reports whether Fire takes runs. False after Shutdown begins.

func (*Agent) AddDefaultListener added in v1.0.0

func (a *Agent) AddDefaultListener(l ResponseListener)

AddDefaultListener adds a listener that observes every run, for observers that only come into existence after the agent was constructed — the subagent tools, which learn at Fire time which run's registry to forget. Call before the first Fire: a race against a starting run means that run may or may not carry the listener, never a corruption.

func (*Agent) Cancel

func (a *Agent) Cancel(requestID string) bool

Cancel stops a run by request id. False when no such run is live — already finished, or never started. The cancellation goes down the tree as well: a subagent nobody is waiting for any more is still work with a shell or an MCP call in it, and cancelling the parent alone would leave it running.

func (*Agent) Fire

func (a *Agent) Fire(req Request) error

Fire starts a run and returns immediately; results reach listeners, never the caller. A caller that has to wait for the answer waits on a listener.

func (*Agent) FireOrQueue added in v1.0.0

func (a *Agent) FireOrQueue(req Request, text func() string, display string) bool

FireOrQueue offers a message to the run already in flight over the same conversation, by the same user — a correction, an addition, an answer to "should I go on?" arriving while the agent works. True means the run took it: it is read into the turn at the next tool boundary, and this message needs no run of its own. False means nobody matching is live (or the run had already stopped reading), and the caller should Fire the request — which is why this does not fire on the caller's behalf: the caller built the request and owns what a refusal of it means.

A run matches when it is of the same conversation and the same user, not a background run and not a subagent: a group card being public does not make someone else's aside a correction of this run, and a subagent has no conversation to correct.

func (*Agent) Shutdown

func (a *Agent) Shutdown(ctx context.Context) error

Shutdown stops accepting and waits for in-flight runs, up to ten minutes — long, deliberately, because a run may be mid-tool-call on a slow MCP server, and killing it loses the turn a user is watching a card for. The caller decides what happens when the wait gives out.

type AgentOption

type AgentOption func(*Agent)

AgentOption configures an Agent during construction.

func WithBackend

func WithBackend(backend store.Backend) AgentOption

WithBackend supplies the persistence backend used by the pending-question guard. The agent does not own the backend or any of its connections.

func WithDefaultListener

func WithDefaultListener(listener ResponseListener) AgentOption

WithDefaultListener adds a listener that observes every run.

func WithKnowledgeBase added in v1.0.0

func WithKnowledgeBase(base knowledge.KnowledgeBase, cfg knowledge.RetrievalConfig) AgentOption

WithKnowledgeBase attaches an optional scoped knowledge base. Retrieval is best-effort: a vector-store outage is logged and the conversation still has a chance to answer from its normal context.

func WithLogger

func WithLogger(log *slog.Logger) AgentOption

WithLogger supplies the logger used for runtime and callback failures.

func WithMemoryWindow

func WithMemoryWindow(window int) AgentOption

WithMemoryWindow limits the number of conversation messages loaded per run. A non-positive value keeps the full conversation.

func WithModelName

func WithModelName(name string) AgentOption

WithModelName supplies a fallback name for model callbacks when the stream does not include one.

type Image

type Image struct {
	URL    string
	Base64 string
}

Image is one image attached to a user message: a URL, or Base64 data.

type ListenerFuncs

type ListenerFuncs struct {
	OnStartFunc     func(run *RunContext)
	OnSubscribeFunc func()
	OnModelFunc     func(model string)
	OnContentFunc   func(contentSoFar string)
	OnReasoningFunc func(reasoningSoFar string)
	OnUsageFunc     func(model string, usage *schema.TokenUsage)
	OnSubagentFunc  func(event SubagentEvent)
	// OnMessageQueuedFunc and OnQueuedMessageReadFunc observe FireOrQueue
	// joining messages to a run in flight.
	OnMessageQueuedFunc     func(requestID, display string)
	OnQueuedMessageReadFunc func(requestIDs []string)
	OnErrorFunc             func(err error)
	OnFinishedFunc          func(outcome Outcome)
	// ShouldContinueFunc nil means always continue.
	ShouldContinueFunc func() bool
}

ListenerFuncs adapts functions to ResponseListener. The zero value is a listener that observes nothing and always continues — embed it and fill the hooks a surface needs.

func (ListenerFuncs) OnContent

func (l ListenerFuncs) OnContent(c string)

func (ListenerFuncs) OnError

func (l ListenerFuncs) OnError(err error)

func (ListenerFuncs) OnFinished

func (l ListenerFuncs) OnFinished(o Outcome)

func (ListenerFuncs) OnMessageQueued added in v1.0.0

func (l ListenerFuncs) OnMessageQueued(requestID, display string)

func (ListenerFuncs) OnModel

func (l ListenerFuncs) OnModel(m string)

func (ListenerFuncs) OnQueuedMessageRead added in v1.0.0

func (l ListenerFuncs) OnQueuedMessageRead(requestIDs []string)

func (ListenerFuncs) OnReasoning added in v1.0.0

func (l ListenerFuncs) OnReasoning(r string)

func (ListenerFuncs) OnStart

func (l ListenerFuncs) OnStart(run *RunContext)

func (ListenerFuncs) OnSubagent added in v1.0.0

func (l ListenerFuncs) OnSubagent(e SubagentEvent)

func (ListenerFuncs) OnSubscribe

func (l ListenerFuncs) OnSubscribe()

func (ListenerFuncs) OnUsage

func (l ListenerFuncs) OnUsage(m string, u *schema.TokenUsage)

func (ListenerFuncs) ShouldContinue

func (l ListenerFuncs) ShouldContinue() bool

type Outcome

type Outcome string

Outcome is how a run ended.

const (
	OutcomeCompleted Outcome = "COMPLETED"
	OutcomeFailed    Outcome = "FAILED"
	OutcomeCancelled Outcome = "CANCELLED"
)

type Request

type Request struct {
	// RequestID is what Cancel stops; empty means not cancellable.
	RequestID string
	// ParentRequestID names the run that started this one, when it is a
	// subagent: cancelling the parent cancels this run, its tokens count on
	// the parent's turn, and the parent is held open until it finishes. A
	// parent that already ended is forgotten — there is nobody to tell.
	ParentRequestID string
	// Description is one line saying what the run is for, shown to listeners
	// of the parent run while a subagent works.
	Description string
	// Scenario is required (NewRequest's argument).
	Scenario Scenario
	UserID   string
	ChatID   string
	// ChatType defaults to "p2p".
	ChatType string
	// GroupID and TenantID scope the run's reads beyond the user's own
	// home: a group's or tenant's shared files and skills are read through
	// (never written to), and the knowledge base and sandbox carry the same
	// scope. Empty means no scope.
	GroupID  string
	TenantID string
	// SituationID is set by optional external-event triage runs. It is carried
	// to situation management tools and otherwise has no runtime meaning.
	SituationID string
	// ScheduledTaskID identifies the task whose firing is executing.
	ScheduledTaskID string
	// ConversationID groups the runs that share chat memory.
	ConversationID string
	RootMessageID  string
	ReplyMessageID string
	// Background runs post no answer of their own (see ScheduledTask).
	Background bool
	// PromptVariables extend the system prompt's variable set.
	PromptVariables map[string]string
	// Images may accompany Text.
	Images []Image
	// Listeners observe this run, alongside any application-level observers.
	Listeners []ResponseListener
	// TodoHandlers receive this run's todo updates.
	TodoHandlers []tools.TodoEventHandler

	// Text is the user message passed to the model.
	Text string
	// KnowledgeRetrieval is an explicit, fixed-scope lookup for unattended
	// runs. When nil, an attached knowledge base derives scope from identity and
	// retrieves using Text.
	KnowledgeRetrieval *knowledge.KnowledgeRetrieval
}

Request describes one run. Built with NewRequest plus options; the zero Request is not runnable. Scenario and Text are required, and a caller that has no conversation id gets a memoryless run rather than an error: the identity fields default, ConversationID does not.

func NewRequest

func NewRequest(scenario Scenario, text string, opts ...RequestOption) Request

NewRequest starts a Request for a scenario. Required: scenario and text.

func (Request) UserMessage

func (r Request) UserMessage() *schema.Message

UserMessage assembles the user message for the model call.

type RequestOption

type RequestOption func(*Request)

RequestOption mutates a Request during NewRequest.

func WithBackground

func WithBackground(background bool) RequestOption

WithBackground marks the run unattended (no answer of its own).

func WithConversation

func WithConversation(conversationID, rootMessageID, replyMessageID string) RequestOption

WithConversation places the run in a conversation (chat memory) and identifies the root and reply messages for surfaces.

func WithDescription added in v1.0.0

func WithDescription(description string) RequestOption

WithDescription says what the run is for, in one line, where a surface shows work in progress.

func WithIdentity

func WithIdentity(userID, chatID, chatType string) RequestOption

WithIdentity sets who is talking and where.

func WithImages

func WithImages(images ...Image) RequestOption

WithImages attaches images to the user message, as URLs or data-URI Base64 payloads.

func WithKnowledgeRetrieval added in v1.0.0

func WithKnowledgeRetrieval(retrieval knowledge.KnowledgeRetrieval) RequestOption

WithKnowledgeRetrieval sets a fixed-scope retrieval request. The scope is not derived from the incoming message, which is important for event bodies and other untrusted briefings.

func WithListener

func WithListener(l ResponseListener) RequestOption

WithListener attaches a run listener.

func WithParent added in v1.0.0

func WithParent(parentRequestID string) RequestOption

WithParent ties the run to the run that started it, making it a subagent: cancelled with the parent, its usage counted on the parent's turn, and the parent held open until it finishes.

func WithPromptVariables

func WithPromptVariables(vars map[string]string) RequestOption

WithPromptVariables supplies extra system-prompt variables.

func WithRequestID

func WithRequestID(id string) RequestOption

WithRequestID names the run for Cancel.

func WithScheduledTaskID added in v1.0.0

func WithScheduledTaskID(id string) RequestOption

WithScheduledTaskID binds optional task self-control tools to a firing.

func WithScope added in v1.0.0

func WithScope(groupID, tenantID string) RequestOption

WithScope gives the run a group and tenant: their homes' files and skills are read through the user's own, and the knowledge base and sandbox carry the same scope. Blank ids mean no scope.

func WithSituationID added in v1.0.0

func WithSituationID(id string) RequestOption

WithSituationID binds optional event-management tools to one situation.

func WithTodoHandler

func WithTodoHandler(h tools.TodoEventHandler) RequestOption

WithTodoHandler attaches a todo handler.

type ResponseListener

type ResponseListener interface {
	OnStart(run *RunContext)
	OnSubscribe()
	OnModel(model string)
	OnContent(contentSoFar string)
	// OnReasoning receives the reasoning so far, accumulated like OnContent
	// but reset per model call: a run's thinking is one block per model call,
	// joined by a blank line, because a second call after a tool result is a
	// new line of thought, not a continuation of the first.
	OnReasoning(reasoningSoFar string)
	OnUsage(model string, usage *schema.TokenUsage)
	// OnSubagent reports what a run this one started is doing. Only the
	// parent's listeners receive it, and only the fields the event kind
	// carries — see SubagentEvent.
	OnSubagent(event SubagentEvent)
	// OnMessageQueued reports that a message joined this run in flight via
	// FireOrQueue and is waiting to be read at the next tool boundary.
	// Display names the message (a preview, a sender); it is identifying
	// prose, not the body.
	OnMessageQueued(requestID, display string)
	// OnQueuedMessageRead reports the request ids the run just read into
	// itself at a tool boundary — messages this run answers, so their
	// senders are not waiting on a run of their own.
	OnQueuedMessageRead(requestIDs []string)
	OnError(err error)
	// OnFinished is called exactly once, whatever the run ended by.
	OnFinished(outcome Outcome)
	// ShouldContinue is polled between model iterations; false ends the run as
	// cancelled. It is how a surface that stopped listening (a closed card,
	// say) stops the work that was feeding it.
	ShouldContinue() bool
}

ResponseListener observes a run. Every method has a safe default (a no-op or true), and every method is called with the run's failures contained: a callback that panics is recovered and logged, because one listener must not be able to fail another's run.

OnContent receives the content so far, accumulated — not the deltas: a surface replaces what it shows, it does not append, so deltas would make every surface do its own accumulation and get it subtly wrong.

type RunContext

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

RunContext is the per-run contribution point, handed to OnStart and valid for the duration of that call only: after OnStart returns, the agent reads it once and later mutations are dropped. A listener uses it to attach a late listener of its own, a todo handler, a question handler (the ask tool needs somewhere to put the questions before the run can offer it), or a context value tools will read.

func (*RunContext) Abort

func (r *RunContext) Abort(reason string)

Abort stops the run before it starts, with a reason the error carries.

func (*RunContext) AbortReason

func (r *RunContext) AbortReason() string

AbortReason is the reason passed to Abort, empty when none was.

func (*RunContext) AddContext

func (r *RunContext) AddContext(mutate func(context.Context) context.Context)

AddContext adds a context decorator the run's tool context passes through.

func (*RunContext) AddListener

func (r *RunContext) AddListener(l ResponseListener)

AddListener attaches a listener to this run.

func (*RunContext) AddQuestionHandler

func (r *RunContext) AddQuestionHandler(h tools.QuestionHandler)

AddQuestionHandler attaches a question handler to this run. Dropped for background runs, where there is no surface to put a question on and no card to answer it — an ask that cannot be presented fails the ask, which the model experiences as "no channel could ask".

func (*RunContext) AddTodoHandler

func (r *RunContext) AddTodoHandler(h tools.TodoEventHandler)

AddTodoHandler attaches a todo handler to this run.

func (*RunContext) Request

func (r *RunContext) Request() Request

Request returns the request this run context belongs to.

type Scenario

type Scenario interface {
	// Name identifies the scenario in logs and surfaces.
	Name() string

	// ConversationMemory reports whether the run joins the conversation it
	// names — reads its history and appends to it. A run with no memory
	// still has an identity, but each one starts from nothing.
	ConversationMemory() bool

	// Offers decides whether a run of this scenario gets the named tool.
	// The default is everything; see ScheduledTaskScenario for the one
	// exception the runtime ships.
	Offers(toolName string) bool
}

Scenario names the kind of run, and what follows from that: whether the run reads and writes conversation memory, and which tools it is offered.

var ChatScenario Scenario = &chatScenario{namedScenario{ScenarioBase{}, "CHAT"}}

ChatScenario is the ordinary run: a user said something, the agent answers in the conversation. Task-self-control tools are omitted because an ordinary run is not the firing of a task.

var ScheduledTaskScenario Scenario = &scheduledTaskScenario{namedScenario{ScenarioBase{}, "SCHEDULED_TASK"}}

ScheduledTaskScenario is a firing of a scheduled task. It does not get the tools that create or manage another task, but it does get the two task-self-control tools: a firing may stop or re-arm the task it is already executing without creating a second task.

var SubagentScenario Scenario = &subagentScenario{namedScenario{ScenarioBase{}, "SUBAGENT"}}

SubagentScenario is a run another run started. It joins no conversation — its whole task is the brief it was given, and a conversation of its own keeps it out of every store whatever the backend does. It gets neither the subagent tools (that is the depth cap: one level, enforced by name with no counter to get wrong) nor the schedule tools — unattended work must not leave work behind.

type ScenarioBase

type ScenarioBase struct{}

ScenarioBase is embeddable default behaviour: memory on, every tool.

func (ScenarioBase) ConversationMemory

func (ScenarioBase) ConversationMemory() bool

func (ScenarioBase) Offers

func (ScenarioBase) Offers(string) bool

type SubagentEvent added in v1.0.0

type SubagentEvent struct {
	SubagentID   string
	Description  string
	ContentSoFar string
	Model        string
	Usage        *schema.TokenUsage
	Outcome      Outcome
}

SubagentEvent is one report about one subagent of the run. Which fields are set says what happened; the predicates name them. ContentSoFar is the subagent's accumulated answer (set while it is talking and once at the end); Usage is one model call's spend, attributed; Outcome is set when the subagent has ended.

func (SubagentEvent) Ended added in v1.0.0

func (e SubagentEvent) Ended() bool

Ended reports the subagent finished, and how; with the final ContentSoFar.

func (SubagentEvent) Said added in v1.0.0

func (e SubagentEvent) Said() bool

Said reports ContentSoFar is set: the subagent's answer so far. A surface renders this where it shows work being waited on — never as the reply, which is what OnContent means.

func (SubagentEvent) Spent added in v1.0.0

func (e SubagentEvent) Spent() bool

Spent reports Usage is set: tokens this subagent just used, counted on the parent's turn as well.

func (SubagentEvent) Started added in v1.0.0

func (e SubagentEvent) Started() bool

Started reports the subagent was started.

Jump to

Keyboard shortcuts

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