Documentation
¶
Overview ¶
Package harness provides the stateful orchestration layer over the agent runtime: persistent session trees with branching, automatic context compaction, branch summaries, and skill/template resources — the pieces an agent application needs beyond a single run.
A Session is an append-only entry tree over a Store (MemoryStore for ephemeral use, JSONLStore/Repo for durable files). Appending advances the active leaf; Session.MoveTo branches from any earlier entry, and Session.Context reconstructs the model-visible conversation, applying the latest compaction and any branch summaries.
A Harness drives an agent.Agent over that tree:
store, _ := harness.Repo{Dir: "sessions"}.Create("", nil)
sess, _ := harness.NewSession(store)
h, _ := harness.New(model, sess,
harness.WithTools(myTools...),
harness.WithCompaction(harness.CompactionSettings{ContextTokens: 200_000}),
)
result, err := h.Prompt(ctx, "Let's get to work.")
Each prompt reconstructs context from the tree, runs the loop, and persists every accepted message at turn boundaries (assistant entries carry their turn's token usage), so a process can stop and resume mid-project. Harness.PromptStream exposes the same lifecycle incrementally; Harness.Cancel can stop the active prompt from another goroutine. When automatic compaction is enabled, oversized context is summarized — with pi-style cut points that never separate a tool result from its call — before the prompt runs.
ContinuationWorker adapts one complete prompt run to the neutral continuation lifecycle without making the Harness session control authority.
The package has no third-party runtime dependencies.
Index ¶
- Constants
- Variables
- func EstimateContext(path []Entry) int
- func EstimateTokens(msg ai.Message) int
- func FormatSkillsPrompt(skills []Skill) string
- func LoadSkillFSWithDiagnostics(fsys fs.FS, p string) (Skill, []SkillDiagnostic, error)
- func MarshalEntry(e Entry) ([]byte, error)
- func NewSkillTool(catalog *SkillCatalog) (agent.Tool, error)
- func ShouldCompact(tokens int, s CompactionSettings) bool
- func SummarizeBranch(ctx context.Context, model ai.LanguageModel, sess *Session, ...) (string, error)
- func SummarizeCompaction(ctx context.Context, model ai.LanguageModel, prep *CompactionPlan, ...) (string, error)
- func ValidateTemplates(templates ...PromptTemplate) error
- type CompactionPlan
- type CompactionSettings
- type Context
- type ContinuationPrompt
- type ContinuationResultMapper
- type ContinuationWorker
- type ContinuationWorkerOption
- type Entry
- type Harness
- func (h *Harness) Cancel() error
- func (h *Harness) Compact(ctx context.Context, instructions string) error
- func (h *Harness) FollowUp(msgs ...ai.Message) error
- func (h *Harness) Model() ai.LanguageModel
- func (h *Harness) NavigateTo(ctx context.Context, entryID string, summarize bool) error
- func (h *Harness) Phase() Phase
- func (h *Harness) Prompt(ctx context.Context, text string) (*agent.RunResult, error)
- func (h *Harness) PromptMessages(ctx context.Context, msgs ...ai.Message) (*agent.RunResult, error)
- func (h *Harness) PromptMessagesStream(ctx context.Context, msgs ...ai.Message) iter.Seq2[agent.Event, error]
- func (h *Harness) PromptStream(ctx context.Context, text string) iter.Seq2[agent.Event, error]
- func (h *Harness) PromptTemplate(ctx context.Context, name string, args ...string) (*agent.RunResult, error)
- func (h *Harness) ResolvePending(ctx context.Context, ...) error
- func (h *Harness) ResolveToolCalls(resolutions ...agent.ToolResolution) error
- func (h *Harness) Session() *Session
- func (h *Harness) SetModel(m ai.LanguageModel) error
- func (h *Harness) SkillCatalog() *SkillCatalog
- func (h *Harness) Steer(msgs ...ai.Message) error
- type JSONLPrefix
- type JSONLPrefixLimits
- type JSONLStore
- type Kind
- type MemoryStore
- type Option
- func WithAgentOptions(opts ...agent.Option) Option
- func WithCompaction(s CompactionSettings) Option
- func WithOnEvent(fn func(context.Context, agent.Event)) Option
- func WithSkillCatalog(catalog *SkillCatalog) Option
- func WithSkills(skills ...Skill) Option
- func WithSkillsDir(dir string) Option
- func WithSkillsFS(fsys fs.FS) Option
- func WithSummaryModel(m ai.LanguageModel) Option
- func WithSystem(s string) Option
- func WithSystemFunc(fn func(SystemContext) string) Option
- func WithSystemSuffix(s string) Option
- func WithTemplates(templates ...PromptTemplate) Option
- func WithTemplatesDir(dir string) Option
- func WithTemplatesFS(fsys fs.FS) Option
- func WithTools(tools ...agent.Tool) Option
- type Phase
- type PromptTemplate
- type Repo
- func (r Repo) Create(id string, extra map[string]string) (*JSONLStore, error)
- func (r Repo) Delete(id string) error
- func (r Repo) Fork(sourceID, atEntryID, newID string) (*JSONLStore, error)
- func (r Repo) ForkSession(source *Session, atEntryID string, newID string, extra map[string]string) (*JSONLStore, error)
- func (r Repo) List() ([]SessionMetadata, error)
- func (r Repo) Open(id string) (*JSONLStore, error)
- type Session
- func (s *Session) AppendCompaction(summary, firstKeptID string, tokensBefore int) (string, error)
- func (s *Session) AppendCustom(customType string, data ai.JSON) (string, error)
- func (s *Session) AppendMessage(msg ai.Message, usage *ai.Usage) (string, error)
- func (s *Session) AppendModelChange(provider ai.Provider, modelID string) (string, error)
- func (s *Session) CommonAncestor(a, b string) (string, error)
- func (s *Session) Context() (Context, error)
- func (s *Session) Entries() []Entry
- func (s *Session) Entry(id string) (Entry, bool)
- func (s *Session) Labels() map[string]string
- func (s *Session) LeafID() string
- func (s *Session) Metadata() SessionMetadata
- func (s *Session) MoveTo(entryID, summary string) error
- func (s *Session) Name() string
- func (s *Session) Path() []Entry
- func (s *Session) Pending() ([]ai.ToolCallPart, error)
- func (s *Session) SetLabel(targetID, label string) error
- func (s *Session) SetName(name string) error
- func (s *Session) Tree(limits TreeLimits) (TreeSnapshot, error)
- type SessionMetadata
- type Skill
- type SkillActivation
- type SkillCatalog
- func (c *SkillCatalog) Activate(name string) (SkillActivation, error)
- func (c *SkillCatalog) Activations() []SkillActivation
- func (c *SkillCatalog) ForModel() (*SkillCatalog, error)
- func (c *SkillCatalog) ForModelWith(names ...string) (*SkillCatalog, error)
- func (c *SkillCatalog) ForUser() (*SkillCatalog, error)
- func (c *SkillCatalog) List() []Skill
- func (c *SkillCatalog) Resource(name, resourcePath string) (SkillResource, error)
- func (c *SkillCatalog) Search(query string) []Skill
- type SkillDiagnostic
- type SkillInvocation
- type SkillResource
- type Store
- type SystemContext
- type TreeLimits
- type TreeNode
- type TreeSnapshot
Examples ¶
Constants ¶
const ( DefaultCompactionReserveTokens = 16384 DefaultCompactionKeepRecentTokens = 20000 // DefaultCompactionSummaryTokens bounds summary generation independently // from the output space reserved for the next conversation turn. DefaultCompactionSummaryTokens = 4096 )
Default compaction settings (mirroring pi's harness defaults).
const ( CompactionPrefix = "[Conversation summary — earlier context was compacted]\n\n" BranchSummaryPrefix = "[Summary of an abandoned conversation branch]\n\n" )
Summary message prefixes. Context reconstruction renders compaction and branch summaries as user messages carrying these prefixes.
const ( // DefaultTreeMaxNodes bounds one tree projection while retaining useful // interactive histories without exposing an unbounded allocation surface. DefaultTreeMaxNodes = 4096 // DefaultTreeMaxDepth bounds parent traversal for one projected node. DefaultTreeMaxDepth = 256 )
const SkillToolName = "skill"
SkillToolName is the reserved Tool name for explicit Skill activation.
Variables ¶
var ( // ErrBusy means the harness is already running a prompt, compaction, or // navigation; wait for it to become idle. ErrBusy = errors.New("harness: harness is busy") // ErrIdle means the operation needs an active run (steering an idle // harness, for example). ErrIdle = errors.New("harness: no active run") // ErrNothingToCompact means the session has no compactable history — it // is empty or already ends at a compaction point. ErrNothingToCompact = errors.New("harness: nothing to compact") // ErrEntryNotFound means the referenced entry ID is not in the session. ErrEntryNotFound = errors.New("harness: entry not found") // ErrSessionCorrupt means persisted entries do not form a valid append-only // session graph. The store must not be used for writes until repaired. ErrSessionCorrupt = errors.New("harness: corrupt session") // ErrInvalidEntry means a caller attempted to append an invalid entry. ErrInvalidEntry = errors.New("harness: invalid entry") )
Sentinel errors returned by Harness and session operations. Match them with errors.Is.
Functions ¶
func EstimateContext ¶
EstimateContext estimates the context size of a branch: the last recorded assistant usage (provider-reported input plus output) plus the heuristically estimated messages after it.
func EstimateTokens ¶
EstimateTokens estimates one message's token count with a conservative four-characters-per-token heuristic.
func FormatSkillsPrompt ¶
FormatSkillsPrompt renders skills as the system-prompt block the model reads to know which skills exist (agentskills.io style). It returns "" when there are no skills.
func LoadSkillFSWithDiagnostics ¶
LoadSkillFSWithDiagnostics loads one explicit Agent Skills manifest and reports non-fatal ecosystem compatibility decisions. It never discovers sibling Skills or reads sibling resources.
func MarshalEntry ¶ added in v0.1.4
MarshalEntry encodes an entry into its stable JSON envelope: exactly one entry, without the line terminator the JSONL store adds. The bytes are what the JSONL store writes on a line and they round-trip through UnmarshalEntry, so an out-of-package Store can persist entries in the same format instead of reimplementing it.
func NewSkillTool ¶
func NewSkillTool(catalog *SkillCatalog) (agent.Tool, error)
NewSkillTool returns the reserved Tool that activates exact names from catalog. The dynamic name enum prevents the model from guessing unavailable skills. An optional resource path reads one bounded text resource after the skill has been activated.
The Tool never exposes Source, executes scripts, interprets allowed-tools, reads binary resources, or changes policy.
func ShouldCompact ¶
func ShouldCompact(tokens int, s CompactionSettings) bool
ShouldCompact reports whether an estimated context size crosses the compaction threshold.
func SummarizeBranch ¶
func SummarizeBranch(ctx context.Context, model ai.LanguageModel, sess *Session, fromID, toID string) (string, error)
SummarizeBranch generates a summary of the branch being abandoned by a move from fromID back to toID (typically their common ancestor — see Session.CommonAncestor). Pass the result to Session.MoveTo. It fails with ErrNothingToCompact when the abandoned segment has no messages.
func SummarizeCompaction ¶
func SummarizeCompaction(ctx context.Context, model ai.LanguageModel, prep *CompactionPlan, settings CompactionSettings, instructions string) (string, error)
SummarizeCompaction generates the compaction summary for a prepared plan using the given model: the history summary (updating a previous one when present), plus a separately summarized turn prefix for split turns. instructions optionally focus the summary. Commit the result with Session.AppendCompaction.
func ValidateTemplates ¶
func ValidateTemplates(templates ...PromptTemplate) error
ValidateTemplates checks names, content, and duplicate identities for a complete prompt-template set.
Types ¶
type CompactionPlan ¶
type CompactionPlan struct {
// FirstKeptID is the entry where retained history starts.
FirstKeptID string
// ToSummarize is the history being folded into the summary.
ToSummarize ai.Messages
// TurnPrefix holds the leading messages of a split turn, summarized
// separately (see SplitTurn).
TurnPrefix ai.Messages
// SplitTurn reports that the cut lands inside a turn: its prefix is
// summarized while its tail is retained.
SplitTurn bool
// TokensBefore is the estimated context size before compaction.
TokensBefore int
// Previous is the prior compaction's summary, updated iteratively.
Previous string
}
CompactionPlan is a planned compaction, produced by PlanCompaction and consumed by SummarizeCompaction.
func PlanCompaction ¶
func PlanCompaction(path []Entry, settings CompactionSettings) *CompactionPlan
PlanCompaction plans a compaction of the branch: it finds the cut point that keeps roughly KeepRecentTokens of recent history (never separating a tool result from its call) and collects the messages to summarize. It returns nil when there is nothing to compact.
type CompactionSettings ¶
type CompactionSettings struct {
// ContextTokens is the total context budget compaction defends.
ContextTokens int
// ReserveTokens is kept free for the summarization prompt and the next
// turn's output (default [DefaultCompactionReserveTokens]).
ReserveTokens int
// KeepRecentTokens is approximately how much recent history survives a
// compaction (default [DefaultCompactionKeepRecentTokens]).
KeepRecentTokens int
// SummaryTokens is the maximum output budget for the generated summary
// (default [DefaultCompactionSummaryTokens]). It is deliberately distinct
// from ReserveTokens, which protects the following normal model request.
SummaryTokens int
}
CompactionSettings tunes automatic compaction. ContextTokens must be set to the model's context-window budget — the ai package deliberately has no per-model window table.
type Context ¶
type Context struct {
// Messages is the conversation to send, oldest first.
Messages ai.Messages
// Provider and ModelID identify the model the branch last ran with (from
// model_change entries and assistant responses); empty when unknown.
Provider ai.Provider
ModelID string
}
Context is the model-visible reconstruction of the active branch.
type ContinuationPrompt ¶
type ContinuationPrompt func(context.Context, continuation.WorkRequest) ([]ai.Message, error)
ContinuationPrompt maps neutral Work input to one Harness prompt.
type ContinuationResultMapper ¶
type ContinuationResultMapper func( context.Context, continuation.WorkRequest, *agent.RunResult, string, string, ) (ai.JSON, error)
ContinuationResultMapper projects one Harness run into bounded Controller evidence.
type ContinuationWorker ¶
type ContinuationWorker struct {
// contains filtered or unexported fields
}
ContinuationWorker adapts exactly one Harness prompt to continuation.Worker.
func NewContinuationWorker ¶
func NewContinuationWorker( harness *Harness, prompt ContinuationPrompt, options ...ContinuationWorkerOption, ) (*ContinuationWorker, error)
NewContinuationWorker creates a first-party Harness Worker adapter.
func (*ContinuationWorker) Run ¶
func (worker *ContinuationWorker) Run( ctx context.Context, request continuation.WorkRequest, ) (continuation.WorkResult, error)
Run implements continuation.Worker with one complete PromptMessages call.
type ContinuationWorkerOption ¶
type ContinuationWorkerOption func(*continuationWorkerConfig) error
ContinuationWorkerOption configures a ContinuationWorker.
func WithContinuationResultMapper ¶
func WithContinuationResultMapper(mapper ContinuationResultMapper) ContinuationWorkerOption
WithContinuationResultMapper replaces the default evidence projection.
func WithContinuationTextLimit ¶
func WithContinuationTextLimit(maxBytes int) ContinuationWorkerOption
WithContinuationTextLimit bounds final response text in default evidence.
type Entry ¶
type Entry struct {
Kind Kind
ID string
ParentID string
Time time.Time
// Message and Usage are set on message entries. Usage is recorded for
// assistant messages when the harness knows the turn's accounting.
Message ai.Message
Usage *ai.Usage
// Provider and ModelID are set on model_change entries.
Provider ai.Provider
ModelID string
// Summary is set on compaction and branch_summary entries; FirstKeptID
// and TokensBefore on compaction; FromID on branch_summary.
Summary string
FirstKeptID string
TokensBefore int
FromID string
// Custom names the application entry type and Data carries its payload.
Custom string
Data ai.JSON
// TargetID and Label are set on label entries.
TargetID string
Label string
// Name is set on name entries.
Name string
// LeafID is set on leaf entries.
LeafID string
}
Entry is one node of a session tree. Entries form the tree through ParentID ("" is the root); the storage order is append order, and the active conversation is the path from the current leaf to the root.
Only the fields documented for the entry's Kind are meaningful.
func UnmarshalEntry ¶ added in v0.1.4
UnmarshalEntry decodes an entry written by MarshalEntry.
Decoding is strict, matching the JSONL loader: unknown fields, trailing data and oversized input are rejected rather than ignored, so a store can tell a corrupted or version-skewed row from a readable one.
type Harness ¶
type Harness struct {
// contains filtered or unexported fields
}
Harness orchestrates an agent over a persistent Session: each prompt reconstructs the model-visible context from the tree, runs the agent loop, and records every new message back — with usage accounting, automatic compaction, branch navigation, and skill/template resources.
A harness runs one operation at a time (ErrBusy otherwise); its phases are visible through Harness.Phase.
func (*Harness) Cancel ¶
Cancel requests cancellation of the active prompt run. It is safe to call concurrently and returns ErrIdle when no agent run is active.
func (*Harness) Compact ¶
Compact summarizes older history into a compaction entry, optionally focused by instructions. It fails with ErrNothingToCompact when there is nothing to fold.
func (*Harness) FollowUp ¶
FollowUp queues follow-up work into the active run (see agent.Session.FollowUp). It fails with ErrIdle when no run is active.
func (*Harness) Model ¶
func (h *Harness) Model() ai.LanguageModel
Model returns the model serving the next prompt.
func (*Harness) NavigateTo ¶
NavigateTo moves the session to an earlier entry, branching the tree. With summarize, the abandoned branch is summarized (with the summary model) and recorded so its context carries over.
func (*Harness) Prompt ¶
Prompt runs one user prompt through the agent loop, persisting the exchange to the session.
func (*Harness) PromptMessages ¶
PromptMessages runs the agent loop over the session's reconstructed context plus msgs. When automatic compaction is enabled and the estimated context crosses the threshold, the harness compacts first. All new messages — prompts, assistant turns, tool results, injected steering — are persisted at turn boundaries, with assistant entries carrying their turn's usage; a failed run keeps everything recorded up to the failure.
func (*Harness) PromptMessagesStream ¶
func (h *Harness) PromptMessagesStream( ctx context.Context, msgs ...ai.Message, ) iter.Seq2[agent.Event, error]
PromptMessagesStream is the streaming form of Harness.PromptMessages. Runtime events retain their original order. Normal completion, failure, cancellation, and early iterator termination all preserve completed messages and release the active prompt lifecycle.
func (*Harness) PromptStream ¶
PromptStream is the streaming form of Harness.Prompt. It persists the same save points as the blocking path and restores the harness to idle when the consumer stops early.
func (*Harness) PromptTemplate ¶
func (h *Harness) PromptTemplate(ctx context.Context, name string, args ...string) (*agent.RunResult, error)
PromptTemplate formats the named template with args (see PromptTemplate.Format) and prompts with the result.
func (*Harness) ResolvePending ¶
func (h *Harness) ResolvePending(ctx context.Context, fn func(ctx context.Context, call ai.ToolCallPart) ([]ai.Part, error)) error
ResolvePending answers tool calls left pending by a paused run (see agent.StopPaused), persisting the resolution so the next prompt can continue.
func (*Harness) ResolveToolCalls ¶
func (h *Harness) ResolveToolCalls(resolutions ...agent.ToolResolution) error
ResolveToolCalls answers any subset of durable pending calls (see agent.Session.ResolveToolCalls) and persists the new result message.
func (*Harness) SetModel ¶
func (h *Harness) SetModel(m ai.LanguageModel) error
SetModel switches the model for later prompts, recording a model_change entry. It fails with ErrBusy during an active operation.
func (*Harness) SkillCatalog ¶
func (h *Harness) SkillCatalog() *SkillCatalog
SkillCatalog returns the harness's validated skill catalog. It is safe to use concurrently with prompt runs; activation audit records are synchronized.
type JSONLPrefix ¶
type JSONLPrefix struct {
Metadata SessionMetadata
Entries []Entry
Truncated bool
}
JSONLPrefix is a validated, bounded prefix of one session file. Truncated reports that more durable data exists after Entries.
func ReadJSONLPrefix ¶
func ReadJSONLPrefix(path string, limits JSONLPrefixLimits) (JSONLPrefix, error)
ReadJSONLPrefix reads a bounded, validated prefix without opening an append handle. It is intended for list/index projections that must not load an unbounded transcript. A line crossing the byte budget is left unread from the result and sets Truncated instead of being treated as corruption.
type JSONLPrefixLimits ¶
JSONLPrefixLimits bound the read-only prefix used by session pickers and indexes. Zero values select conservative defaults.
type JSONLStore ¶
type JSONLStore struct {
// contains filtered or unexported fields
}
JSONLStore persists a session as a JSON-Lines file: a header line followed by one entry per line, appended as the session grows. Entries are cached in memory, so reads never touch the file after opening. A file expects a single process and a single writing Session.
func CreateJSONL ¶
func CreateJSONL(path, id string, extra map[string]string) (*JSONLStore, error)
CreateJSONL creates a new session file at path (parent directories included). An empty id gets a generated one.
func OpenJSONL ¶
func OpenJSONL(path string) (*JSONLStore, error)
OpenJSONL opens an existing session file, validating its header and loading all entries.
func (*JSONLStore) Append ¶
func (s *JSONLStore) Append(e Entry) error
Append implements Store, writing the entry through to disk.
func (*JSONLStore) Close ¶
func (s *JSONLStore) Close() error
Close releases the underlying file. The store is unusable afterwards.
func (*JSONLStore) Entries ¶
func (s *JSONLStore) Entries() ([]Entry, error)
Entries implements Store.
func (*JSONLStore) Metadata ¶
func (s *JSONLStore) Metadata() SessionMetadata
Metadata implements Store.
type Kind ¶
type Kind string
Kind discriminates Entry variants.
const ( // KindMessage carries one conversation message (with usage accounting on // assistant messages, when known). KindMessage Kind = "message" // KindModelChange records a model switch effective for later prompts. KindModelChange Kind = "model_change" // KindCompaction replaces earlier history with a summary; context // reconstruction cuts at FirstKeptID (see [Session.Context]). KindCompaction Kind = "compaction" // KindBranchSummary carries a summary of an abandoned branch, injected // when navigating the tree. KindBranchSummary Kind = "branch_summary" // KindCustom carries application data; it never enters model context. KindCustom Kind = "custom" // KindLabel attaches (or, with an empty label, clears) a label on a // target entry. KindLabel Kind = "label" // KindName records the session's human-readable name; the last one wins. KindName Kind = "name" // KindLeaf records the active tree position; the last one wins. An empty // LeafID means the root. KindLeaf Kind = "leaf" )
Entry kinds.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is an in-memory Store for tests and ephemeral sessions.
func NewMemoryStore ¶
func NewMemoryStore(id string) *MemoryStore
NewMemoryStore returns an empty in-memory store. An empty id gets a generated one.
func NewMemoryStoreWithMetadata ¶
func NewMemoryStoreWithMetadata(metadata SessionMetadata) *MemoryStore
NewMemoryStoreWithMetadata returns an empty in-memory store with explicit metadata. It is useful for applications that need lineage on ephemeral sessions while preserving NewMemoryStore's compact constructor.
func (*MemoryStore) Entries ¶
func (m *MemoryStore) Entries() ([]Entry, error)
Entries implements Store.
func (*MemoryStore) Metadata ¶
func (m *MemoryStore) Metadata() SessionMetadata
Metadata implements Store.
type Option ¶
type Option func(*hconfig)
Option configures a Harness.
func WithAgentOptions ¶
WithAgentOptions passes options through to the underlying agent (gates, hooks, limits, request tweaks). Do not pass agent.WithOnEvent here — use WithOnEvent; and tools/system configured on the harness win.
func WithCompaction ¶
func WithCompaction(s CompactionSettings) Option
WithCompaction enables automatic compaction: before each prompt, when the estimated context crosses the threshold, the harness compacts first.
func WithOnEvent ¶
WithOnEvent observes agent events across all prompt runs. Configure event observation here rather than through WithAgentOptions — the harness chains its own recording callback.
func WithSkillCatalog ¶
func WithSkillCatalog(catalog *SkillCatalog) Option
WithSkillCatalog registers a validated catalog. Its discovery entries are included in the system prompt; applications can retain the catalog to explicitly activate full instructions and inspect activation records.
func WithSkills ¶
WithSkills registers skills, surfaced to the model through the system prompt (see FormatSkillsPrompt).
func WithSkillsDir ¶
WithSkillsDir loads skills from a directory tree at construction time (see LoadSkills); New fails when loading does.
func WithSkillsFS ¶
WithSkillsFS is WithSkillsDir for any fs.FS — embedded assets included.
func WithSummaryModel ¶
func WithSummaryModel(m ai.LanguageModel) Option
WithSummaryModel sets a dedicated (typically cheaper) model for compaction and branch summaries; the main model is used otherwise.
func WithSystem ¶
WithSystem sets a static system prompt (the skills block is appended).
func WithSystemFunc ¶
func WithSystemFunc(fn func(SystemContext) string) Option
WithSystemFunc assembles the system prompt per prompt run; the skills block is appended to its result. It overrides WithSystem.
func WithSystemSuffix ¶
WithSystemSuffix appends an application-owned suffix after the generated available-Skills block. It is intended for interaction-scoped context and explicitly activated Skill instructions that must not rewrite the stable system prefix.
func WithTemplates ¶
func WithTemplates(templates ...PromptTemplate) Option
WithTemplates registers prompt templates for Harness.PromptTemplate.
func WithTemplatesDir ¶
WithTemplatesDir loads prompt templates from a directory at construction time (see LoadTemplates); New fails when loading does.
func WithTemplatesFS ¶
WithTemplatesFS is WithTemplatesDir for any fs.FS.
type PromptTemplate ¶
type PromptTemplate struct {
// Name is the stable identifier for lookup.
Name string
// Description is optional documentation.
Description string
// Content is the template text; see [PromptTemplate.Format].
Content string
}
PromptTemplate is a reusable prompt with positional argument placeholders.
func LoadTemplateFS ¶
func LoadTemplateFS(fsys fs.FS, p string) (PromptTemplate, error)
LoadTemplateFS loads one Markdown prompt template from p in fsys. It is useful when a caller has an explicit resource list and must not discover sibling templates implicitly.
func LoadTemplates ¶
func LoadTemplates(dir string) ([]PromptTemplate, error)
LoadTemplates loads prompt templates from a directory (see LoadTemplatesFS).
func LoadTemplatesFS ¶
func LoadTemplatesFS(fsys fs.FS) ([]PromptTemplate, error)
LoadTemplatesFS walks fsys for .md files (skipping SKILL.md manifests) and parses each into a PromptTemplate: frontmatter name (defaulting to the file name without extension) and description, with the body as Content.
func (PromptTemplate) Format ¶
func (t PromptTemplate) Format(args ...string) string
Format substitutes arguments into the template: $1..$9 reference positional arguments, $ARGUMENTS expands to all of them space-joined. Templates without placeholders get the arguments appended.
type Repo ¶
type Repo struct {
// Dir is the directory holding the session files.
Dir string
}
Repo manages a directory of JSONL session files, one file per session named "<id>.jsonl".
func (Repo) Fork ¶
func (r Repo) Fork(sourceID, atEntryID, newID string) (*JSONLStore, error)
Fork copies the source session's path from the root through atEntryID (its whole active branch when atEntryID is empty) into a new session, and returns the new store positioned at the copied tip.
func (Repo) ForkSession ¶
func (r Repo) ForkSession( source *Session, atEntryID string, newID string, extra map[string]string, ) (*JSONLStore, error)
ForkSession failure-atomically copies a validated source path into a new stored Session. extra overlays source header metadata; nil preserves it. The source Session remains unchanged and may stay open under its writer lock.
func (Repo) List ¶
func (r Repo) List() ([]SessionMetadata, error)
List returns metadata for every session in the repository directory.
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session is a persistent conversation tree over a Store: entries link through parent IDs, appending advances the active leaf, and Session.MoveTo re-points the leaf to branch from any earlier entry. The active conversation is always the path from the leaf to the root, and Session.Context reconstructs the model-visible view of it (applying compaction and branch summaries).
A Session is safe for concurrent use and must be the store's only writer.
func NewSession ¶
NewSession loads (or starts) a session over the given store.
func (*Session) AppendCompaction ¶
AppendCompaction commits a compaction: summary replaces all context before firstKeptID (see Session.Context).
func (*Session) AppendCustom ¶
AppendCustom records application data; it never enters model context.
func (*Session) AppendMessage ¶
AppendMessage appends a conversation message, with optional usage accounting (recorded for assistant messages so compaction can estimate context size from provider counts).
func (*Session) AppendModelChange ¶
AppendModelChange records a model switch effective for later prompts.
func (*Session) CommonAncestor ¶
CommonAncestor returns the deepest entry present on both branches ending at a and b ("" when they only share the root).
func (*Session) Context ¶
Context reconstructs the model-visible conversation for the active branch: the latest compaction entry replaces everything before its first-kept entry with its summary, branch summaries render as summary messages, and bookkeeping entries (custom, labels, names, leaves) are skipped.
func (*Session) Metadata ¶
func (s *Session) Metadata() SessionMetadata
Metadata identifies the underlying stored session.
func (*Session) MoveTo ¶
MoveTo re-points the leaf to the given entry ("" for the root), branching the tree: later appends grow from there. A non-empty summary (from SummarizeBranch or hand-written) is recorded as a branch_summary entry under the new position so the abandoned branch's context is not lost.
func (*Session) Path ¶
Path returns the active branch in conversation order: the entries from the root down to the current leaf.
func (*Session) Pending ¶
func (s *Session) Pending() ([]ai.ToolCallPart, error)
Pending returns the unanswered tool calls on the active branch in call order. A non-empty result means the conversation cannot continue until the calls are resolved through the owning Harness. Returned arguments are independent copies and may be retained or modified by the caller.
Example ¶
package main
import (
"fmt"
"github.com/rsbin1178/pips/agent/harness"
"github.com/rsbin1178/pips/ai"
)
func main() {
store := harness.NewMemoryStore("example")
sess, _ := harness.NewSession(store)
_, _ = sess.AppendMessage(ai.Assistant(
ai.ToolCallPart{ID: "call-1", Name: "write_file", Args: ai.JSON(`{"path":"main.go"}`)},
), nil)
pending, _ := sess.Pending()
fmt.Println(pending[0].Name)
}
Output: write_file
func (*Session) Tree ¶
func (s *Session) Tree(limits TreeLimits) (TreeSnapshot, error)
Tree returns a bounded defensive projection of the durable Session graph.
type SessionMetadata ¶
type SessionMetadata struct {
// ID uniquely identifies the session.
ID string `json:"id"`
// CreatedAt is when the session was created.
CreatedAt time.Time `json:"created_at"`
// Path locates the backing file for file-based stores; empty otherwise.
Path string `json:"path,omitempty"`
// Extra carries application metadata recorded at creation.
Extra map[string]string `json:"extra,omitempty"`
}
SessionMetadata identifies a stored session.
func ReadJSONLMetadata ¶
func ReadJSONLMetadata(path string) (SessionMetadata, error)
ReadJSONLMetadata reads and validates only the bounded header line. It does not open an append handle or scan conversation entries.
type Skill ¶
type Skill struct {
// Name is the stable identifier listed to the model.
Name string
// Description tells the model when the skill applies.
Description string
// Content is the full instruction text.
Content string
// Source records application-owned provenance. Skill discovery and the
// activation Tool never expose it to the model.
Source string
// License is the optional skill license declaration.
License string
// Compatibility records optional host or environment requirements.
Compatibility string
// Metadata is the optional scalar metadata mapping from the manifest.
Metadata map[string]string
// AllowedTools is declarative metadata only. A host must still enforce its
// own Tool policy; this package never grants execution from a Skill.
AllowedTools []string
// Invocation controls user and model discovery. Its zero value allows both.
Invocation SkillInvocation
// Resources contains immutable application-validated sibling resources.
Resources []SkillResource
}
Skill is an instruction resource surfaced to the model through the system prompt. Applications own loading skills (from SKILL.md files or anywhere else) and pass the parsed values in.
func LoadSkillFS ¶
LoadSkillFS loads one Agent Skills manifest from p in fsys. It is useful when a caller has an explicit resource list and must not discover sibling skills implicitly.
func LoadSkills ¶
LoadSkills loads skills from a directory tree (see LoadSkillsFS).
func LoadSkillsFS ¶
LoadSkillsFS walks fsys for Agent Skills standard manifests. Every skill must have a valid SKILL.md YAML frontmatter and a standards-compliant name and description. It never executes bundled scripts or reads references on the model's behalf.
func (Skill) CopyMetadata ¶
CopyMetadata returns a mutable copy of metadata for hosts that need to add their own fields without mutating a catalog value.
func (Skill) ModelInvocable ¶
ModelInvocable reports whether the model may discover and activate the Skill.
func (Skill) UserInvocable ¶
UserInvocable reports whether a user may explicitly select the Skill.
type SkillActivation ¶
SkillActivation records an explicit request for one skill. The record is useful for application audit trails and for making full-content injection a deliberate operation rather than an incidental side effect of discovery.
type SkillCatalog ¶
type SkillCatalog struct {
// contains filtered or unexported fields
}
SkillCatalog is an immutable, indexed collection of validated skills. It separates low-cost discovery (List and Search) from explicit activation, which is the only operation that exposes a skill's full instructions.
A catalog never executes scripts, expands dynamic content, or changes the agent's tool permissions. Those are host responsibilities.
func NewSkillCatalog ¶
func NewSkillCatalog(skills ...Skill) (*SkillCatalog, error)
NewSkillCatalog validates and indexes skills. Names must be unique and use the Agent Skills identifier grammar. Inputs and values returned by the catalog are defensively copied.
func (*SkillCatalog) Activate ¶
func (c *SkillCatalog) Activate(name string) (SkillActivation, error)
Activate returns a skill's full content and appends an in-memory audit record. Hosts that persist audit data should copy Activations after a run.
func (*SkillCatalog) Activations ¶
func (c *SkillCatalog) Activations() []SkillActivation
Activations returns a snapshot of activation records in activation order.
func (*SkillCatalog) ForModel ¶
func (c *SkillCatalog) ForModel() (*SkillCatalog, error)
ForModel returns a catalog containing only skills that permit autonomous model discovery and activation. Full instructions remain protected by Activate.
func (*SkillCatalog) ForModelWith ¶
func (c *SkillCatalog) ForModelWith(names ...string) (*SkillCatalog, error)
ForModelWith returns the model-invocable catalog plus explicitly named skills. It lets an application honor user-only selections for one request without making every user-only skill autonomously available to the model.
func (*SkillCatalog) ForUser ¶
func (c *SkillCatalog) ForUser() (*SkillCatalog, error)
ForUser returns a catalog containing only skills that permit explicit user invocation. Full instructions remain protected by Activate.
func (*SkillCatalog) List ¶
func (c *SkillCatalog) List() []Skill
List returns skill discovery metadata and does not include full content.
func (*SkillCatalog) Resource ¶
func (c *SkillCatalog) Resource(name, resourcePath string) (SkillResource, error)
Resource returns one exact resource from a skill. Callers must explicitly request both the skill and slash-relative resource path. Binary resources contain metadata only; applications decide whether and how to expose them.
func (*SkillCatalog) Search ¶
func (c *SkillCatalog) Search(query string) []Skill
Search returns discovery metadata for skills whose name, description, or metadata contain every case-insensitive query term. An empty query lists all skills. Results are ordered by exact-name match, then prefix match, then stable name order.
type SkillDiagnostic ¶
SkillDiagnostic describes a non-fatal compatibility decision made while loading one Skill manifest. Messages are intended for application logs; callers should use Code and Field for stable presentation.
type SkillInvocation ¶
type SkillInvocation uint8
SkillInvocation controls whether a Skill can be selected by a user, a model, both, or neither. The zero value preserves the historical behavior and allows both invocation paths.
const ( // SkillInvocationDefault allows both user and model invocation. SkillInvocationDefault SkillInvocation = iota // SkillInvocationUserOnly allows explicit user invocation only. SkillInvocationUserOnly // SkillInvocationModelOnly allows autonomous model invocation only. SkillInvocationModelOnly // SkillInvocationDisabled keeps the Skill unavailable to both paths. SkillInvocationDisabled )
type SkillResource ¶
SkillResource is one immutable, bounded resource bundled with a Skill. Path is slash-relative to the Skill directory. Text content is available only when Text is true; binary resources expose metadata but not content.
type Store ¶
type Store interface {
// Metadata identifies the stored session.
Metadata() SessionMetadata
// Append persists one entry.
Append(e Entry) error
// Entries returns all entries in append order.
Entries() ([]Entry, error)
}
Store is an append-only entry log backing a Session. Implementations must preserve append order in Entries. A store expects a single writer — the owning Session serializes access.
type SystemContext ¶
type SystemContext struct {
// Model serves the upcoming run.
Model ai.LanguageModel
// Skills and Templates are the harness's configured resources.
Skills []Skill
Templates []PromptTemplate
// Session is the conversation being continued.
Session *Session
}
SystemContext is what a WithSystemFunc callback sees when assembling the system prompt for a prompt run.
type TreeLimits ¶
TreeLimits bound a Session.Tree projection. Zero values select defaults.