Documentation
¶
Overview ¶
Package harness is a stateful orchestration layer over the core agent.
Where agent.Agent is a thin wrapper over the run loop, Harness composes the surrounding concerns an application usually needs — transcript persistence, context compaction, and a per-turn system prompt — without forking the loop. It wraps an agent.Agent and drives the existing LoopConfig hooks, so the core transcript, steering and follow-up queues, and event subscription are reused rather than reimplemented.
Each concern is a field on Options; leaving one nil disables it, so the zero-configured Harness behaves like a plain Agent. It implements Session (transcript persistence and resume), a per-turn system-prompt builder, and a Compactor that shrinks the transcript projected to the model. Automatic compaction is projection-only: the Session and transcript keep the full history. Compact rewrites history permanently for explicit reclamation.
A run can be reconfigured between turns via the Set* methods — model, thinking level, system prompt, the tool registry, and which registered tools are active (advertised to the model). Changes apply from the next run.
Skills and prompt templates are named, reusable prompts: Skill injects a skill's instructions as a turn, PromptFromTemplate expands a template's arguments, and FormatSkillsForSystemPrompt advertises the model-invocable skills inside a system prompt.
Index ¶
- Variables
- func FormatSkillsForSystemPrompt(skills []Skill) string
- type CompactionSettings
- type Compactor
- type Harness
- func (h *Harness) Abort()
- func (h *Harness) ActiveTools() []agent.AgentTool
- func (h *Harness) Agent() *agent.Agent
- func (h *Harness) Compact(ctx context.Context) (bool, error)
- func (h *Harness) Continue(ctx context.Context) error
- func (h *Harness) FollowUp(text string, images ...llm.ImageContent)
- func (h *Harness) Messages() []agent.AgentMessage
- func (h *Harness) Prompt(ctx context.Context, text string, images ...llm.ImageContent) error
- func (h *Harness) PromptFromTemplate(ctx context.Context, name string, args ...string) error
- func (h *Harness) PromptTemplates() []PromptTemplate
- func (h *Harness) SetActiveTools(names ...string) error
- func (h *Harness) SetModel(model llm.Model)
- func (h *Harness) SetPromptTemplates(templates []PromptTemplate)
- func (h *Harness) SetSkills(skills []Skill)
- func (h *Harness) SetSystemPrompt(prompt string)
- func (h *Harness) SetThinkingLevel(level llm.ModelThinkingLevel)
- func (h *Harness) SetTools(tools []agent.AgentTool)
- func (h *Harness) Skill(ctx context.Context, name string, additionalInstructions ...string) error
- func (h *Harness) Skills() []Skill
- func (h *Harness) Snapshot() agent.State
- func (h *Harness) Steer(text string, images ...llm.ImageContent)
- func (h *Harness) Subscribe(listener func(agent.AgentEvent)) (unsubscribe func())
- func (h *Harness) Tools() []agent.AgentTool
- type InMemorySession
- type JSONLSession
- type Options
- type PromptTemplate
- type ReplaceableSession
- type Session
- type Skill
- type SummarizeFunc
- type SummarizingCompactor
- type SystemPromptFunc
- type TurnInfo
Constants ¶
This section is empty.
Variables ¶
var DefaultCompactionSettings = CompactionSettings{
ReserveTokens: 16384,
KeepRecentTokens: 20000,
}
DefaultCompactionSettings are used for any zero field on a SummarizingCompactor.
var ErrBusy = errors.New("harness: a run is already in progress")
ErrBusy is returned by Prompt and Continue when a run is already in progress. Steer and FollowUp are the way to inject messages into a running agent.
Functions ¶
func FormatSkillsForSystemPrompt ¶
FormatSkillsForSystemPrompt renders the model-invocable skills as a block to include in a system prompt, so the model can choose to use them. Skills with DisableModelInvocation set are omitted; it returns "" when none are visible.
Types ¶
type CompactionSettings ¶
type CompactionSettings struct {
// ReserveTokens is headroom kept below the model's context window; compaction
// triggers once the estimate exceeds contextWindow - ReserveTokens.
ReserveTokens int
// KeepRecentTokens is the approximate amount of recent context to retain
// verbatim after compaction.
KeepRecentTokens int
}
CompactionSettings tunes when and how much SummarizingCompactor compacts.
type Compactor ¶
type Compactor interface {
Compact(ctx context.Context, messages []agent.AgentMessage) ([]agent.AgentMessage, error)
}
Compactor shrinks a transcript before it is projected to the model. It runs on every turn via the agent's TransformContext hook and returns the messages to send for that request. It must not mutate its input.
Compaction here is projection-only: the harness and Session keep the full transcript, and only what travels to the model is shortened. Returning the input unchanged (the common case below the threshold) disables it for a turn.
type Harness ¶
type Harness struct {
// contains filtered or unexported fields
}
Harness is a stateful orchestrator over agent.Agent. It owns the wrapped agent and, when configured, persists the transcript to a Session.
Prompt and Continue block until the run completes and are mutually exclusive; a concurrent call returns ErrBusy. Steer, FollowUp, Abort, Subscribe, and Snapshot are safe to call while a run is in progress.
func New ¶
New builds a Harness. When a Session is configured, its stored transcript is loaded and used to seed the agent, so a new Harness resumes where the last one left off.
func (*Harness) ActiveTools ¶
ActiveTools returns the tools currently advertised to the model, in registry order.
func (*Harness) Agent ¶
Agent returns the wrapped agent for advanced callers that need direct access.
func (*Harness) Compact ¶
Compact rewrites the transcript to a compacted form using the configured Compactor, making the reduction permanent — unlike the projection-only compaction that runs automatically during a turn, this frees stored history. It compacts only when the Compactor decides it is warranted (e.g. over the threshold) and reports whether it did.
It requires an idle harness (returns ErrBusy otherwise) and a configured Compactor. A configured Session must implement ReplaceableSession, since the rewrite cannot be expressed as an append.
func (*Harness) Continue ¶
Continue resumes a run from the current transcript without adding a message, blocking until it completes. It returns ErrBusy if a run is already in progress.
func (*Harness) FollowUp ¶
func (h *Harness) FollowUp(text string, images ...llm.ImageContent)
FollowUp queues a message to continue the agent once it would otherwise stop.
func (*Harness) Messages ¶
func (h *Harness) Messages() []agent.AgentMessage
Messages returns the current transcript.
func (*Harness) Prompt ¶
Prompt starts a run from a text message and optional images, blocking until it completes. Newly appended messages are persisted to the Session. It returns ErrBusy if a run is already in progress.
func (*Harness) PromptFromTemplate ¶
PromptFromTemplate invokes a registered prompt template by name, substituting args into its placeholders, and runs the result like Prompt. It returns an error if the template is unknown, or ErrBusy if a run is already in progress.
func (*Harness) PromptTemplates ¶
func (h *Harness) PromptTemplates() []PromptTemplate
PromptTemplates returns a copy of the registered prompt templates.
func (*Harness) SetActiveTools ¶
SetActiveTools restricts the tools advertised to the model to the named subset of the registry. Passing no names re-activates the whole registry. It returns an error naming any tool that is not registered, leaving the active set unchanged. Changes apply from the next run.
func (*Harness) SetModel ¶
SetModel changes the model used for subsequent runs. A configured Compactor keeps its own model and is unaffected.
func (*Harness) SetPromptTemplates ¶
func (h *Harness) SetPromptTemplates(templates []PromptTemplate)
SetPromptTemplates replaces the registered prompt templates.
func (*Harness) SetSkills ¶
SetSkills replaces the registered skills. Changes apply from the next run.
func (*Harness) SetSystemPrompt ¶
SetSystemPrompt sets the static system prompt for subsequent runs. It has no effect while BuildSystemPrompt is configured, which rebuilds the prompt each turn.
func (*Harness) SetThinkingLevel ¶
func (h *Harness) SetThinkingLevel(level llm.ModelThinkingLevel)
SetThinkingLevel changes the reasoning level for subsequent runs.
func (*Harness) SetTools ¶
SetTools replaces the full tool registry. The model is advertised the active subset; any active name no longer in the registry drops out. Changes apply from the next run.
func (*Harness) Skill ¶
Skill invokes a registered skill by name: it injects the skill's instructions (plus any additional instructions) as a new user turn and runs it like Prompt. It returns an error if the skill is unknown, or ErrBusy if a run is already in progress.
func (*Harness) Steer ¶
func (h *Harness) Steer(text string, images ...llm.ImageContent)
Steer queues a message to inject after the current turn's tool calls finish.
func (*Harness) Subscribe ¶
func (h *Harness) Subscribe(listener func(agent.AgentEvent)) (unsubscribe func())
Subscribe registers a listener for run events and returns a function that removes it.
type InMemorySession ¶
type InMemorySession struct {
// contains filtered or unexported fields
}
InMemorySession is a Session backed by an in-process slice. It persists for the lifetime of the value only, which makes it a useful default for tests and ephemeral sessions. It is safe for concurrent use.
func (*InMemorySession) Append ¶
func (s *InMemorySession) Append(_ context.Context, messages ...agent.AgentMessage) error
Append retains a copy of the given messages.
func (*InMemorySession) Load ¶
func (s *InMemorySession) Load(context.Context) ([]agent.AgentMessage, error)
Load returns a copy of the retained transcript.
func (*InMemorySession) Replace ¶
func (s *InMemorySession) Replace(_ context.Context, messages []agent.AgentMessage) error
Replace overwrites the retained transcript.
type JSONLSession ¶
type JSONLSession struct {
// contains filtered or unexported fields
}
JSONLSession persists the transcript as JSON Lines — one message per line — in a file, so a session resumes across process restarts. Normal runs append; Compact rewrites the file atomically via Replace. It is safe for concurrent use.
Only llm-backed messages are supported. A custom (UI-only) AgentMessage has no llm projection and makes Append and Replace return an error, since the session cannot serialize it.
func NewJSONLSession ¶
func NewJSONLSession(path string) *JSONLSession
NewJSONLSession returns a session backed by the file at path. The file is created on first write and need not exist yet.
func (*JSONLSession) Append ¶
func (s *JSONLSession) Append(_ context.Context, messages ...agent.AgentMessage) error
Append writes the given messages to the end of the file, one JSON line each.
func (*JSONLSession) Load ¶
func (s *JSONLSession) Load(_ context.Context) ([]agent.AgentMessage, error)
Load reads the persisted transcript. A missing file is an empty history, not an error.
func (*JSONLSession) Replace ¶
func (s *JSONLSession) Replace(_ context.Context, messages []agent.AgentMessage) error
Replace overwrites the file with messages, written atomically through a temp file and rename so a crash cannot leave a half-written transcript.
type Options ¶
type Options struct {
// Model is the model used for turns. Required.
Model llm.Model
// SystemPrompt is the static system prompt, used when BuildSystemPrompt is
// nil.
SystemPrompt string
// BuildSystemPrompt, when set, builds the system prompt before every turn and
// takes precedence over SystemPrompt. Use it for prompts that depend on the
// model or conversation state.
BuildSystemPrompt SystemPromptFunc
// ThinkingLevel sets the reasoning effort for each turn.
ThinkingLevel llm.ModelThinkingLevel
// Tools is the full tool registry. The model is advertised the active subset
// (see ActiveTools and SetActiveTools).
Tools []agent.AgentTool
// ActiveTools restricts which registered tools are advertised initially, by
// name. A nil or empty slice activates every tool in Tools.
ActiveTools []string
// ToolExecution is the default batch execution mode. Empty means parallel.
ToolExecution agent.ExecutionMode
// StreamOptions are the base per-request options for every turn.
StreamOptions llm.StreamOptions
// StreamFn reaches a model for one turn. A nil value uses llm.Stream; it
// exists mainly as a seam for tests and custom transports.
StreamFn agent.StreamFn
// ConvertToLLM projects the transcript into llm messages for one request. A
// nil value uses the agent default.
ConvertToLLM func([]agent.AgentMessage) []llm.Message
// GetAPIKey resolves the provider API key before each turn, for short-lived
// tokens. A non-empty return overrides StreamOptions.APIKey.
GetAPIKey func(provider string) string
// SteeringMode and FollowUpMode control how many queued messages are injected
// at one drain point. The zero value is agent.QueueOneAtATime.
SteeringMode agent.QueueMode
FollowUpMode agent.QueueMode
// Session persists the transcript and seeds it on construction. A nil Session
// disables persistence.
Session Session
// Compactor shrinks the transcript projected to the model each turn. A nil
// Compactor disables compaction.
Compactor Compactor
// Skills are named instruction sets that can be invoked explicitly with
// Skill and advertised to the model via FormatSkillsForSystemPrompt.
Skills []Skill
// PromptTemplates are named, parameterized prompts invoked with
// PromptFromTemplate.
PromptTemplates []PromptTemplate
}
Options configures a Harness. The fields mirror the subset of agent.Options a harness drives, plus the subsystem hooks. A zero value beyond Model is a plain agent run with no persistence.
type PromptTemplate ¶
type PromptTemplate struct {
// Name is the stable identifier used for lookup.
Name string
// Description is an optional note for command lists or autocomplete.
Description string
// Content is the template body with argument placeholders.
Content string
}
PromptTemplate is a named, parameterized prompt invoked with PromptFromTemplate. In Content, $1..$N expand to positional arguments and $ARGUMENTS (or $@) to all arguments joined by spaces.
type ReplaceableSession ¶
type ReplaceableSession interface {
Session
// Replace overwrites the stored transcript with messages, in order.
Replace(ctx context.Context, messages []agent.AgentMessage) error
}
ReplaceableSession is a Session that can overwrite its whole transcript. The harness requires it for Compact, which rewrites history to a compacted form; a plain Session is append-only and cannot support that.
type Session ¶
type Session interface {
// Load returns the persisted transcript, oldest message first. It returns an
// empty slice (not an error) for a session that has no history yet.
Load(ctx context.Context) ([]agent.AgentMessage, error)
// Append records the messages a run added to the transcript, in order.
Append(ctx context.Context, messages ...agent.AgentMessage) error
}
Session persists an agent transcript across runs. Load returns the prior transcript a Harness seeds itself with at construction; Append receives the messages each run adds, in transcript order, after the run completes.
Implementations should treat the slices as read-only and copy anything they retain. A nil Session disables persistence.
type Skill ¶
type Skill struct {
// Name is the stable identifier used for lookup and model-visible listings.
Name string
// Description is a short, model-visible note on when to use the skill.
Description string
// Content is the full instructions injected when the skill is invoked.
Content string
// DisableModelInvocation hides the skill from FormatSkillsForSystemPrompt
// while still allowing explicit invocation via Skill.
DisableModelInvocation bool
}
Skill is a named set of instructions the harness can inject as a turn. Unlike the reference implementation, a Skill is in-memory content rather than a file: loading from disk is left to the caller.
type SummarizeFunc ¶
type SummarizeFunc func(ctx context.Context, model llm.Model, prior []agent.AgentMessage) (string, error)
SummarizeFunc condenses prior conversation into a single summary string.
type SummarizingCompactor ¶
type SummarizingCompactor struct {
// Model is the model whose context window bounds compaction and that the
// default summarizer calls. Required.
Model llm.Model
// Settings tunes the thresholds; zero fields fall back to defaults.
Settings CompactionSettings
// StreamOptions are passed to the default summarizer's model request.
StreamOptions llm.StreamOptions
// Summarize produces the summary. A nil value uses a default that calls the
// model with a structured summarization prompt.
Summarize SummarizeFunc
// contains filtered or unexported fields
}
SummarizingCompactor keeps the most recent context and replaces the older prefix with a model-generated summary once the estimated context exceeds the threshold. It is safe for concurrent use, though the harness drives it one run at a time.
func (*SummarizingCompactor) Compact ¶
func (c *SummarizingCompactor) Compact(ctx context.Context, messages []agent.AgentMessage) ([]agent.AgentMessage, error)
Compact summarizes the older prefix when the estimated context exceeds the threshold, otherwise returns the messages unchanged.
type SystemPromptFunc ¶
SystemPromptFunc builds the system prompt for an upcoming turn. When set on Options it is called before every turn, so the prompt can reflect the current model or conversation state, and its result takes precedence over the static Options.SystemPrompt.
type TurnInfo ¶
type TurnInfo struct {
// Model is the model that will run the upcoming turn.
Model llm.Model
// ThinkingLevel is the reasoning effort for the upcoming turn.
ThinkingLevel llm.ModelThinkingLevel
// Tools are the tools advertised to the model.
Tools []agent.AgentTool
// Messages is the transcript as it stands before the upcoming turn. On the
// first turn of a run it does not yet include the prompt being submitted; on
// later turns it includes every message appended so far.
Messages []agent.AgentMessage
// Skills are the registered skills. Pass them to FormatSkillsForSystemPrompt
// to advertise the model-invocable ones in the prompt.
Skills []Skill
}
TurnInfo describes the run state a system-prompt builder sees when a turn is about to start. It lets the prompt depend on the live conversation — the current model, reasoning level, advertised tools, and transcript so far.