learn

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package learn is the agent's learning loop: it turns a finished run into durable knowledge the agent can reuse, so experience compounds across runs instead of being discarded when a conversation ends.

This package is the capture half. A Curator takes the outcome of a run, asks a Distiller what reusable lesson it taught, and writes the result to the skill and memory stores, stamped with the run's provenance. Capture is outcome-gated: only a run that actually converged is distilled, so the agent never crystallizes a lesson from work that failed or stalled. That gate, plus the provenance stamp, is what keeps a self-curating skill set from degrading into noise: every stored lesson is traceable to a run that met its goal.

A skill can also carry an executable check. When a Verifier is supplied, the Curator runs that check in a sandbox before crystallizing the skill: a skill whose check runs and fails is proven broken and dropped, never stored; one that passes is tagged verified; one with no runnable check is kept but tagged unverified. A captured procedure is thus trusted in proportion to evidence that it actually works, rather than on the model's say-so alone.

The Distiller is a port. The model-backed implementation (ModelDistiller) asks a language model to summarize the run; a test supplies a scripted one. The Curator itself contains no model dependency, so its gating, provenance, and persistence are deterministic and fully testable.

Index

Constants

View Source
const DistillAction = "learn.distill"

DistillAction is the dispatch action name a distillation runs under, so the model call that turns a finished run into lessons is admitted, traced, and recorded on the spine under a stable name rather than reaching the model on a side channel.

View Source
const VerifyAction = "learn.verify"

VerifyAction is the dispatch action name a skill check runs under, so a verification appears on the event spine and in traces under a stable, greppable name alongside the tools the agent invokes.

Variables

This section is empty.

Functions

func Confidence

func Confidence(uses, wins int) float64

Confidence estimates how reliably a skill helps, as the Wilson lower bound of its win rate (wins over uses). The lower bound is the principled small-sample estimate: it is conservative when evidence is thin, so a skill that won its only use does not outrank one that won 50 of 55, and it rises toward the raw win rate as evidence accumulates. It is 0 when the skill has never been used.

Grading a skill by its confirmed outcomes is what makes this learning loop self-correcting where a recency- or usage-only one is not: a skill that keeps appearing in failing runs decays however often it is touched.

func Decay

func Decay(ctx context.Context, skills state.SkillStore, scope state.Scope, p DecayPolicy) ([]state.Skill, error)

Decay archives the skills in scope that the policy judges unhelpful, returning the ones it archived. Archiving is a soft delete (a tombstone), so a retired skill is recoverable and never silently lost, and a skill with little evidence is kept because it has not yet earned retirement.

func Reinforce

func Reinforce(ctx context.Context, skills state.SkillStore, slugs []string, success bool) error

Reinforce records one run's outcome against the skills it recalled: each is used once more, and once more a win if the run succeeded. The evidence accrues on the skill so it can be ranked and retired by how it actually performs. A duplicate or empty slug, or one with no live skill, is skipped; the first store error is returned. It is a read-modify-write per skill, which is safe under the agent's single-writer local store.

Types

type Captured

type Captured struct {
	Skills   []state.Skill
	Memories []state.MemoryItem
	Dropped  []Lesson
}

Captured is what a Curate call persisted, returned for audit and for the caller to surface ("learned 2 skills, 1 memory from this run"). Dropped holds skill lessons rejected because their check ran and failed, so a caller can report what was discarded as broken rather than silently losing it.

type Curator

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

Curator is the capture half of the learning loop: it distills a converged run into lessons and persists them, stamped with provenance. It holds no model dependency of its own; the Distiller supplies that. With a Verifier set, a skill's executable check gates whether it is crystallized.

func NewCurator

func NewCurator(d Distiller, skills state.SkillStore, memories state.MemoryStore, opts ...Option) *Curator

NewCurator builds a Curator over a distiller and the skill and memory stores it writes to.

func (*Curator) Curate

func (c *Curator) Curate(ctx context.Context, o Outcome) (Captured, error)

Curate distills o and persists the resulting lessons, returning what it stored. It is outcome-gated: a run that did not converge yields nothing, so a failed or stalled run never crystallizes a lesson. A lesson missing a body is skipped. The first store error aborts and is returned, with whatever was stored before it.

type DecayPolicy

type DecayPolicy struct {
	MinUses       int
	MinConfidence float64
}

DecayPolicy decides when a skill is retired: once it has at least MinUses of evidence and its confidence is below MinConfidence, it has been tried enough and helped too rarely to keep surfacing.

