harness

package
v0.5.4-0...-4dd0ed7 Latest Latest
Warning

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

Go to latest
Published: Jun 30, 2026 License: MIT Imports: 12 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
var DefaultCompactionSettings = CompactionSettings{
	ReserveTokens:    16384,
	KeepRecentTokens: 20000,
}

DefaultCompactionSettings are used for any zero field on a SummarizingCompactor.

View Source
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

func FormatSkillsForSystemPrompt(skills []Skill) string

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

func New(ctx context.Context, opts Options) (*Harness, error)

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) Abort

func (h *Harness) Abort()

Abort cancels an in-progress run.

func (*Harness) ActiveTools

func (h *Harness) ActiveTools() []agent.AgentTool

ActiveTools returns the tools currently advertised to the model, in registry order.

func (*Harness) Agent

func (h *Harness) Agent() *agent.Agent

Agent returns the wrapped agent for advanced callers that need direct access.

func (*Harness) Compact

func (h *Harness) Compact(ctx context.Context) (bool, error)

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

func (h *Harness) Continue(ctx context.Context) error

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

func (h *Harness) Prompt(ctx context.Context, text string, images ...llm.ImageContent) error

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

func (h *Harness) PromptFromTemplate(ctx context.Context, name string, args ...string) error

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

func (h *Harness) SetActiveTools(names ...string) error

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

func (h *Harness) SetModel(model llm.Model)

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

func (h *Harness) SetSkills(skills []Skill)

SetSkills replaces the registered skills. Changes apply from the next run.

func (*Harness) SetSystemPrompt

func (h *Harness) SetSystemPrompt(prompt string)

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

func (h *Harness) SetTools(tools []agent.AgentTool)

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

func (h *Harness) Skill(ctx context.Context, name string, additionalInstructions ...string) error

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) Skills

func (h *Harness) Skills() []Skill

Skills returns a copy of the registered skills.

func (*Harness) Snapshot

func (h *Harness) Snapshot() agent.State

Snapshot returns a read-only snapshot of the underlying agent state.

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.

func (*Harness) Tools

func (h *Harness) Tools() []agent.AgentTool

Tools returns a copy of the full tool registry.

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

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

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

type SystemPromptFunc func(TurnInfo) string

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.

Jump to

Keyboard shortcuts

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