harness

package
v0.1.4 Latest Latest
Warning

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

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

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

Examples

Constants

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

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

View Source
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
)
View Source
const SkillToolName = "skill"

SkillToolName is the reserved Tool name for explicit Skill activation.

Variables

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

func EstimateContext(path []Entry) int

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

func EstimateTokens(msg ai.Message) int

EstimateTokens estimates one message's token count with a conservative four-characters-per-token heuristic.

func FormatSkillsPrompt

func FormatSkillsPrompt(skills []Skill) string

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

func LoadSkillFSWithDiagnostics(
	fsys fs.FS,
	p string,
) (Skill, []SkillDiagnostic, error)

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

func MarshalEntry(e Entry) ([]byte, error)

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

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

func UnmarshalEntry(data []byte) (Entry, error)

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 New

func New(model ai.LanguageModel, sess *Session, opts ...Option) (*Harness, error)

New returns a harness driving model over the given session.

func (*Harness) Cancel

func (h *Harness) Cancel() error

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

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

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

func (h *Harness) FollowUp(msgs ...ai.Message) error

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

func (h *Harness) NavigateTo(ctx context.Context, entryID string, summarize bool) error

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

func (h *Harness) Phase() Phase

Phase reports what the harness is currently doing.

func (*Harness) Prompt

func (h *Harness) Prompt(ctx context.Context, text string) (*agent.RunResult, error)

Prompt runs one user prompt through the agent loop, persisting the exchange to the session.

func (*Harness) PromptMessages

func (h *Harness) PromptMessages(ctx context.Context, msgs ...ai.Message) (*agent.RunResult, error)

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

func (h *Harness) PromptStream(ctx context.Context, text string) iter.Seq2[agent.Event, error]

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

func (h *Harness) Session() *Session

Session returns the underlying session tree.

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.

func (*Harness) Steer

func (h *Harness) Steer(msgs ...ai.Message) error

Steer queues messages into the active run (see agent.Session.Steer). It fails with ErrIdle when no run is active.

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

type JSONLPrefixLimits struct {
	MaxBytes   int
	MaxEntries int
}

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

func (m *MemoryStore) Append(e Entry) error

Append implements Store.

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

func WithAgentOptions(opts ...agent.Option) Option

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

func WithOnEvent(fn func(context.Context, agent.Event)) Option

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

func WithSkills(skills ...Skill) Option

WithSkills registers skills, surfaced to the model through the system prompt (see FormatSkillsPrompt).

func WithSkillsDir

func WithSkillsDir(dir string) Option

WithSkillsDir loads skills from a directory tree at construction time (see LoadSkills); New fails when loading does.

func WithSkillsFS

func WithSkillsFS(fsys fs.FS) Option

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

func WithSystem(s string) Option

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

func WithSystemSuffix(s string) Option

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

func WithTemplatesDir(dir string) Option

WithTemplatesDir loads prompt templates from a directory at construction time (see LoadTemplates); New fails when loading does.

func WithTemplatesFS

func WithTemplatesFS(fsys fs.FS) Option

WithTemplatesFS is WithTemplatesDir for any fs.FS.

func WithTools

func WithTools(tools ...agent.Tool) Option

WithTools sets the agent tool set.

type Phase

type Phase string

Phase is what a Harness is currently doing.

const (
	PhaseIdle          Phase = "idle"
	PhaseTurn          Phase = "turn"
	PhaseCompaction    Phase = "compaction"
	PhaseBranchSummary Phase = "branch_summary"
)

Harness phases.

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

func (r Repo) Create(id string, extra map[string]string) (*JSONLStore, error)

Create starts a new stored session. An empty id gets a generated one.

func (Repo) Delete

func (r Repo) Delete(id string) error

Delete removes the stored session with the given id.

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.

func (Repo) Open

func (r Repo) Open(id string) (*JSONLStore, error)

Open opens the stored session with the given id.

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

func NewSession(store Store) (*Session, error)