func DefaultDecay

func DefaultDecay() DecayPolicy

DefaultDecay retires a skill only after a fair number of uses with a poor confirmed win rate, so a still-unproven skill keeps its chance.

type Distiller

type Distiller interface {
	Distill(ctx context.Context, o Outcome) ([]Lesson, error)
}

Distiller turns a run outcome into zero or more lessons. Returning none is a valid result: not every run teaches something worth keeping.

type DistillerOption

type DistillerOption func(*ModelDistiller)

DistillerOption configures a ModelDistiller.

func WithMaxTokens

func WithMaxTokens(n int) DistillerOption

WithMaxTokens caps the output length requested of the model.

func WithSystem

func WithSystem(s string) DistillerOption

WithSystem overrides the standing instruction framing the distillation.

type GovernedDistiller

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

GovernedDistiller routes a Distiller's model call through the dispatch waist, so summarising a finished run into skills and memory is admitted, traced, and recorded like every other action rather than reaching the model on a side channel. It wraps any Distiller; the lessons stay in this closure and never reach dispatch.

func NewGovernedDistiller

func NewGovernedDistiller(inner Distiller, opts ...dispatch.Option) *GovernedDistiller

NewGovernedDistiller wraps inner so its distillation runs through a dispatcher built from opts. Pass the same admitter, event sink, and observability the rest of the run uses; with no options the dispatcher applies standalone defaults and the distillation is recorded but ungoverned.

func (*GovernedDistiller) Distill

func (g *GovernedDistiller) Distill(ctx context.Context, o Outcome) ([]Lesson, error)

Distill governs the distillation as a scoped action and returns the inner lessons. If admission declines the distillation it never runs, so nothing is captured and no error is raised (governance opting out is not a failure); a cancelled context is a hard error. An error from the inner distiller itself propagates, so a broken distiller stays visible rather than silently dropping knowledge.

type GovernedVerifier

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

GovernedVerifier routes each skill check through the dispatch waist before it runs, so a verification is admitted, traced, and recorded on the event spine exactly like any tool the agent invokes. Executing a model-proposed command is itself an action with consequences; sending it through the same chokepoint means one capability gate, audit trail, and replay path cover it, rather than letting verification be a side channel that bypasses governance.

func NewGovernedVerifier

func NewGovernedVerifier(inner Verifier, opts ...dispatch.Option) *GovernedVerifier

NewGovernedVerifier wraps inner so its checks run through a dispatcher built from opts. Pass the same admitter, event sink, and observability the rest of the agent uses (dispatch.WithAdmitter / WithEventSink / WithObservability) so verifications share its governance and spine; with no options the dispatcher applies standalone defaults and the check is recorded but ungoverned.

func (*GovernedVerifier) Verify

func (g *GovernedVerifier) Verify(ctx context.Context, l Lesson, scope state.Scope) (Verdict, error)

Verify governs the check as a scoped action and returns the inner verdict. The lesson and verdict stay in this closure; the dispatcher brackets the run without seeing them. A cancelled context is a hard error, matching the inner verifier. Any other failure (admission rejected the check, or the inner verifier failed for a non-cancel reason) means the check did not run to a clean verdict, so the skill is reported unproven rather than broken: governance can decline a verification without that being mistaken for evidence the skill is wrong.

type Lesson

type Lesson struct {
	Kind  LessonKind
	Title string
	Body  string
	Tags  []string
	// Check is an optional shell command that verifies a skill works: it is run in
	// a sandbox and a zero exit code means the skill is sound. Empty means the skill
	// has no executable check. Ignored for memory items, which are not executable.
	Check string
}

Lesson is one piece of durable knowledge a Distiller proposes from a run. Title names a skill (and seeds its slug); for a memory item it is an optional label. Body is the lesson itself.

type LessonKind

type LessonKind string

LessonKind selects where a distilled lesson is stored: a reusable Skill (a procedure the agent can apply again) or a Memory item (a fact or observation to recall).

const (
	// LessonSkill is a reusable procedure, stored as a skill keyed by its slug so a
	// later run with the same lesson updates rather than duplicates it.
	LessonSkill LessonKind = "skill"
	// LessonMemory is a fact or observation, stored as an append-only memory item.
	LessonMemory LessonKind = "memory"
)

type ModelDistiller

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

ModelDistiller is a Distiller backed by a language model: it summarizes a run into lessons by asking the model for a strict JSON array, then parsing it. It is the production distiller; the Curator stays model-free and testable behind the Distiller port.

