agentprofile

package
v0.0.0-...-86b21bc Latest Latest
Warning

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

Go to latest
Published: May 8, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package agentprofile implements the persistent agent profile (gm-v5z2.2, work-planning.md §4 Layer 1.2).

Sister to the session profile in internal/planner. Same shape (concepts {tag:weight} + files {path:weight}), keyed on AgentRef.ID, but with two key differences:

  • Survives `gt handoff`. A new session inherits its agent's profile as a warm starting point, then accumulates session- specific weight on top.

  • Different question. Session profile answers "what is this session warm on right now?" Agent profile answers "what is this agent good at over weeks?" An agent who has been deep in the planner across multiple sessions stays primed on planner concepts even on its first bead of a fresh session.

Decay: per-day half-life (default 14d), distinct from the session profile's per-bead-event half-life (default 5).

Writes: the retrospective hook (§7) writes both profiles on bead completion. Session row gets the bead's actual concepts/ files at full weight; agent row gets the same contribution scaled by 1 / (lifetime bead count) so a single bead doesn't dominate a long-running agent's profile.

Reads: §4 Layer 3.2 Affinity reads BOTH profiles, weighted (default 0.7 session + 0.3 agent — tunable). The mix lives in the scoring layer; this package only owns persistence + decay.

Index

Constants

View Source
const DefaultDecayHalfLifeDays = 14.0

DefaultDecayHalfLifeDays is the per-day half-life of agent concept / file weights. 14 days matches the spec rule of thumb — a hand-off after a couple weeks of focused work still gives the new session a warm starting point, but a long quiet period fades the priming back toward neutral.

Variables

This section is empty.

Functions

func AgeByDays

func AgeByDays(in map[planner.ConceptTag]float64, days, halfLifeDays float64) map[planner.ConceptTag]float64

AgeByDays returns a copy of `in` with every weight multiplied by 0.5 ^ (days / halfLifeDays). Pure: input is not mutated.

halfLifeDays <= 0 falls back to DefaultDecayHalfLifeDays. days <= 0 is a no-op (returns a copy with weights unchanged) so the same-day re-read path stays free of arithmetic noise.

nil `in` returns nil so callers can chain without nil-checking.

func AgeFilesByDays

func AgeFilesByDays(in map[string]float64, days, halfLifeDays float64) map[string]float64

AgeFilesByDays is the string-keyed twin of AgeByDays for the AgentProfile.Files map.

Types

type AgentProfile

type AgentProfile struct {
	AgentID core.AgentID `json:"agent_id"`

	// Concepts and Files are recency-decayed weight maps. Decay is
	// per-day (vs per-event for the session profile) so an idle
	// agent doesn't accumulate inflated priming just from absence
	// of new bead events.
	Concepts map[planner.ConceptTag]float64 `json:"concepts,omitempty"`
	Files    map[string]float64             `json:"files,omitempty"`

	// LifetimeBeadCount is the number of bead-completion events
	// this agent has seen across every session. Used by
	// RecordCompletion to scale a new bead's contribution by
	// 1 / count so a single bead doesn't dominate the profile.
	LifetimeBeadCount int64 `json:"lifetime_bead_count"`

	// LastActivityAt is the wall-clock of the most recent bead
	// completion. AgeByDays uses (now - last_activity_at) as the
	// decay input; a fresh profile (no completions yet) carries a
	// zero-value LastActivityAt and AgeByDays is a no-op.
	LastActivityAt time.Time `json:"last_activity_at"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

AgentProfile is the per-agent persistent state. Mirrors the shape of planner.SessionProfile so the affinity scorer can read both with the same map types.

type CompletionEvent

type CompletionEvent struct {
	AgentID  core.AgentID
	BeadID   core.WorkItemID
	Concepts []planner.ConceptTag
	Files    []string

	// HalfLifeDays overrides DefaultDecayHalfLifeDays per call.
	HalfLifeDays float64
	// ContributionScale, when set > 0, overrides the default
	// 1/(lifetime+1) scaling. Tests pass 1.0 for predictable
	// arithmetic; production leaves it 0 and accepts the default.
	ContributionScale float64
	// Now is injected for deterministic test runs. nil → time.Now.
	Now func() time.Time
}

CompletionEvent is the input to RecordCompletion — what the retrospective hook knows when a bead lands. Concepts/Files come from the bead's actual (post-merge) values; ContributionScale, when zero, defaults to 1 / max(LifetimeBeadCount+1, 1) so a single bead doesn't dominate a long-running agent's profile.

type Store

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

Store persists AgentProfile rows. Wraps a *sql.DB the caller owns; nil db disables every method (callers branch on nil-store rather than feature-flagging).

func NewStore

func NewStore(db *sql.DB) *Store

NewStore wraps the given *sql.DB. Returns nil when db is nil so the wiring stays branch-free at the call site.

func (*Store) EnsureSchema

func (s *Store) EnsureSchema(ctx context.Context) error

EnsureSchema applies planner.SchemaSQL — the same DDL bundle the rest of the planner uses — so a process that brings up the agent-profile store also brings up session_profiles, scorer_ grades, etc. Safe to call multiple times.

func (*Store) Get

func (s *Store) Get(ctx context.Context, agentID core.AgentID) (*AgentProfile, error)

Get reads the profile for agentID. Returns (nil, nil) when no row exists — matches the planner readers' "absent ≠ error" convention.

func (*Store) List

func (s *Store) List(ctx context.Context) ([]AgentProfile, error)

List returns every profile, sorted by agent id. The selection layer doesn't fan over this — it joins per-session — but the CLI's `gemba agent profile --all` and operator audits read it.

func (*Store) RecordCompletion

func (s *Store) RecordCompletion(ctx context.Context, ev CompletionEvent) (*AgentProfile, error)

RecordCompletion ages the existing profile by the days elapsed since LastActivityAt, scales the bead's contribution by 1/(lifetime+1), merges, bumps the lifetime counter, and stamps LastActivityAt = Now. Returns the resulting profile so callers can echo / publish without an extra read.

func (*Store) Upsert

func (s *Store) Upsert(ctx context.Context, p *AgentProfile) error

Upsert writes p verbatim. Used by tooling and tests that want to seed a profile without going through RecordCompletion. The retrospective hook should call RecordCompletion instead.

Jump to

Keyboard shortcuts

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