NewSession loads (or starts) a session over the given store.

func (*Session) AppendCompaction

func (s *Session) AppendCompaction(summary, firstKeptID string, tokensBefore int) (string, error)

AppendCompaction commits a compaction: summary replaces all context before firstKeptID (see Session.Context).

func (*Session) AppendCustom

func (s *Session) AppendCustom(customType string, data ai.JSON) (string, error)

AppendCustom records application data; it never enters model context.

func (*Session) AppendMessage

func (s *Session) AppendMessage(msg ai.Message, usage *ai.Usage) (string, error)

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

func (s *Session) AppendModelChange(provider ai.Provider, modelID string) (string, error)

AppendModelChange records a model switch effective for later prompts.

func (*Session) CommonAncestor

func (s *Session) CommonAncestor(a, b string) (string, error)

CommonAncestor returns the deepest entry present on both branches ending at a and b ("" when they only share the root).

func (*Session) Context

func (s *Session) Context() (Context, error)

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

func (s *Session) Entries() []Entry

Entries returns all entries in append order.

func (*Session) Entry

func (s *Session) Entry(id string) (Entry, bool)

Entry returns the entry with the given ID.

func (*Session) Labels

func (s *Session) Labels() map[string]string

Labels returns the effective labels by entry ID.

func (*Session) LeafID

func (s *Session) LeafID() string

LeafID returns the active tree position ("" when at the root).

func (*Session) Metadata

func (s *Session) Metadata() SessionMetadata

Metadata identifies the underlying stored session.

func (*Session) MoveTo

func (s *Session) MoveTo(entryID, summary string) error

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

func (s *Session) Name() string

Name returns the session's current name, or "".

func (*Session) Path

func (s *Session) Path() []Entry

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

func (s *Session) SetLabel(targetID, label string) error

SetLabel attaches a label to the target entry; an empty label clears it.

func (*Session) SetName

func (s *Session) SetName(name string) error

SetName records the session's human-readable name.

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

func LoadSkillFS(fsys fs.FS, p string) (Skill, error)

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

func LoadSkills(dir string) ([]Skill, error)

LoadSkills loads skills from a directory tree (see LoadSkillsFS).

func LoadSkillsFS

func LoadSkillsFS(fsys fs.FS) ([]Skill, error)

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

func (s Skill) CopyMetadata() map[string]string

CopyMetadata returns a mutable copy of metadata for hosts that need to add their own fields without mutating a catalog value.

func (Skill) ModelInvocable

func (s Skill) ModelInvocable() bool

ModelInvocable reports whether the model may discover and activate the Skill.

func (Skill) UserInvocable

func (s Skill) UserInvocable() bool

UserInvocable reports whether a user may explicitly select the Skill.

type SkillActivation

type SkillActivation struct {
	Skill       Skill
	ActivatedAt time.Time
}

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

type SkillDiagnostic struct {
	Code    string
	Field   string
	Message string
}

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

type SkillResource struct {
	Path    string
	Size    int64
	Text    bool
	Content string
}

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

type TreeLimits struct {
	MaxNodes int
	MaxDepth int
}

TreeLimits bound a Session.Tree projection. Zero values select defaults.

type TreeNode

type TreeNode struct {
	ID           string
	ParentID     string
	Kind         Kind
	CreatedAt    time.Time
	Depth        int
	Label        string
	Current      bool
	OnActivePath bool
	HasSummary   bool
	Compacted    bool
}

TreeNode is one durable non-leaf-marker entry in a TreeSnapshot.

type TreeSnapshot

type TreeSnapshot struct {
	SessionID  string
	Name       string
	LeafID     string
	Nodes      []TreeNode
	TotalNodes int
	MaxDepth   int
	Truncated  bool
}

TreeSnapshot is an immutable, append-ordered projection of a Session graph. Nodes are flat so renderers never recurse over untrusted depth.

Jump to

Keyboard shortcuts

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