func NewModelDistiller

func NewModelDistiller(m llm.Model, opts ...DistillerOption) *ModelDistiller

NewModelDistiller builds a model-backed distiller over m.

func (*ModelDistiller) Distill

func (d *ModelDistiller) Distill(ctx context.Context, o Outcome) ([]Lesson, error)

Distill asks the model for lessons learned from o and parses its JSON reply. A reply with no JSON array is treated as "nothing to capture" (no error); a reply whose array is malformed is a terminal fault, so a broken distiller is visible rather than silently dropping knowledge.

type Option

type Option func(*Curator)

Option configures a Curator.

func WithVerifier

func WithVerifier(v Verifier) Option

WithVerifier gates skill capture on an executable check: a skill whose check runs and fails is dropped, one that passes is tagged verified, and one with no runnable check is kept tagged unverified. Without a verifier, skills are kept as captured (untagged by verification).

type Outcome

type Outcome struct {
	Objective  string
	Result     string
	Transcript []llm.Message
	Converged  bool
	Scope      state.Scope
	// Source identifies the run this knowledge came from (a session or stream id),
	// stamped onto every captured item so a lesson is always traceable to its run.
	Source string
}

Outcome is the finished run a Curator learns from: what it set out to do, what it produced, the conversation it took to get there, whether it converged, the scope the knowledge belongs to, and a provenance string identifying the run.

type RegradeResult

type RegradeResult struct {
	Checked     int           // skills with a check that were re-run
	Reconfirmed []state.Skill // checks re-ran and still passed
	Retired     []state.Skill // checks re-ran and now failed, so they were archived
}

RegradeResult summarizes a regrade pass: the skills whose checks were re-run and what became of them.

func Regrade

func Regrade(ctx context.Context, skills state.SkillStore, scope state.Scope, v Verifier) (RegradeResult, error)

Regrade re-runs the verification check of every checkable skill in scope and brings the corpus back in line with current ground truth: a skill whose check still passes is re-confirmed (tagged verified), and one whose check now fails is retired (archived, recoverable). Skills with no check, or whose check could not run, are left untouched.

This is what a write-once skill file cannot do: knowledge captured earlier is re-graded as the environment changes, so a procedure that has quietly stopped working is caught and removed rather than recalled forever. A nil verifier is a no-op; the first store or verifier error aborts and is returned.

type SandboxFactory

type SandboxFactory func(ctx context.Context) (sandbox.Sandbox, error)

SandboxFactory creates the sandbox a single verification runs in. A fresh sandbox per check keeps verifications independent and confines a check the same way the agent's own tools are confined.

type SandboxVerifier

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

SandboxVerifier runs a skill's check as a shell command in a sandbox and treats a zero exit code as verification. Running the candidate before keeping it is what separates a procedure that works from one that merely sounds plausible; doing it inside the sandbox is what makes executing model-proposed commands safe.

func NewSandboxVerifier

func NewSandboxVerifier(f SandboxFactory) *SandboxVerifier

NewSandboxVerifier builds a verifier that runs each check in a sandbox from f.

func (*SandboxVerifier) Verify

func (v *SandboxVerifier) Verify(ctx context.Context, l Lesson, _ state.Scope) (Verdict, error)

Verify runs l's check in a fresh sandbox. A skill with no check is unproven (Ran=false), not broken. A sandbox that cannot start the check is unproven too, not an error, so a verification-environment hiccup keeps the skill (tagged unverified) rather than discarding it; only a cancelled context is a hard error.

type Verdict

type Verdict struct {
	Verified bool
	Ran      bool
	Detail   string
}

Verdict is the outcome of verifying a skill's check. Ran reports whether the check actually executed: a check that ran and did not verify is proven broken, while one that could not run (none was supplied, or the sandbox failed to start it) is simply unproven. Detail is a short human-readable reason.

type Verifier

type Verifier interface {
	// Verify runs l's check and reports the verdict. scope is the run the lesson
	// belongs to: it carries through to governance and the audit trail when the
	// check is dispatched as an action (see GovernedVerifier), so a verification is
	// attributed to the same scope as the work that proposed it.
	Verify(ctx context.Context, l Lesson, scope state.Scope) (Verdict, error)
}

Verifier decides whether a skill lesson is sound by running its check. It is the gate that makes a captured procedure trustworthy in proportion to evidence: the Curator crystallizes a verified skill, drops a proven-broken one, and keeps an unproven one tagged unverified.

Jump to

Keyboard shortcuts

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