store

package
v0.4.10 Latest Latest
Warning

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

Go to latest
Published: Aug 8, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Favorite setters for the DB-owned entities. Memories and notes are not here: their favorite flag lives in file frontmatter (the source of truth) and reaches the index through the files layer's re-index, never a direct UPDATE. A star is metadata, not authorship, so none of these bump updated_at -- starring must not churn recency sorts or the briefing recency trim.

Gamification: the day tape and the personal-records latch behind the Now screen's arcade layer.

Every number here is judged from recorded activity -- the tasks table and the event log -- never invented. Day boundaries follow the localDay conventions momentum established (local midnights advanced with AddDate, never 24h arithmetic). Records only ever move forward, like the maturity latch: a quiet day is an empty tape, not a lost record, and deleting the settings row merely resets the ledger to be re-earned.

Project isolation: the policy fence against agent-to-agent knowledge leakage. Every enforcement point (write funnel, read funnel, by-id paths, briefing, gardener) routes through CanRead/CanWrite so the whole matrix lives here and in one table-driven test. Like the favorite setters, the state UPDATE never bumps updated_at -- isolation is owner curation, not authorship, and must not churn recency sorts.

Momentum: latched project maturity stages.

Each project earns a stage -- seedling, sprouting, established, deep-rooted -- from real thresholds over age, memory count, event volume, and reach, following the utility-activation pattern: exported threshold constants (so the computation and the console's "why not yet" tooltip agree by construction) and a stored latch that never flaps. Stages never regress: a project that sheds memories keeps the stage it earned, and a crossing is minted exactly once.

Momentum: the latched milestone ledger.

A short, honest set of once-ever milestones -- counts a project actually crossed and firsts that actually happened -- minted as milestone.reached events via events.RecordOnce, whose kind+item_id guarded insert is the once-ever latch. The plan.shipped settlement (once per plan, on the task transition that closes its last step as done) is minted here too, through the same latch. Every payload states its exact claim verbatim and the real count behind it ("100 memories written in seamless", count 100), never a score-shaped invention. The checks piggyback on the event recorder's write path, where the counts change: there is no polling pass, and while the momentum feature is off no threshold check runs and nothing is minted (the gamification-arcade discipline).

Momentum: the capture streak behind the activity calendar.

The streak counts covered local days -- days on which at least one session left a durable artifact (the same covered-ness test as SessionCoverage), so it rewards knowledge capture, not raw usage. Day boundaries follow the localBucketAxis conventions: local midnights advanced with AddDate, never 24h arithmetic, so DST transitions cannot split or merge a day.

The longest-ever run is cached in a settings row and extended incrementally, finalized through yesterday; only the days since the cached watermark are ever queried, so a load never rewalks the full session history. Today is always computed live and never persisted: a day is finalized only once it is over, which also gives a session created late yesterday until the first load of today for its artifact to land. (An artifact landing two or more days after its session's creation day can still be missed by an already-finalized day -- accepted: the cache only ever underreports, and longest never regresses.)

Human-facing search over stable identifiers and structured entities. Memories and notes still get their text candidates from FTSSearch (fused with semantic hits by internal/retrieve), but their ids, memory names, and note slugs are resolved here from the index mirrors so an exact reference cannot be lost in tokenized FTS results. Tasks, sessions, projects, plans, and trials have no FTS mirror and match with LIKE over their short, low-cardinality labels.

Every query takes the search text as a bound parameter escaped through escapeLikePrefix, so a literal % or _ matches itself rather than acting as a wildcard.

Package store owns the SQLite database: connection setup, schema migrations, FTS5, and embedding storage. It takes a database path (not the config) to stay a leaf dependency. Files remain the source of truth for durable knowledge; the *_index tables and the fts virtual table are rebuildable mirrors.

Task CRUD and the declarations the rest of the tasks_*.go files share. The dependency-aware queue splits across: tasks_claim.go (leases), tasks_deps.go (the edge DAG), tasks_query.go (the ready/blocked/list reads), tasks_plans.go (plan rollups) and tasks_scan.go (row scanning).

Lease-based task claiming: the compare-and-set claim and the four ways a claim ends (holder release, owner force-release, session teardown, lease expiry). Expiry is enforced lazily inside ClaimTask -- there is no background sweeper.

The task dependency DAG: edge insertion with dangling-reference and cycle rejection, plus the depends-on reads (single and batched) the queue builds on.

Plan rollups: a plan's status is derived from its step tasks, never stored.

The task read surface: the ready queue, the blocked list with each task's blockers, and the plain per-project/per-plan listings. "Ready" means open with no blocker still open or in_progress -- one rule, repeated as a NOT EXISTS in each query here and mirrored by ClaimTask's branch (a) and ActivePlans.

Row scanning for the tasks table. scanTasksWithDeps is the batched path every multi-row task query drains through, so listing N tasks costs 1 query for the rows plus a handful for their edges -- not N+1.

Index

Constants

View Source
const (
	SnippetStartMark = "\x01"
	SnippetEndMark   = "\x02"
)

Snippet marks wrap the matched terms inside a SnippetHit.Snippet. They are control characters rather than "<mark>" because a snippet is raw item text: handing HTML to the caller would make every consumer responsible for telling our markup from the item's own. A control char cannot survive HTML escaping as markup, so a consumer escapes first and substitutes second (see the console's highlightSnippet), and a sentinel a writer embedded in their own body can only ever produce a stray inert <mark>, never an injection.

View Source
const (
	ProposalPending   = "pending"
	ProposalApplied   = "applied"
	ProposalDismissed = "dismissed"
	ProposalHidden    = "hidden"
)

Proposal statuses. A rejection has two strengths: ProposalDismissed is the regular one, suppressing the pattern only until new evidence for it arrives, while ProposalHidden is the forever block the owner asks for explicitly.

View Source
const (
	ProposalMerge        = "merge"
	ProposalArchive      = "archive"
	ProposalDigest       = "digest"
	ProposalConsolidate  = "consolidate"
	ProposalReproject    = "reproject"     // move one memory to another project
	ProposalSplit        = "split"         // set up child/shared projects + family for a project split
	ProposalAbandonPlan  = "abandon_plan"  // retag a never-approved captured plan plan-status:abandoned
	ProposalMemoryWanted = "memory_wanted" // agents repeatedly searched for knowledge that does not exist
	ProposalToolError    = "tool_error"    // agents keep hitting the same tool-call or hook-stage error
	ProposalRekind       = "rekind"        // change one memory's kind classification in place
	ProposalShipPlan     = "ship_plan"     // retag an implemented-but-unapproved captured plan plan-status:shipped
	// ProposalRelocate moves a GLOBAL memory back inside the project whose
	// sessions wrote it, after that project tightened its isolation. Its effect
	// is a reproject's, but the decision is not: the evidence is provenance (a
	// leak that predates the fence), not scope tidying, so the inbox groups and
	// explains it on its own terms.
	ProposalRelocate = "relocate"
	// ProposalMergePlans folds a never-approved captured plan into an existing
	// composition that already carries the steps for the same work. It is the
	// third answer the stale-plan pass can give (beside abandon and ship) and
	// the only one that moves anything: the capture's notes are retagged onto
	// the surviving slug instead of being settled where they sit.
	ProposalMergePlans = "merge_plans"
)

Proposal kinds (mirrors the gardener_proposals.kind CHECK constraint).

View Source
const (
	StageSproutingMinAgeDays  = 7
	StageSproutingMinMemories = 5
	StageSproutingMinEvents   = 25

	StageEstablishedMinAgeDays  = 30
	StageEstablishedMinMemories = 15
	StageEstablishedMinEvents   = 250

	StageDeepRootedMinAgeDays  = 90
	StageDeepRootedMinMemories = 40
	StageDeepRootedMinEvents   = 1000
	StageDeepRootedMinReachPct = 40
)

Maturity thresholds. A stage requires EVERY listed bar; age counts from the project's registration (projects.created_at -- an unregistered slug stays a seedling), volume from the project's total event count, and deep-rooted additionally asks that knowledge demonstrably reaches the work (windowed reach at evaluation time; the latch keeps the stage once earned).

View Source
const (
	EmbedderModeAuto = "auto"
	EmbedderModeOff  = "off"
)

Embedder override modes. Auto defers to the LLM config (embeddings run when a provider is configured); Off disables them regardless of config.

View Source
const (
	// DefaultTrialQueryLimit is used when TrialFilter.Limit is left at zero.
	DefaultTrialQueryLimit = 20
	// MaxTrialQueryLimit bounds both the result slice and the metrics-filter
	// over-fetch query. The console's largest reader asks for 200 rows.
	MaxTrialQueryLimit = 200
)
View Source
const (
	UtilityReadyMinAgeDays  = 14                  // first demand event at least this old
	UtilityReadyMinEvents   = 20                  // session-deduped demand events in the window
	UtilityReadyMinMemories = 10                  // distinct memories that demand touched
	UtilityReadyWindow      = 30 * 24 * time.Hour // trailing window for the two counts above
)

Utility-activation readiness thresholds. A project's briefing switches to utility-blended ordering (in "auto" mode) only once its demand history is deep enough to rank on: old enough that decay has meaning, busy enough that the scores are not one session's noise, and broad enough that ordering by them changes more than a couple of lines. All three must hold. Exported so the gardener (which latches) and the console (which shows progress toward them) agree by construction.

View Source
const DefaultFunnelFollow = 24 * time.Hour

DefaultFunnelFollow is how long after an injection a query-gated pull of the same item still counts as that injection's funnel conversion. A session-scale horizon: reads driven by an injection happen within the same working day, while a pull weeks later says nothing about the injection that preceded it.

View Source
const EventMilestoneReached core.EventKind = "milestone.reached"

EventMilestoneReached is the milestone ledger's event kind: a latched milestone, minted at most once per item_id (the milestone key) via events.RecordOnce. Defined here rather than in internal/core because the milestone vocabulary -- keys, thresholds, claims -- lives in this file and the ledger's consumers read them from here.

View Source
const MilestoneRecallAnsweredThreshold = 1000

MilestoneRecallAnsweredThreshold is the per-project count of answered recalls -- recall-tool calls that returned at least one hit -- that mints a milestone. Kind-browse listings are passive exposure, not answered recalls, and do not count (closed-loop-utility-signal-contract).

View Source
const PlanFinishLinePct = 80

PlanFinishLinePct is the closed-step share at which an active plan is "at the finish line" -- the qualification the momentum feature's Overview card and briefing emphasis both derive from, so the two surfaces agree by construction.

View Source
const SettingBriefingConfig = "briefing_config"

SettingBriefingConfig is the settings key holding the console-saved briefing override: a JSON-encoded config.Briefing. When present it layers over the file/env briefing config (see BriefingConfig), so the owner can tune the SessionStart injection from the console without editing seamless.yaml or restarting the daemon.

View Source
const SettingCaptureStreak = "momentum_capture_streak"

SettingCaptureStreak is the settings key caching the momentum capture streak: {"longest":N,"run":R,"through":"2006-01-02"}, finalized through the named local day. Deleting the row is safe -- the next load rebuilds it from the session table.

View Source
const SettingEmbedderMode = "embedder_mode"

SettingEmbedderMode is the settings key holding the owner's embedder override. The only stored value is EmbedderModeOff; EmbedderModeAuto clears the row, so "auto" and "no override" are the same state. The daemon reads it once at serve start (main.go resolves the embedder before anything holds it), so a console change applies from the next restart.

View Source
const SettingFeaturesConfig = "features_config"

SettingFeaturesConfig is the settings key holding the optional-features override: a JSON-encoded config.Features. When present it layers over the file/env features config (see FeaturesConfig), so the owner can turn optional features on and off from the console without editing seamless.yaml or restarting the daemon.

Two writers reach this row: the console Settings form, and the one-time grandfather migration that keeps research enabled on installations that already hold trial data. That is why the console calls it a "stored override" rather than implying the owner set it.

View Source
const SettingGamificationRecords = "gamification_records"

SettingGamificationRecords is the settings key holding the personal-records ledger, as EvaluateGamificationRecords maintains it. Deleting the row resets the records; nothing else references it.

View Source
const SettingProjectFamilies = "project_families"

SettingProjectFamilies is the settings key holding project families: a JSON object {family-name: [slug, ...]}. Sibling briefings surface recent findings from a project's family members.

View Source
const SettingProjectStages = "momentum_project_stages"

SettingProjectStages is the settings key holding the latched stage per project: {"projects":{"slug":{"stage":"sprouting","since":"..."}}}. Deleting the row is safe in the sense that nothing breaks -- but unlike a cache it IS the latch, so earned stages would be recomputed from current facts.

View Source
const SettingRepoProjectMap = "repo_project_map"

SettingRepoProjectMap is the settings key holding the cwd->project-slug map: a JSON object {absolute-path: slug}. The hooks and session_start resolve an agent's working directory to a project slug through it.

View Source
const SettingUtilityActivation = "utility_activation"

SettingUtilityActivation is the settings key holding the per-project utility-ranking activation state: which projects' briefings order their memory index by the utility blend. The gardener latches projects in as their demand data matures (see gardener.evaluateUtilityActivation); the owner can force a project on or off from the console. The bounded recall/prompt boosts are always on and are not gated here -- this state guards only the consequential surface, briefing index re-ordering.

Variables

View Source
var ErrFamilyExists = errors.New("store: project family already exists")

ErrFamilyExists is returned when a family create or rename would overwrite a different existing family.

View Source
var ErrFamilyNoMembers = errors.New("store: project family has no members")

ErrFamilyNoMembers is returned when a family replacement contains no usable project slugs. Empty families are not persisted.

View Source
var ErrFamilyNotFound = errors.New("store: project family not found")

ErrFamilyNotFound is returned by RemoveFamilyMembers when the named family does not exist.

View Source
var ErrInvalidTrialQueryLimit = errors.New("invalid trial query limit")

ErrInvalidTrialQueryLimit marks a TrialFilter limit outside the store API.

View Source
var ErrIsolationHasChildren = errors.New("project has child projects")

ErrIsolationHasChildren is returned by TightenProjectIsolation when the project still has child projects. Isolation requires a standalone project, and re-parenting someone else's children is not this call's decision to make.

View Source
var ErrIsolationStandalone = errors.New("isolation requires a standalone project")

ErrIsolationStandalone is returned by SetProjectParent when either side of a parent link is isolated. It is the same topology rule ErrIsolationHasChildren enforces from the tighten side, judged from the link side: isolation requires a standalone project, so a fenced project may neither take a parent nor be one.

It is deliberately NOT an alias of gardener.ErrIsolatedProject: that sentinel answers a different question (whether a fenced project may be SPLIT), and one name for two refusals would make errors.Is agree where the two do not.

View Source
var ErrNotATighten = errors.New("not a tighten")

ErrNotATighten is returned when the requested state is not tighter than the current one. Loosening is immediate and ceremony-free -- nothing has leaked while the fence was up -- so it goes through SetProjectIsolation instead.

View Source
var ErrProjectNotFound = errors.New("project not found")

ErrProjectNotFound is returned when a slug has no projects-table row.

View Source
var ErrSessionIdentityExists = errors.New("store: ambient session identity already exists")

ErrSessionIdentityExists is returned when an ambient row already owns the same full external session id and client discriminator. It is distinct from a display-name collision so callers can safely converge a concurrent create on the existing authoritative identity.

View Source
var ErrSessionNameExists = errors.New("store: session name already exists")

ErrSessionNameExists is returned by CreateSession when the name is already taken (sessions.name is UNIQUE). It mirrors ErrSlugExists so a caller racing to create a named session can tell "someone beat me to this name" (resume it) apart from a real database failure, instead of having to match on error text.

It reads any uniqueness failure on the insert as a name collision. The table's other unique constraint is the id primary key, and callers mint a fresh ULID per call, so a PK collision cannot happen without an id-generation bug -- the same assumption ErrSlugExists makes.

View Source
var ErrSlugExists = errors.New("store: project slug already exists")

ErrSlugExists is returned by CreateProject when the slug is already taken.

View Source
var ErrTaskBlocked = errors.New("task blocked")

ErrTaskBlocked is returned when a claim targets an open task with an unfinished dependency; the message names the blockers. No one holds the task -- it becomes claimable the moment its blockers close.

View Source
var ErrTaskClaimConflict = errors.New("task already claimed")

ErrTaskClaimConflict is returned when a claim loses the compare-and-set race to another live lease (or, from Release/Update paths, when the caller is not the holder). A refused claim whose cause is not a holder reports ErrTaskBlocked or ErrTaskClosed instead -- those need a different fix (finish the blocker; nothing, respectively), and reporting them as "already claimed" used to send agents chasing a holder that did not exist ("held by \"\"").

View Source
var ErrTaskClosed = errors.New("task closed")

ErrTaskClosed is returned when a claim targets a done/dropped task. No one holds it either; there is simply nothing left to claim.

View Source
var ErrTaskCycle = errors.New("dependency would create a cycle")

ErrTaskCycle is returned when adding a dependency edge would create a cycle.

View Source
var ErrTaskNotFound = errors.New("task not found")

ErrTaskNotFound is returned when a task id does not exist (e.g. a dangling depends_on reference).

View Source
var InjectionSurfaces = []string{"session-start", "subagent-start"}

InjectionSurfaces are the briefing injection surfaces the read-after-inject funnel segments by, in display order. Each value is the hook name the hooks layer stamps into a retrieval.injected payload: "session-start" is the main SessionStart briefing, "subagent-start" the constraints-only child briefing.

View Source
var MilestoneMemoryWrittenThresholds = []int{100, 500, 1000}

MilestoneMemoryWrittenThresholds are the per-project memory-written counts that mint a milestone, ascending. Exported maturity-style so the ledger's consumers and this computation agree by construction.

ProposalKinds lists every valid proposal kind. This is the canonical set: derive the MCP schema's enum from it rather than transcribing, so a new kind cannot reach the store while staying invisible at the boundary.

View Source
var RetrievalWindowKeys = []string{"24h", "7d", "30d", "all"}

RetrievalWindowKeys are the selectable trailing windows for the retrieval-health views, in display order. "24h" is the default; "all" spans every recorded event.

Functions

func ActiveAmbientByCWD

func ActiveAmbientByCWD(ctx context.Context, db *sql.DB, cwd string) ([]core.Session, error)

ActiveAmbientByCWD returns the active ambient (cc/* or cx/*) sessions whose cwd matches, most recent first. session_start consults it to link a freshly created explicit session to the Claude session that owns the cwd (via its claude_session_id), so a graceful SessionEnd can close both at once instead of leaving the explicit one to the idle reaper. An empty cwd matches nothing (no basis to link).

func ActiveAmbientProjects

func ActiveAmbientProjects(ctx context.Context, db *sql.DB, within time.Duration) ([]string, error)

ActiveAmbientProjects returns the distinct project slugs that have at least one active ambient (cc/* or cx/*) session updated within the window, ordered by each project's most recent activity. The MCP fallback consults it to tell a safe single-project inference (len 1) from the ambiguous concurrent-agent case (len > 1, agents in different repos) where guessing would bleed a write into the wrong project. A non-positive within disables the recency filter. The global scope is reported as the empty string, distinct from any named project.

func ActiveAmbientSessionsForProject

func ActiveAmbientSessionsForProject(ctx context.Context, db *sql.DB, project string, within time.Duration) ([]core.Session, error)

ActiveAmbientSessionsForProject returns every active ambient (cc/* or cx/*) session in the given project updated within the window, most recent first. resolveSession uses it to refuse targeting a session by inference when more than one same- project ambient could be the one meant -- two agents in the same repo -- so a session_update/end without an explicit id can't complete a sibling's session. A non-positive within disables the recency filter.

func ActiveMemories

func ActiveMemories(ctx context.Context, db *sql.DB, project string) ([]core.Memory, error)

ActiveMemories returns the active (not superseded/archived) memories visible to a project: its own plus all global memories (project == ""). Rows carry index metadata only (no body); newest-updated first. Passing project == "" returns only global memories.

func ActiveMemoriesForProjects

func ActiveMemoriesForProjects(ctx context.Context, db *sql.DB, slugs []string) ([]core.Memory, error)

ActiveMemoriesForProjects returns the active memories belonging to exactly the given project slugs -- no own-project or global union, unlike ActiveMemoriesForScope -- newest-updated first. It backs the briefing's opt-in sibling-memories cross-over, where the caller already holds the own scope and wants only the family members' rows. Blank/duplicate slugs are ignored; no slugs yields nil.

func ActiveMemoriesForScope

func ActiveMemoriesForScope(ctx context.Context, db *sql.DB, project string, extra []string) ([]core.Memory, error)

ActiveMemoriesForScope returns the active memories visible to a project widened by extra project slugs (e.g. a shared parent whose memories a split injects into each child). It is ActiveMemories plus the union of extra: rows where invalid_at IS NULL and project is the project, global (”), or any extra slug -- deduped, newest-updated first. Blank/duplicate extras are ignored; with no extras it is exactly ActiveMemories.

func ActiveSessionIDs

func ActiveSessionIDs(ctx context.Context, db *sql.DB, ids []string) (map[string]bool, error)

ActiveSessionIDs reports which of the given session ids belong to a currently active session. It backs the MCP server's connection-binding sweep: a binding whose session is no longer active -- ended by the session_end tool, the SessionEnd hook, or the idle reaper -- is evicted. Ids absent from the result (unknown or non-active) are simply not set.

func ActiveSessionsByExternalIdentity added in v0.3.7

func ActiveSessionsByExternalIdentity(
	ctx context.Context,
	db *sql.DB,
	externalClient, externalSessionID string,
) ([]core.Session, error)

ActiveSessionsByExternalIdentity returns every active session -- the ambient cc/* or cx/* plus any explicit session_start that linked to it -- stamped with the same full external id and client discriminator, ambient first. A graceful SessionEnd uses it to close only the issuing client's session family.

func AddFamilyMembers

func AddFamilyMembers(ctx context.Context, db *sql.DB, family string, slugs []string) ([]string, error)

AddFamilyMembers adds slugs to the named family, creating the family when it is new, and persists the result. Existing members keep their order and duplicates are ignored, so callers may re-add the same slugs idempotently. Returns the family's resulting members.

The read-decode-mutate-write runs inside one transaction (the AddRepoMapping recipe): the pool is capped at a single connection (see Open), so the whole mutation is serialized against concurrent mutators and two callers growing different families at once can no longer clobber each other's family. If the pool ever grows past one connection this needs BEGIN IMMEDIATE, since two deferred transactions could still interleave read-then-write.

func AddRepoMapping

func AddRepoMapping(ctx context.Context, db *sql.DB, repoPath, slug string) error

AddRepoMapping records repoPath -> slug in the repo_project_map and persists it, so the mapping survives restarts and is shared by every agent. It is a no-op when that exact entry already exists, so callers may invoke it on every resolve without churning the setting.

func AllActiveMemories

func AllActiveMemories(ctx context.Context, db *sql.DB) ([]core.Memory, error)

AllActiveMemories returns every active memory across all projects (index rows, no body), newest-updated first. It backs the gardener's reference scan, which reads each file's body to find [[name]] links.

func AllMemoriesIncludingInvalid

func AllMemoriesIncludingInvalid(ctx context.Context, db *sql.DB) ([]core.Memory, error)

AllMemoriesIncludingInvalid returns every memory index row -- active and invalid (superseded or archived) -- newest-updated first. It backs the console Memories browser, which renders supersession chains and archived items.

func AllReadyTasks

func AllReadyTasks(ctx context.Context, db *sql.DB) ([]core.Task, error)

AllReadyTasks returns the ready tasks across every project (see ReadyTasks for the readiness rule), oldest-created first. It backs the console Tasks page.

func AllRetrievalStats

func AllRetrievalStats(ctx context.Context, db *sql.DB) (map[string]RetrievalStat, error)

AllRetrievalStats loads the whole retrieval_stats table into a map keyed by item id, so a caller (the console) can annotate many memories with one query instead of N GetRetrievalStat calls.

func AllTasksByStatus

func AllTasksByStatus(ctx context.Context, db *sql.DB, status core.TaskStatus) ([]core.Task, error)

AllTasksByStatus returns every task with the given status across all projects, newest-updated first.

func AmbientSessionByExternalIdentity added in v0.3.7

func AmbientSessionByExternalIdentity(
	ctx context.Context,
	db *sql.DB,
	externalClient, externalSessionID string,
) (core.Session, bool, error)

AmbientSessionByExternalIdentity resolves the authoritative ambient row for a client-issued session id. Display names are deliberately absent from the predicate so legacy and digest-suffixed names behave identically.

func BriefingConfig

func BriefingConfig(ctx context.Context, db *sql.DB, base config.Briefing) (cfg config.Briefing, overridden bool, err error)

BriefingConfig returns the effective briefing config: base (the file/env values) with the console-saved override row, when present, decoded over it. overridden reports whether such a row exists. Absent fields in a stored override keep their base value, so a row written by an older console version stays forward-compatible.

func BriefingExposureSince added in v0.4.1

func BriefingExposureSince(ctx context.Context, db *sql.DB, since time.Time) (map[string]int, error)

BriefingExposureSince counts, per item id, session-start briefing injections since the cutoff -- the exposure denominator for dead-weight detection.

func CanRead added in v0.4.10

func CanRead(ctx context.Context, db *sql.DB, callerProject, targetProject string) (bool, error)

CanRead reports whether a session bound to callerProject may read targetProject's knowledge ("" is the global scope). A scope always reads itself; a fenced (confidential or sealed) target never leaks to another scope; a sealed caller reads nothing outside itself, global included.

func CanWrite added in v0.4.10

func CanWrite(ctx context.Context, db *sql.DB, callerProject, targetProject string) (bool, error)

CanWrite reports whether a session bound to callerProject may write into targetProject ("" is the global scope). A scope always writes itself; a fenced caller writes nowhere else -- an agent-initiated outside write, global included, is an outbound leak; a sealed target admits nothing from outside, while a confidential target stays writable from outside (inbound is unchanged -- relocating knowledge INTO the fence is how it gets there).

func CaptureStreak added in v0.4.10

func CaptureStreak(ctx context.Context, db *sql.DB, now time.Time) (current, longest int, err error)

CaptureStreak reports the momentum capture streak: the current run of consecutive covered local days and the longest run ever. Today counts into the current run as soon as it is covered; an uncovered today is a pause, not a break -- the run through yesterday stands until the day is actually over. A momentum-only surface: callers gate on the feature, so a disabled install neither queries nor writes the cache.

func CheckMilestones added in v0.4.10

func CheckMilestones(ctx context.Context, db *sql.DB, rec OnceRecorder, base config.Features, e core.Event) error

CheckMilestones runs the milestone checks the just-landed event e can have moved, minting any newly crossed milestone through rec. The event recorder calls it after every successful insert, so each COUNT already includes e.

Gated on the effective momentum config, resolved live (base overlaid with the console's stored override, the same discipline as every momentum surface): off means no threshold check runs and nothing is minted. Events with no project slug move no per-project count and are skipped outright, as are all kinds outside the small watched set -- in particular milestone.reached itself, which bounds the recorder's re-entrant call. plan.shipped is watched on purpose: the recorder feeds a settlement this very function just minted back through here to latch the first-plan-shipped milestone, and that second re-entry ends at milestone.reached.

func ClearBriefingConfig

func ClearBriefingConfig(ctx context.Context, db *sql.DB) error

ClearBriefingConfig removes the console briefing override, reverting the effective briefing config to the file/env base.

func ClearFeaturesConfig added in v0.4.10

func ClearFeaturesConfig(ctx context.Context, db *sql.DB) error

ClearFeaturesConfig removes the optional-features override, reverting the effective config to the file/env base -- which, for an installation that never set the keys, means every optional feature is off again.

func CompletedSessionsSince

func CompletedSessionsSince(ctx context.Context, db *sql.DB, since time.Time) ([]core.Session, error)

CompletedSessionsSince returns completed sessions with non-empty findings updated on or after since, across all projects, newest first. It feeds the gardener's monthly digest pass.

func Cosine

func Cosine(a, b []float32) float64

Cosine returns the cosine similarity of two equal-length vectors, in [-1, 1]. Mismatched lengths or a zero-magnitude vector yield 0.

func CountMemoriesUnsurfacedSince added in v0.4.10

func CountMemoriesUnsurfacedSince(ctx context.Context, db *sql.DB, cutoff time.Time) (int, error)

CountMemoriesUnsurfacedSince counts active memories that have not entered an agent context since cutoff: never injected, or last injected before it. It is the Overview's "going stale" bucket, and it is deliberately narrower than StaleMemories -- that pass also weighs updates and reads, which answers "is anyone touching this", while this one answers only "is this still reaching agents".

A memory created after the cutoff is excluded: something written yesterday has not had 45 days in which to surface, and counting it would make every burst of writing look like decay.

func CreateProject

func CreateProject(ctx context.Context, db *sql.DB, p core.Project) error

CreateProject inserts a project. It returns ErrSlugExists if the slug is taken. A zero Isolation is stored as open (the default); a present-but-unrecognized state is an error, never silently defaulted.

func CreateSession

func CreateSession(ctx context.Context, db *sql.DB, s core.Session) error

CreateSession inserts a session. The caller mints the ULID id; a duplicate name returns ErrSessionNameExists, while a duplicate authoritative ambient identity returns ErrSessionIdentityExists.

func CreateTask

func CreateTask(ctx context.Context, db *sql.DB, t core.Task) error

CreateTask inserts a task and its dependency edges in one transaction. Every depends_on must reference an existing task (dangling references are rejected) and must not create a cycle. The caller mints the ULID id and timestamps.

func CreateTrial

func CreateTrial(ctx context.Context, db *sql.DB, tr core.Trial) error

CreateTrial inserts a trial row. The caller mints the ULID id and timestamp.

func DecodeVector

func DecodeVector(b []byte) []float32

DecodeVector reverses EncodeVector. A byte slice whose length is not a multiple of four yields nil (corrupt row; skip it).

func DeleteSetting

func DeleteSetting(ctx context.Context, db *sql.DB, key string) error

DeleteSetting removes a settings key. Deleting an absent key is a no-op.

func DemandItemIDsSince added in v0.4.1

func DemandItemIDsSince(ctx context.Context, db *sql.DB, since time.Time) (map[string]struct{}, error)

DemandItemIDsSince returns every item id that saw query-gated demand -- a recall hit, a prompt-recall match, or an explicit read -- since the cutoff. Passive briefing injections do not count, mirroring the utility score.

func DistinctPlanSlugsForProject

func DistinctPlanSlugsForProject(ctx context.Context, db *sql.DB, project string) ([]string, error)

DistinctPlanSlugsForProject returns the full set of plan slugs a project has ever had, including completed plans, sorted. Unlike ActivePlans (which drops a plan once every step is closed) this keeps completed plans, so it backs a history/lineage view. It rejects an empty project for the same reason as ListSessionsForProject.

Plan slugs are unique only PER PROJECT: two projects may each have a plan "refactor". A cross-project consumer must key by (project, slug), never slug alone.

func EmbedderMode added in v0.4.1

func EmbedderMode(ctx context.Context, db *sql.DB) (string, error)

EmbedderMode returns the stored embedder override: EmbedderModeOff when the owner switched embeddings off, EmbedderModeAuto otherwise. An unset row or an unrecognized stored value both read as auto -- the override can only ever narrow behavior, never invent a new state.

func EncodeVector

func EncodeVector(vec []float32) []byte

EncodeVector serializes a float32 vector to a little-endian byte slice, the on-disk form of the embeddings.vec BLOB column.

func EnsureProject

func EnsureProject(ctx context.Context, db *sql.DB, slug, name string) (core.Project, error)

EnsureProject returns the project registered under slug, creating a minimal row when none exists yet. It is the idempotent upsert used by the importer and by session resolution so that every project referenced by memories, notes, or sessions also has a first-class projects-table row -- the row project_list reads. A blank slug is the global scope: it is never registered and yields the zero Project with no error. When name is blank the slug is used as the name.

func EvaluateGamificationRecords added in v0.4.10

func EvaluateGamificationRecords(ctx context.Context, db *sql.DB, today DayCounts, liveAgents int, now time.Time) (GamificationRecords, []RecordCrossing, error)

EvaluateGamificationRecords compares today's judged counts (and the live agent count) against the stored ledger, latches any new bests forward, and returns the crossings. Forward-only, like the maturity latch: a slower day never regresses a record, and re-rendering with unchanged values persists nothing. A corrupt or absent row reads as an empty ledger and is simply re-earned. A gamification-only surface: callers gate on the feature, so a disabled install neither reads nor writes the row.

func ExpireStaleSessions

func ExpireStaleSessions(ctx context.Context, db *sql.DB, cutoff time.Time) ([]core.Session, error)

ExpireStaleSessions reaps active sessions whose last activity predates cutoff, flipping each to SessionExpired. It leaves updated_at untouched so the row still records when the session was last alive (the console orders and dates by it), which also keeps the flip idempotent: a reaped session no longer matches the active filter. It returns only the sessions it actually flipped (id + project) so the caller can release their task claims and record telemetry -- a session that was heartbeated or gracefully completed between the candidate read and the flip is skipped, never falsely reaped. Passing updated_at unchanged means a later graceful session_end/resume can still upgrade the row (its guards key off completed/active, not expired).

func FeaturesConfig added in v0.4.10

func FeaturesConfig(ctx context.Context, db *sql.DB, base config.Features) (cfg config.Features, overridden bool, err error)

FeaturesConfig returns the effective optional-features config: base (the file/env values) with the stored override row, when present, decoded over it. overridden reports whether such a row exists. Absent fields in a stored override keep their base value, so a row written before a feature existed stays forward-compatible -- a newly added feature keeps its default rather than being zeroed off by an old row.

Callers resolve this LIVE (per request or per assembly, like the briefing override) and must be failure-soft: on error, log and fall back to base rather than failing an agent call or a console page over a corrupt row.

func ForceReleaseTask

func ForceReleaseTask(ctx context.Context, db *sql.DB, id string, now time.Time) (core.Task, error)

ForceReleaseTask unconditionally releases a claimed (in_progress) task, reopening it (status back to open, claim and lease cleared) regardless of who holds it or whether the lease is still live. It backs the owner override (the console "release lock" button and `seam task release --force`); it is not reachable from the agent MCP surface. Unlike ReleaseTask it does not check the holder. Returns ErrTaskClaimConflict when the task is not in_progress (nothing to release) and ErrTaskNotFound when the id is unknown.

func GetSetting

func GetSetting(ctx context.Context, db *sql.DB, key string) (string, bool, error)

GetSetting returns the value for a settings key. found is false when unset.

func GlobalMemoriesFromProjectSessions added in v0.4.10

func GlobalMemoriesFromProjectSessions(ctx context.Context, db *sql.DB, project string) ([]core.Memory, error)

GlobalMemoriesFromProjectSessions returns the ACTIVE global-scope memories whose source session was bound to project, newest-updated first. It is the provenance audit behind a tighten: knowledge this project's agents wrote OUTSIDE the fence before the fence existed, which no read/write check can reach afterwards because the memory itself is global.

The source_session stamp is matched against both spellings a memory can carry -- the session NAME for ambient stamps (cc/ab12cd34), the session ULID for bound ones (see MemoriesForSession) -- via a UNION subquery rather than a JOIN, so a stamp can never match two session rows and duplicate a memory.

It reports only; relocating is a gardener proposal the owner applies.

func IdentifierMatchPriority added in v0.4.9

func IdentifierMatchPriority(kind IdentifierMatchKind) int

IdentifierMatchPriority returns the relevance tier for a match kind. The empty/unknown value is deliberately last so callers can stable-sort ordinary search results after every recognized identifier match.

func IsolationOf added in v0.4.10

func IsolationOf(ctx context.Context, db *sql.DB, slug string) (core.Isolation, error)

IsolationOf returns a project's isolation state. The global scope ("") is never isolated, and an unregistered slug is open by construction -- a project must have a projects-table row before it can be fenced.

func LatestActiveAmbientSessionForProject

func LatestActiveAmbientSessionForProject(ctx context.Context, db *sql.DB, project string, within time.Duration) (core.Session, bool, error)

LatestActiveAmbientSessionForProject returns the most recently updated active ambient (cc/* or cx/*) session in the given project, updated within the window, or found=false when none. It backs the MCP write-scope fallback: an agent that writes without calling session_start inherits its project's ambient session's provenance. A non-positive within disables the recency filter. Scoping to a single project is what prevents cross-agent bleed -- see ActiveAmbientProjects.

func ListNotes

func ListNotes(ctx context.Context, db *sql.DB) ([]core.Note, error)

ListNotes returns every note, newest-updated first. Rows carry index metadata only (no body, which lives in the file). It backs the console Notes browser.

func ListProjects

func ListProjects(ctx context.Context, db *sql.DB) ([]core.Project, error)

ListProjects returns every project, ordered by slug.

func ListSessions

func ListSessions(ctx context.Context, db *sql.DB, status core.SessionStatus, since time.Time, limit int) ([]core.Session, error)

ListSessions returns sessions newest-updated first, optionally filtered by status and to those updated since a cutoff (a zero `since` means all time), capped at limit (default 100). It backs the console Sessions list.

func ListSessionsForProject

func ListSessionsForProject(ctx context.Context, db *sql.DB, project string, status core.SessionStatus, since time.Time, limit int) ([]core.Session, error)

ListSessionsForProject returns a project's sessions newest-updated first, optionally filtered by status and to those updated since a cutoff (a zero `since` means all time), capped at limit (default 100).

It is STRICT per-slug (project_slug = ?): unlike RecentFindings it does NOT union the global scope, so a project sees only its own sessions. It rejects an empty project: an empty project_slug marks real global sessions, so "" is ambiguous between "all sessions" and "global-only" -- use ListSessions for all sessions (there is no global-only variant; pass an explicit slug for one project).

func ListTasks

func ListTasks(ctx context.Context, db *sql.DB, project string, status core.TaskStatus) ([]core.Task, error)

ListTasks returns a project's tasks, optionally filtered by status, newest first. An empty status returns every status.

func ListTasksForPlan

func ListTasksForPlan(ctx context.Context, db *sql.DB, project string, status core.TaskStatus, plan string) ([]core.Task, error)

ListTasksForPlan returns a plan's step tasks in a project, optionally filtered by status, newest first. Unlike ListTasks it includes (only) plan-scoped tasks.

func LiveSessionCount added in v0.4.10

func LiveSessionCount(ctx context.Context, db *sql.DB, cutoff time.Time) (int, error)

LiveSessionCount counts the sessions live right now: active and heartbeating on or after the cutoff -- the same predicate as ProjectsWithCounts' live column and Session.LiveAsOf. It backs the console's Now nav badge, so it is one scalar query, safe on every page load.

func LooksLikeSessionULID added in v0.3.7

func LooksLikeSessionULID(s string) bool

LooksLikeSessionULID reports whether s has the shape of a bare session ULID: no '/' (session names always contain one), exactly 26 chars, and a valid ULID encoding. It disambiguates the session-name vs session-id relation guards, and lets provenance consumers pick the right lookup for a source_session stamp (name for ambient stamps, ULID for bound stamps) without a doomed first query.

func MemoriesByIDs

func MemoriesByIDs(ctx context.Context, db *sql.DB, ids []string) (map[string]core.Memory, error)

MemoriesByIDs returns the memories for the given ids in no particular order. Missing ids are simply absent from the result; callers key by ID.

func MemoriesForSession

func MemoriesForSession(ctx context.Context, db *sql.DB, sess core.Session) ([]core.Memory, error)

MemoriesForSession returns the memories a session produced -- the index rows whose source_session matches the session -- newest-updated first. Memory.SourceSession holds the session NAME for ambient stamps (cc/ab12cd34) but the session ULID for bound stamps (see the source-session-stamps memory), so the query matches either spelling and callers pass the whole session.

func MemoriesSuperseding

func MemoriesSuperseding(ctx context.Context, db *sql.DB, id string) ([]core.Memory, error)

MemoriesSuperseding returns the memories that the given memory replaced: the index rows whose superseded_by points at id. It is the inverse of a memory's own SupersededBy edge (which the store does not otherwise materialize), newest-updated first, and empty when the memory superseded nothing. It backs the console memory peek's reverse "supersedes" section.

func MemoryByID

func MemoryByID(ctx context.Context, db *sql.DB, id string) (core.Memory, bool, error)

MemoryByID returns the memory with the given id. found is false when absent.

func MemoryByName

func MemoryByName(ctx context.Context, db *sql.DB, project, name string) (core.Memory, bool, error)

MemoryByName returns the active memory with an exact (project, name), most recently updated first. found is false when none matches. It does not fall back to the global scope; a caller that wants that resolves it explicitly.

func MemoryByNameIncludingInvalid

func MemoryByNameIncludingInvalid(ctx context.Context, db *sql.DB, project, name string) (core.Memory, bool, error)

MemoryByNameIncludingInvalid returns the most recently updated memory with an exact (project, name), whether or not it is still valid. It backs memory_read's warning path: a superseded memory (which MemoryByName excludes) is still readable, prefixed with a warning pointing at its replacement.

func NoteByID

func NoteByID(ctx context.Context, db *sql.DB, id string) (core.Note, bool, error)

NoteByID returns the note with the given id. found is false when absent.

func NoteBySlug

func NoteBySlug(ctx context.Context, db *sql.DB, project, slug string) (core.Note, bool, error)

NoteBySlug returns the note with an exact (project, slug). found is false when none matches.

func NotesByIDs

func NotesByIDs(ctx context.Context, db *sql.DB, ids []string) (map[string]core.Note, error)

NotesByIDs returns the notes for the given ids keyed by ID; missing ids are simply absent.

func NotesByTag

func NotesByTag(ctx context.Context, db *sql.DB, project, tag string) ([]core.Note, error)

NotesByTag returns the notes carrying an exact tag, newest-updated first, optionally scoped to a project ("" = every project). Tags are stored as a JSON array, so the match runs through json_each rather than LIKE.

func NotesByTagPrefix

func NotesByTagPrefix(ctx context.Context, db *sql.DB, project, prefix string) ([]core.Note, error)

NotesByTagPrefix returns the notes carrying at least one tag that begins with prefix (e.g. "plan:" for every plan composition), newest-updated first, optionally scoped to a project ("" = every project). It backs surfaces that enumerate a tag family without knowing the concrete values up front. prefix is treated literally: its LIKE metacharacters are escaped.

func Open

func Open(dbPath string) (*sql.DB, error)

Open opens (creating if needed) the SQLite database at dbPath, applies PRAGMAs via the DSN so every pooled connection inherits them, runs migrations, and returns the handle. The caller is responsible for closing the *sql.DB.

func PriorWindow added in v0.4.10

func PriorWindow(w RetrievalWindow, now time.Time) (since, until time.Time, ok bool)

PriorWindow returns the equally long stretch immediately before w: the window a delta chip compares against. ok is false when there is nothing to compare -- the "all time" selection has no prior window, and neither does a window whose lower bound is not in the past.

func ProjectBySlug

func ProjectBySlug(ctx context.Context, db *sql.DB, slug string) (core.Project, bool, error)

ProjectBySlug returns the project with the given slug. found is false when absent.

func ProjectFamilies

func ProjectFamilies(ctx context.Context, db *sql.DB) (map[string][]string, error)

ProjectFamilies decodes the project_families setting into a map. An unset or blank value yields an empty map (not an error).

func ProjectMemoriesIncludingInvalid

func ProjectMemoriesIncludingInvalid(ctx context.Context, db *sql.DB, project string) ([]core.Memory, error)

ProjectMemoriesIncludingInvalid returns a project's memory index rows -- both active and invalid (superseded/archived) -- newest-updated first, for a lineage view that must render supersession chains.

It is STRICT per-slug (project = ?): it does NOT union the global (empty) scope. This is the deliberate opposite of ActiveMemories, which DOES union global rows into a project's view. Mixing the two conventions is exactly how a project count comes out larger than expected, so callers must pick the strict variant on purpose. It rejects an empty project for the same reason as ListSessionsForProject.

func ProjectsByParent

func ProjectsByParent(ctx context.Context, db *sql.DB, parent string) ([]core.Project, error)

ProjectsByParent returns the projects whose parent_slug equals parent, ordered by slug (using idx_projects_parent). It backs walking a project's children in the parent/child topology a split builds.

func ProposalKeyBlocks added in v0.4.10

func ProposalKeyBlocks(ctx context.Context, db *sql.DB) (map[string]ProposalBlock, error)

ProposalKeyBlocks returns the suppression rule for every payload "key" across proposals of EVERY status. The gardener consults it before proposing, so a suggestion the owner already saw is not raised again -- forever for an applied, pending or hidden one, and until the evidence recurs for a regular dismissal.

A key can carry several rows (a dismissal, then the recurrence that re-raised it). The strongest rule wins: any hard row makes the key hard, otherwise the latest dismissal is the one to beat.

func QueryTrials

func QueryTrials(ctx context.Context, db *sql.DB, f TrialFilter) ([]core.Trial, error)

QueryTrials returns trials matching the filter, newest first. Lab/outcome/ project are filtered in SQL; MetricsEquals is applied in Go (labs are small, and this matches the DB-first metrics design without a JSON1 dependency).

func ReactivateAmbientSession added in v0.3.7

func ReactivateAmbientSession(
	ctx context.Context,
	db *sql.DB,
	externalClient, externalSessionID, project string,
	now time.Time,
) (core.Session, bool, error)

ReactivateAmbientSession resumes an ambient session by its authoritative external identity. The targeted UPDATE flips status back to active, re-scopes only when project is non-empty, and bumps recency without touching findings or metadata. Returning the row gives callers its actual display name, including a legacy pre-digest name preserved by migration 010.

func ReadyTasks

func ReadyTasks(ctx context.Context, db *sql.DB, project string) ([]core.Task, error)

ReadyTasks returns the actionable tasks for a project: open tasks with no blocking dependency still open or in_progress (done/dropped deps do not block). in_progress tasks are excluded (already claimed) but still block their dependents. Ordered oldest-created first, ties broken by id (ULID-monotonic), so the queue is stable and agent-predictable.

func ReadyTasksForPlan

func ReadyTasksForPlan(ctx context.Context, db *sql.DB, project, plan string) ([]core.Task, error)

ReadyTasksForPlan returns the ready (claimable) step tasks of one plan in a project: open, unclaimed, with no unfinished blocker. Same readiness rule as ReadyTasks, scoped to plan_slug instead of excluding plan tasks.

func RebuildRetrievalStats

func RebuildRetrievalStats(ctx context.Context, db *sql.DB) error

RebuildRetrievalStats recomputes the entire retrieval_stats table from the event log. It is the canonical maintenance path (the gardener calls it at the top of each pass, and it is safe to call after an import): retrieval.injected events contribute inject_count + last_injected_at (per item id, whether the id is in the item_id column or the payload's item_ids array), and memory.read / note.read events contribute read_count + last_read_at. Events are read oldest-first so the last-seen timestamp wins. The same walk accumulates the utility score: each query-gated signal adds its class weight decayed by the event's age.

func RecentFindings

func RecentFindings(ctx context.Context, db *sql.DB, project string, limit int) ([]core.Session, error)

RecentFindings returns ended sessions with meaningful findings visible to a project (its own plus global sessions), most recent first. It backs the briefing's "recent findings" section, so it excludes both blank findings and the core.FindingNoSummary sentinel (a session that ended with nothing to harvest) -- a content-free line is not worth an agent's context.

"Ended" is completed OR reaper-expired-while-ambient. A Codex ambient session has no SessionEnd event (design D5): the Stop hook harvests findings onto the live row and the idle reaper flips it to 'expired', so requiring 'completed' would hide every Codex session's findings. Restricting the expired arm to ambient rows keeps this exact: a CC ambient session only ever gains findings at SessionEnd (which also sets 'completed'), so no CC row is active-with-findings to be reaped, and a crashed explicit session (ambient = 0) still does not surface -- the set added here is precisely Codex's reaper-ended sessions.

func RecentMishapItemIDs added in v0.4.4

func RecentMishapItemIDs(ctx context.Context, db *sql.DB, project string, window time.Duration) (map[string]time.Time, error)

RecentMishapItemIDs returns the ids of memories referenced by a project's agent.mishap events recorded within window of now, each mapped to the timestamp of its most recent referencing mishap. It feeds the briefing's mishap promotion -- an ordering signal like favorites -- and deliberately NOT the utility score: RebuildRetrievalStats keeps its query-gated classes (read/recall/prompt) per the closed-loop-utility-signal-contract. Mishaps whose payload names no memory contribute nothing; no references yields an empty map, not an error.

func RecordProposalResult added in v0.4.6

func RecordProposalResult(ctx context.Context, db *sql.DB, id string, result map[string]any) error

RecordProposalResult stores what applying a proposal produced. It is called after ResolveProposal, so it deliberately does not filter on status -- the proposal is already applied by then. A missing row is not an error: the result is a convenience for undo, never a correctness requirement of apply.

func ReleaseClaimsForSession

func ReleaseClaimsForSession(ctx context.Context, db *sql.DB, sessionID string, now time.Time) (int, error)

ReleaseClaimsForSession reopens every in_progress task currently claimed by sessionID (called on session_end so a departing agent's work returns to the queue). It returns the number of tasks released.

func ReleaseTask

func ReleaseTask(ctx context.Context, db *sql.DB, id, sessionID string, now time.Time) (core.Task, error)

ReleaseTask releases a task held by sessionID, reopening it (status back to open, claim and lease cleared) so another agent can pick it up. Only the current holder may release; otherwise it returns ErrTaskClaimConflict (or ErrTaskNotFound when the id is unknown).

func RemoveFamilyMembers

func RemoveFamilyMembers(ctx context.Context, db *sql.DB, family string, slugs []string) ([]string, error)

RemoveFamilyMembers removes slugs from the named family and persists the result. Passing no slugs removes the whole family; a family left with no members after the removal is dropped as well. Returns the family's resulting members, empty when the family was removed. It errors with ErrFamilyNotFound when the named family does not exist.

Like AddFamilyMembers the read-decode-mutate-write runs inside one transaction, serialized by the single-connection pool (see Open), so a concurrent removal from another family survives instead of being clobbered by this write-back. If the pool ever grows past one connection this needs BEGIN IMMEDIATE.

func ReopenProposal added in v0.4.6

func ReopenProposal(ctx context.Context, db *sql.DB, id string) error

ReopenProposal returns a resolved proposal to the pending queue, clearing the resolution stamp and the apply result. It is the store half of undo: the caller has already inverted the effect (or is undoing a rejection, which had none). It errors if the proposal is missing or still pending, so a double undo cannot resurrect an already-restored proposal.

The payload key is deliberately left in place: ProposalKeyBlocks reads every status, so dedup behaves the same whether the proposal is pending, resolved, or reopened -- and a pending row blocks just as hard as an applied one.

func RepoProjectMap

func RepoProjectMap(ctx context.Context, db *sql.DB) (map[string]string, error)

RepoProjectMap decodes the repo_project_map setting into a map. An unset or blank value yields an empty map (not an error), so an unconfigured install simply resolves every cwd to the global scope.

func ResolveProjectForCWD

func ResolveProjectForCWD(ctx context.Context, db *sql.DB, cwd string) (string, error)

ResolveProjectForCWD maps an absolute working directory to a project slug via the repo_project_map, using a longest-prefix match. It is read-only and failure-soft: an unresolvable cwd (or an unconfigured map) returns "" (the global scope), never an error from the matching itself. Use it on read paths (briefing, prompt recall); use RegisterProjectForCWD on session-start paths that should grow the map.

func ResolveProposal

func ResolveProposal(ctx context.Context, db *sql.DB, id, status string, at time.Time) error

ResolveProposal marks a proposal applied, dismissed or hidden and stamps resolved_at. It errors if the proposal is missing or already resolved (not pending).

func RetireProject

func RetireProject(ctx context.Context, db *sql.DB, slug string, at time.Time) error

RetireProject stamps a project's retired_at (marking it emptied by a split) and bumps updated_at. Passing the zero time clears it (un-retire). It is idempotent and leaves the project's rows and files intact -- retirement is a flag, never a delete. An unknown slug affects no rows and returns nil.

func SaveProjectFamily added in v0.3.9

func SaveProjectFamily(ctx context.Context, db *sql.DB, previousName, name string, members []string) ([]string, error)

SaveProjectFamily creates, replaces, or renames one family atomically. previousName is empty for a create; otherwise that family must exist. A create or rename never merges into an existing family because silently combining their context boundaries would be surprising. The submitted member set replaces the old set in full and must contain at least one slug.

func SchemaVersion

func SchemaVersion(db *sql.DB) (int, error)

SchemaVersion returns the highest applied migration version.

func SearchProjects

func SearchProjects(ctx context.Context, db *sql.DB, q string, limit int) ([]core.Project, error)

SearchProjects returns projects whose slug or display name contains q, alphabetically by slug (projects are few and stable, so a name order reads better than a recency one).

func SearchProjectsSince added in v0.3.9

func SearchProjectsSince(ctx context.Context, db *sql.DB, q string, since time.Time, limit int) ([]core.Project, error)

SearchProjectsSince is SearchProjects restricted to projects updated at or after since. A zero since keeps the all-time behavior.

func SearchSessions

func SearchSessions(ctx context.Context, db *sql.DB, q string, limit int) ([]core.Session, error)

SearchSessions returns sessions whose name contains q, newest-updated first. An exact id also matches.

func SearchSessionsSince added in v0.3.9

func SearchSessionsSince(ctx context.Context, db *sql.DB, q string, since time.Time, limit int) ([]core.Session, error)

SearchSessionsSince is SearchSessions restricted to sessions updated at or after since. A zero since keeps the all-time behavior.

func SearchTasks

func SearchTasks(ctx context.Context, db *sql.DB, q string, limit int) ([]core.Task, error)

SearchTasks returns tasks whose title contains q, newest-updated first. An exact id also matches, so pasting a task id from a log finds its task.

func SearchTasksSince added in v0.3.9

func SearchTasksSince(ctx context.Context, db *sql.DB, q string, since time.Time, limit int) ([]core.Task, error)

SearchTasksSince is SearchTasks restricted to tasks updated at or after since. A zero since keeps the all-time behavior.

func SearchTrials added in v0.3.9

func SearchTrials(ctx context.Context, db *sql.DB, q string, limit int) ([]core.Trial, error)

SearchTrials returns trials whose title or lab contains q, newest first. An exact id also matches, so pasting a trial id from a log finds its trial.

func SearchTrialsSince added in v0.3.9

func SearchTrialsSince(ctx context.Context, db *sql.DB, q string, since time.Time, limit int) ([]core.Trial, error)

SearchTrialsSince is SearchTrials restricted to trials created at or after since. A zero since keeps the all-time behavior.

func SessionByID

func SessionByID(ctx context.Context, db *sql.DB, id string) (core.Session, bool, error)

SessionByID returns the session with the given id. found is false when absent.

func SessionByName

func SessionByName(ctx context.Context, db *sql.DB, name string) (core.Session, bool, error)

SessionByName returns the session with the given (unique) name. found is false when absent.

func SetAmbientSessionModel added in v0.3.7

func SetAmbientSessionModel(ctx context.Context, db *sql.DB, externalClient, externalSessionID, model string) error

SetAmbientSessionModel records the model by authoritative external identity.

func SetAmbientSessionTokens added in v0.3.9

func SetAmbientSessionTokens(
	ctx context.Context,
	db *sql.DB,
	externalClient, externalSessionID string,
	tokens core.TokenUsage,
	now time.Time,
) (bool, error)

SetAmbientSessionTokens overwrites the harvested model token totals on an active ambient session, keyed by authoritative external identity. Like the other targeted ambient writers it is a single-purpose write (only the five token columns + updated_at, never a read-modify-write of the whole row), so a concurrent findings/model update cannot clobber it and vice versa.

The totals are absolute cumulative values, so this OVERWRITES rather than accumulates: re-harvesting a resumed session's grown transcript (Claude Code SessionEnd) or re-reading the latest cumulative token_count every turn (Codex Stop) writes the same absolute total again -- idempotent, never double-counted. The active-only + ambient guard means it never revives a completed/expired row and never touches an explicit session. Bumping updated_at doubles as a heartbeat. No-op on an incomplete identity or an empty (all-zero) usage (a turn with no token record leaves any prior harvest intact). Reports whether a row was updated.

func SetBriefingConfig

func SetBriefingConfig(ctx context.Context, db *sql.DB, b config.Briefing) error

SetBriefingConfig persists b as the console briefing override. Callers validate first (config.Briefing.Validate); this only encodes and stores.

func SetEmbedderMode added in v0.4.1

func SetEmbedderMode(ctx context.Context, db *sql.DB, mode string) error

SetEmbedderMode persists the embedder override. Auto deletes the row (no override); Off stores it; anything else is rejected.

func SetFeaturesConfig added in v0.4.10

func SetFeaturesConfig(ctx context.Context, db *sql.DB, f config.Features) error

SetFeaturesConfig persists f as the optional-features override. Callers validate first; this only encodes and stores.

func SetProjectFamilies

func SetProjectFamilies(ctx context.Context, db *sql.DB, families map[string][]string) error

SetProjectFamilies persists the full families map as the project_families setting. Empty families (no members) are dropped and members are trimmed and deduped so the stored value stays canonical; passing an empty map clears the setting to "{}". It is the single writer the family CLI and mutators funnel through, so the setting never accumulates blanks or duplicates.

It reads nothing, so unlike the read-modify-write mutators below it needs no transaction: last-write-wins is inherent to its "set the whole map" contract.

func SetProjectFavorite added in v0.4.0

func SetProjectFavorite(ctx context.Context, db *sql.DB, slug string, favorite bool) error

SetProjectFavorite sets or clears a project's favorite flag. Idempotent; an unknown slug affects no rows and returns nil (matching RetireProject).

func SetProjectIsolation added in v0.4.10

func SetProjectIsolation(ctx context.Context, db *sql.DB, slug string, state core.Isolation) error

SetProjectIsolation sets a project's isolation state without bumping updated_at. Unlike the favorite setters it does NOT treat an unknown slug as a no-op: a fence the owner believes is up but never applied fails open, so not-found is an error here.

func SetProjectParent

func SetProjectParent(ctx context.Context, db *sql.DB, slug, parent string, now time.Time) error

SetProjectParent sets (or clears, with parent == "") a project's parent slug and bumps updated_at. The parent's active memories are injected into the child's briefing (see retrieve.Briefing). It is idempotent -- re-setting the same parent is a harmless no-op write -- so a split apply is retry-safe. An unknown slug affects no rows and returns nil (the caller ensures the row first).

ATTACHING refuses when EITHER side is isolated (ErrIsolationStandalone). Isolation requires a standalone project, and a parent link is a briefing cross-over surface, so a fenced project may neither take a parent nor be one. This is the link-side half of the rule TightenProjectIsolation enforces from the tighten side (it detaches the parent, and refuses outright while children remain, ErrIsolationHasChildren).

The guard lives at the store call rather than at each caller -- unlike the family writers, which are guarded in the console and the CLI so a split apply cannot fail inside the store -- because SetProjectParent's only non-test caller is that split apply, and gardener.Split already refuses a fenced source with its own ErrIsolatedProject. The refusal is therefore unreachable on that path and cannot regress it; if a future proposal ever did name a fenced slug, gardener.Apply surfaces the error as a console flash, not a 500.

DETACHING (a blank parent) is always allowed, an isolated child included: clearing a parent moves TOWARD the standalone rule, and refusing it would strand a project that acquired a fence and a parent by some other route (a legacy row, an import) with no way to comply. TightenProjectIsolation does not route through here -- it detaches inline in its own transaction -- so nothing about the tighten depends on this decision either way.

The check and the UPDATE share one transaction so a concurrent tighten cannot land between them and leave a freshly fenced project holding a parent link.

func SetSessionFavorite added in v0.4.0

func SetSessionFavorite(ctx context.Context, db *sql.DB, id string, favorite bool) error

SetSessionFavorite sets or clears a session's favorite flag. Idempotent; an unknown id affects no rows and returns nil.

func SetSessionModel added in v0.3.7

func SetSessionModel(ctx context.Context, db *sql.DB, id, model string) error

SetSessionModel records which LLM powers an active session's agent, keyed by session ULID. The value is stored verbatim as the provider names it (e.g. "claude-fable-5"). Targeted single-column write for the same reason as ReactivateAmbientSession: no read-modify-write of the whole row, so it cannot clobber a concurrent findings/metadata update. The active-only guard keeps a completed/expired session's attribution frozen at what it ended with, and the model <> ? guard makes repeated reports of the same value free. No-op on an empty id or model (an agent that never learns its model is not an error), so a matched-zero-rows outcome is legitimate and deliberately unchecked.

func SetSetting

func SetSetting(ctx context.Context, db *sql.DB, key, value string) error

SetSetting upserts a settings key/value.

func SetTaskFavorite added in v0.4.0

func SetTaskFavorite(ctx context.Context, db *sql.DB, id string, favorite bool) error

SetTaskFavorite sets or clears a task's favorite flag. Idempotent; an unknown id affects no rows and returns nil.

func SetTrialFavorite added in v0.4.0

func SetTrialFavorite(ctx context.Context, db *sql.DB, id string, favorite bool) error

SetTrialFavorite sets or clears a trial's favorite flag. Idempotent; an unknown id affects no rows and returns nil.

func SetUtilityActivation added in v0.4.1

func SetUtilityActivation(ctx context.Context, db *sql.DB, a UtilityActivation) error

SetUtilityActivation persists the activation state.

func SiblingFindings

func SiblingFindings(ctx context.Context, db *sql.DB, slugs []string, limit int) ([]core.Session, error)

SiblingFindings returns the most recent ended sessions with meaningful findings across the given sibling projects, newest first, capped at limit. It backs the briefing's "Sibling projects" section, so -- like RecentFindings -- it excludes blank findings and the core.FindingNoSummary sentinel, and treats a reaper-expired ambient session as ended so Codex's SessionEnd-less sessions surface too (see RecentFindings for why the expired arm is ambient-only). An empty slugs slice yields no results.

func SiblingProjects

func SiblingProjects(ctx context.Context, db *sql.DB, project string) ([]string, error)

SiblingProjects returns the other project slugs sharing a family with project, deduped and excluding project itself. A project may appear in more than one family; all such siblings are unioned. Returns nil for the global scope or a project with no family.

func StaleMemories

func StaleMemories(ctx context.Context, db *sql.DB, cutoff time.Time) ([]core.Memory, error)

StaleMemories returns active memories that have seen no activity since cutoff: neither updated, injected, nor read on or after that instant. It LEFT JOINs retrieval_stats (a memory with no stats row counts as never injected/read, so only its updated_at matters), oldest-updated first. It backs the gardener's staleness pass.

func TableCount

func TableCount(db *sql.DB) (int, error)

TableCount returns the number of user tables (excluding SQLite internals and FTS shadow tables). Useful for the doctor's DB check.

func TaskByID

func TaskByID(ctx context.Context, db *sql.DB, id string) (core.Task, error)

TaskByID returns a task with its dependency ids populated. found is false when absent.

func TasksBlockedBy

func TasksBlockedBy(ctx context.Context, db *sql.DB, id string) ([]core.Task, error)

TasksBlockedBy returns the tasks that depend on id -- the inverse of a task's DependsOn edges, i.e. the tasks this one blocks. Unlike openBlockers it applies no status filter (closed dependents are included too), oldest-created first. It backs the console task peek's reverse "blocks" section.

func TasksClaimedBy

func TasksClaimedBy(ctx context.Context, db *sql.DB, sessionID string) ([]core.Task, error)

TasksClaimedBy returns the tasks a session holds or held -- the rows whose claimed_by matches the session ULID -- oldest-created first (ties by id, the ULID-monotonic order the ready queue uses). Task.ClaimedBy stores the session ULID, never its name.

GUARD (the inverse of MemoriesForSession): if the argument is not ULID-shaped -- it contains '/' or is not 26 chars or fails to parse as a ULID -- it is almost certainly a session name that would silently match nothing; the call is rejected with a message pointing at SessionByName to resolve the id.

func TouchAmbientSession added in v0.3.7

func TouchAmbientSession(ctx context.Context, db *sql.DB, externalClient, externalSessionID string, now time.Time) error

TouchAmbientSession is TouchSession keyed by the full external session id and client discriminator. The active-only guard prevents late hook traffic from reviving a completed or reaped row.

func TouchSession

func TouchSession(ctx context.Context, db *sql.DB, id string, now time.Time) error

TouchSession heartbeats an active session by bumping its updated_at, marking it still-alive for the idle reaper. It only touches active sessions (the WHERE guard), so it never resurrects a completed or expired one, and is a no-op on an empty id. Callers fire it on activity (a bound MCP tool call), best-effort.

func TrialByID added in v0.3.9

func TrialByID(ctx context.Context, db *sql.DB, id string) (core.Trial, bool, error)

TrialByID loads one trial. found=false means no such trial.

func UnhideProposal added in v0.4.10

func UnhideProposal(ctx context.Context, db *sql.DB, id string) error

UnhideProposal demotes a forever-hidden proposal to a regular dismissal. It is NOT an undo: the proposal stays resolved and out of the queue, and nothing is re-raised on the spot. What changes is the suppression rule -- a hard block becomes one that lapses the moment evidence for the pattern recurs, so the next gardener pass MAY propose it again.

resolved_at is deliberately left at the moment of the hide rather than restamped: it is when the owner decided, and it is the instant the recurrence comparison measures from, so a pattern that is still occurring re-raises on the next pass instead of waiting for evidence newer than the unhide.

func UpdateAmbientFindings added in v0.3.7

func UpdateAmbientFindings(
	ctx context.Context,
	db *sql.DB,
	externalClient, externalSessionID, findings string,
	now time.Time,
) (bool, error)

UpdateAmbientFindings upserts provisional findings onto an active ambient session by full external identity. It backs the Codex Stop hook, which harvests the last agent message every turn: Codex has no SessionEnd event, so findings must be in place BEFORE the idle reaper expires the session.

The write is TARGETED -- only findings + updated_at, never a read-modify-write of the whole row -- so a Stop landing between a resume's read and write cannot be clobbered (mirroring the ensureAmbientSession contract), and repeated Stops simply converge findings on the latest turn's message. The active-only + ambient guard means it never resurrects a completed/expired session and never touches an explicit session (whose findings the agent owns via session_update). Bumping updated_at doubles as a heartbeat. No-op on an incomplete identity or empty findings (a turn with nothing to harvest leaves any prior harvest intact). Reports whether a row was updated.

func UpdateProposalPayload

func UpdateProposalPayload(ctx context.Context, db *sql.DB, id string, payload map[string]any) error

UpdateProposalPayload replaces a pending proposal's payload (e.g. retargeting a reproject to a different project before applying). It errors if the proposal is missing or not pending, so a resolved proposal is never silently rewritten.

func UpdateSession

func UpdateSession(ctx context.Context, db *sql.DB, s core.Session) error

UpdateSession updates the mutable fields of a session (status, findings, metadata, project scope, updated_at) by id.

func UpdateTask

func UpdateTask(ctx context.Context, db *sql.DB, id string, patch TaskPatch, actor string, now time.Time) (core.Task, error)

UpdateTask applies patch to the task and returns the updated task (with deps). Moving to a terminal status stamps closed_at; reopening clears it. Added deps are validated for existence and cycles like CreateTask.

actor is the caller's session id (empty for callers with no session). A task with a live claim (see core.Task.ClaimLive) is locked to its holder: any mutation by a different actor is rejected with ErrTaskClaimConflict, so an agent's claimed task cannot be edited or closed out from under it. The holder itself, an expired lease, and the owner override (ForceReleaseTask) are the only ways past the lock.

func UpsertEmbedding

func UpsertEmbedding(ctx context.Context, db *sql.DB, itemID, kind, model string, vec []float32) error

UpsertEmbedding stores (or replaces) the vector for an item. dims is recorded from the vector length so a later model change is detectable.

func UtilityDemandByProject added in v0.4.1

func UtilityDemandByProject(ctx context.Context, db *sql.DB, now time.Time, window time.Duration) (map[string]UtilityProjectDemand, error)

UtilityDemandByProject walks the query-gated demand events (recall hits, prompt-recall matches, explicit reads -- the same classes utility scores) grouped by the event's project slug. Passive briefing injections do not count, mirroring the score. The window bounds the Recent* counters; Earliest is all-time.

func UtilityScores added in v0.4.1

func UtilityScores(ctx context.Context, db *sql.DB) (map[string]float64, error)

UtilityScores loads item id -> normalized utility for every item with nonzero demand. It is the single wiring point from the stats projection into the ranking paths (briefing, prompt-recall, recall), which read the stored score as-is -- rebuilt hourly by the gardener and on console loads -- and never replay the event log themselves.

Types

type AgentError added in v0.4.2

type AgentError struct {
	TS        time.Time
	SessionID string
	Project   string
	Surface   string         // "tool" | "hook"
	Key       string         // surface "tool": tool name; surface "hook": stage label
	Error     string         // first line of the error, as the payload recorded it
	Args      map[string]any // surface "tool" only: the call's (truncated) arguments
}

AgentError is one agent-facing failure read back out of the event log: an MCP/CLI tool call that returned an error (surface "tool") or a hook-stage failure swallowed fail-open (surface "hook"). The gardener's tool-error pass clusters these into proposals.

func HookErrorsSince added in v0.4.2

func HookErrorsSince(ctx context.Context, db *sql.DB, since time.Time) ([]AgentError, error)

HookErrorsSince returns the hook.error events at or after the cutoff, oldest first. Rows without a stage or error are skipped.

func ToolErrorsSince added in v0.4.2

func ToolErrorsSince(ctx context.Context, db *sql.DB, since time.Time) ([]AgentError, error)

ToolErrorsSince returns the failed tool.call events at or after the cutoff, oldest first. Rows without an error or tool name are unusable for clustering and are skipped.

type BlockedTask

type BlockedTask struct {
	Task     core.Task   `json:"task"`
	Blockers []core.Task `json:"blockers"`
}

BlockedTask is an open task that is not ready, paired with the blockers still keeping it off the ready queue (open/in_progress dependencies).

func AllBlockedTasks

func AllBlockedTasks(ctx context.Context, db *sql.DB) ([]BlockedTask, error)

AllBlockedTasks returns open-but-not-ready tasks across every project, each with its still-open blockers.

func BlockedTasks

func BlockedTasks(ctx context.Context, db *sql.DB, project string) ([]BlockedTask, error)

BlockedTasks returns a project's open-but-not-ready tasks, each with its still-open blockers, so a caller can render the dependency chain legibly.

func BlockedTasksForPlan

func BlockedTasksForPlan(ctx context.Context, db *sql.DB, project, plan string) ([]BlockedTask, error)

BlockedTasksForPlan returns a plan's open-but-not-ready step tasks, each with its still-open blockers.

type ClaimResult

type ClaimResult struct {
	Task        core.Task
	Reclaimed   bool
	PriorHolder string
}

ClaimResult reports the outcome of a successful ClaimTask. Reclaimed is true when a different session's lapsed lease was stolen; PriorHolder then names that session so the caller can record a reclaim event.

func ClaimTask

func ClaimTask(ctx context.Context, db *sql.DB, id, sessionID string, lease time.Duration, now time.Time) (ClaimResult, error)

ClaimTask atomically claims a task for sessionID, moving it to in_progress and stamping a lease that expires at now+lease. It is a compare-and-set (mirrors ResolveProposal): the write lands only when the task is claimable --

(a) open and ready (no open/in_progress blocker), or
(b) in_progress and held by sessionID (a re-claim / heartbeat that refreshes
    the lease), or
(c) in_progress but carrying no live claim -- nobody holds it, so it is up for
    grabs (a steal from a dead holder, or a row that was never claimed).

Otherwise it returns an error naming the actual refusal: ErrTaskClaimConflict when another live lease holds the task, ErrTaskBlocked when the task is open with an unfinished dependency (naming the blockers), ErrTaskClosed when it is done/dropped. Lease expiry is enforced lazily here -- there is no background sweeper. sessionID must be non-empty.

core.Task.ClaimLive is the single authority on whether a task is held, and branches (b)+(c) are exactly its negation for an in_progress row: an empty claimed_by (no holder), a NULL lease_expires_at (no lease stamped), or a lapsed lease (lease_expires_at <= now -- ClaimLive uses After, so a lease expiring exactly at now is already dead). Branch (c) must tolerate a claimless in_progress row because UpdateTask can produce one: `seam task start <id>` and MCP tasks_update status=in_progress patch the status without stamping a claim, which is a legitimate owner action. Such a row is already freely editable by anyone (UpdateTask's holder-lock also defers to ClaimLive), so refusing to claim it would strand it as permanently unclaimable until someone reopened it.

Branch (a) deliberately ignores claimed_by: a live claim exists only on an in_progress task with an unexpired lease, and every path that writes status='open' clears the claim fields, so a claim value on an open row can only be stale residue (rows written before reopen released claims). Claiming such a row overwrites the residue, self-healing it.

type CoverageBucket

type CoverageBucket struct {
	Label   string `json:"label"`
	Total   int    `json:"total"`   // sessions created in the bucket
	Covered int    `json:"covered"` // of those, how many retained knowledge
}

CoverageBucket is one time bucket of the session-coverage trend: a pre-formatted tick label, how many sessions started in it, and how many of those retained knowledge. Total == 0 means no sessions in the bucket, so its coverage is undefined (the trend renders it as a dip to the floor, not a ceiling).

func SessionCoverageBuckets

func SessionCoverageBuckets(ctx context.Context, db *sql.DB, w RetrievalWindow, now time.Time) ([]CoverageBucket, error)

SessionCoverageBuckets computes the windowed session-coverage trend in the viewer's local time -- hourly for the 24h window, daily otherwise (for "all", daily from the earliest session). It applies the same covered-ness test as GetSessionCoverage over the same window, so the coverage hero and this trend describe the same set of sessions. Buckets are contiguous (a quiet stretch reads as a dip), and it shares localBucketAxis/bucketKey with the injection trend so the two charts' x-axes line up. Returns nil when the window holds no sessions.

type DayCounts added in v0.4.10

type DayCounts struct {
	TasksClosed     int `json:"tasksClosed"`     // tasks whose closed_at landed in the window, status done
	MemoriesWritten int `json:"memoriesWritten"` // memory.written events
	NotesWritten    int `json:"notesWritten"`    // note.written events
	PlansTouched    int `json:"plansTouched"`    // distinct (project, plan) whose steps moved
	Sessions        int `json:"sessions"`        // sessions started
}

DayCounts is one day of fleet output, judged from the tasks table and the event log. The day tape renders today's; the trailing week average gives its comparison.

func GamificationDayCounts added in v0.4.10

func GamificationDayCounts(ctx context.Context, db *sql.DB, from, to time.Time) (DayCounts, error)

GamificationDayCounts judges the fleet's output between from (inclusive) and to (exclusive) in one round trip. A gamification-only surface: callers gate on the feature before asking, so a disabled install never runs the query.

type EmbeddingModelStat added in v0.4.1

type EmbeddingModelStat struct {
	Model    string    `json:"model"`
	Dims     int       `json:"dims"`
	Count    int       `json:"count"`
	Memories int       `json:"memories"`
	Notes    int       `json:"notes"`
	Updated  time.Time `json:"updated"`
}

EmbeddingModelStat is one (model, dims) group of stored vectors: how many items it covers, split by kind, and when a vector in it was last written.

type EmbeddingStats added in v0.4.1

type EmbeddingStats struct {
	Total          int                  `json:"total"`
	Models         []EmbeddingModelStat `json:"models"`
	ActiveMemories int                  `json:"activeMemories"`
	Notes          int                  `json:"notes"`
	Missing        int                  `json:"missing"`
}

EmbeddingStats summarizes the vector index for the console's Settings page: what is stored (grouped by model), how big the embeddable corpus is, and how much of it has no vector at all. Missing counts active memories and notes without an embeddings row; invalidated memories keep their vectors but are not owed one, so they never count as missing.

func GetEmbeddingStats added in v0.4.1

func GetEmbeddingStats(ctx context.Context, db *sql.DB) (EmbeddingStats, error)

GetEmbeddingStats reads the vector-index summary. It is a set of plain aggregate queries -- no BLOB is materialized.

type FinishLinePlan added in v0.4.10

type FinishLinePlan struct {
	PlanRollup
	Project   string   `json:"project"`
	Remaining []string `json:"remaining"`
}

FinishLinePlan is one near-done plan for the momentum finish-line card: its rollup, the project it belongs to, and the exact remaining step titles.

func FinishLinePlans added in v0.4.10

func FinishLinePlans(ctx context.Context, db *sql.DB) ([]FinishLinePlan, error)

FinishLinePlans returns every active plan at the finish line (AtFinishLine) across all projects, most recently active first, each carrying the titles of its not-yet-closed steps oldest-first. A momentum-only surface: callers gate on the feature before asking, so a disabled install never runs the query.

type GamificationRecords added in v0.4.10

type GamificationRecords struct {
	TasksDay    RecordMark `json:"tasksDay"`    // most tasks closed in one day
	MemoriesDay RecordMark `json:"memoriesDay"` // most memories written in one day
	LiveAgents  RecordMark `json:"liveAgents"`  // most agents live at one moment
}

GamificationRecords is the stored personal-bests ledger.

type IdentifierMatchKind added in v0.4.9

type IdentifierMatchKind string

IdentifierMatchKind describes how a search query matched an entity's stable identifier. The order is significant: lower priorities are stronger and are promoted ahead of ordinary text/semantic matches by the console search.

const (
	IdentifierMatchExactID         IdentifierMatchKind = "exact_id"
	IdentifierMatchExactIdentifier IdentifierMatchKind = "exact_identifier"
	IdentifierMatchIDPrefix        IdentifierMatchKind = "id_prefix"
	IdentifierMatchIdentifier      IdentifierMatchKind = "identifier"
)

func IDIdentifierMatch added in v0.4.9

func IDIdentifierMatch(query, id string) IdentifierMatchKind

IDIdentifierMatch classifies a query against an entity id. Full ids match case-insensitively. A partial match is accepted only for an 8-25 character Crockford-base32 ULID prefix; this prevents a query such as "01" from flooding search with nearly every recently-created entity.

func NaturalIdentifierMatch added in v0.4.9

func NaturalIdentifierMatch(query, identifier string) IdentifierMatchKind

NaturalIdentifierMatch classifies a query against a human-facing identifier such as a memory name, note slug, project slug, or plan slug.

type IsolationTightenEffects added in v0.4.10

type IsolationTightenEffects struct {
	// Project is the slug being tightened.
	Project string `json:"project"`
	// From is the project's current state, To the requested one.
	From core.Isolation `json:"from"`
	To   core.Isolation `json:"to"`
	// Families names every family the project would be removed from, sorted.
	Families []string `json:"families,omitempty"`
	// Parent is the parent slug the tighten would detach from, "" when the
	// project already has no parent.
	Parent string `json:"parent,omitempty"`
	// Children lists the slugs parented to this project, sorted. Non-empty means
	// the tighten is BLOCKED: they must be re-parented first, and an apply writes
	// nothing.
	Children []string `json:"children,omitempty"`
}

IsolationTightenEffects reports what tightening one project's isolation would change, or what blocks it. Isolation requires a standalone project (the topology rule), so a tighten detaches the project from every family and from its parent, and refuses outright while it still has children.

PreviewIsolationTighten produces it without mutating anything -- the console's confirm panel and the CLI's --yes path describe the change from it -- and TightenProjectIsolation returns it again after performing exactly that.

func PreviewIsolationTighten added in v0.4.10

func PreviewIsolationTighten(ctx context.Context, db *sql.DB, slug string, state core.Isolation) (IsolationTightenEffects, error)

PreviewIsolationTighten reports what tightening slug to state would change, mutating nothing.

Blocking children are part of the report, not an error: read Blocked() and render Children so the owner can re-parent them. Errors are reserved for what makes the question unanswerable -- an unknown slug (ErrProjectNotFound), an unrecognized state, or a state that is not tighter than the current one (ErrNotATighten; loosening needs no ceremony, call SetProjectIsolation). Requesting the state the project is already in is a legal no-op tighten: it re-asserts the standalone rule instead of erroring on a double submit.

func TightenProjectIsolation added in v0.4.10

func TightenProjectIsolation(ctx context.Context, db *sql.DB, slug string, state core.Isolation, now time.Time) (IsolationTightenEffects, error)

TightenProjectIsolation applies exactly what PreviewIsolationTighten described, in one transaction: leave every family, clear the parent link, then set the isolation state. It recomputes the effects inside that transaction and returns them, so a caller that previewed first can see whether the world moved in between. With children present nothing is written and the error is ErrIsolationHasChildren -- the returned effects still list them.

The isolation UPDATE never bumps updated_at (isolation is owner curation, not authorship, and must not churn recency sorts); the parent detach does, because that is a real topology change and SetProjectParent's contract.

func (IsolationTightenEffects) Blocked added in v0.4.10

func (e IsolationTightenEffects) Blocked() bool

Blocked reports whether child projects prevent the tighten.

func (IsolationTightenEffects) DetachesTopology added in v0.4.10

func (e IsolationTightenEffects) DetachesTopology() bool

DetachesTopology reports whether applying would change topology (leave a family, clear the parent link) rather than just flip the isolation flag. It is what makes the confirm step consequential.

type KindReach

type KindReach struct {
	Kind     string `json:"kind"`
	Injects  int    `json:"injects"`
	Memories int    `json:"memories"`
}

KindReach is one memory kind's share of a window's injection activity: total injections (volume) and the number of distinct active memories of that kind that were surfaced at least once.

type KnowledgeIdentifierHit added in v0.4.9

type KnowledgeIdentifierHit struct {
	ItemID  string
	Kind    string
	Match   IdentifierMatchKind
	Updated time.Time
}

KnowledgeIdentifierHit is a memory/note candidate matched through its stable id or natural identifier rather than through FTS or an embedding.

func SearchKnowledgeIdentifiersSince added in v0.4.9

func SearchKnowledgeIdentifiersSince(ctx context.Context, db *sql.DB, query string, kinds, projects []string, since time.Time, limit int) ([]KnowledgeIdentifierHit, error)

SearchKnowledgeIdentifiersSince searches the index mirrors for memory names, note slugs, and memory/note ids. Identifier predicates, project scope, validity, and the time window all run before the limit so an exact match cannot be crowded out by unrelated FTS candidates.

type LabSummary added in v0.3.9

type LabSummary struct {
	Lab          string    `json:"lab"`
	Trials       int       `json:"trials"`
	Pass         int       `json:"pass"`
	Fail         int       `json:"fail"`
	Partial      int       `json:"partial"`
	Inconclusive int       `json:"inconclusive"`
	Other        int       `json:"other"`
	Projects     []string  `json:"projects"` // distinct project slugs ("" = global), sorted
	Sessions     int       `json:"sessions"` // distinct recording sessions
	FirstAt      time.Time `json:"firstAt"`
	LastAt       time.Time `json:"lastAt"`
}

LabSummary aggregates one lab's trials for the console: outcome tallies over the conventional values (anything else -- including an empty outcome -- lands in Other), the distinct projects and sessions its trials touched, and the first/last trial stamps. A lab exists only as the label its trials carry, so this is the lab's whole identity.

func ListLabs added in v0.3.9

func ListLabs(ctx context.Context, db *sql.DB) ([]LabSummary, error)

ListLabs returns every lab with its aggregate summary, most recently active first (ties broken by name). Labs are few -- one per line of investigation -- so this is a single GROUP BY over trials with no pagination.

type MemoryStat

type MemoryStat struct {
	ID           string     `json:"id"`
	Name         string     `json:"name"`
	Kind         string     `json:"kind"`
	Project      string     `json:"project"`
	Injects      int        `json:"injects"`
	Reads        int        `json:"reads"`
	Sessions     int        `json:"sessions"` // distinct sessions reached (retrieval-reach view)
	Updated      time.Time  `json:"updated"`
	LastInjected *time.Time `json:"lastInjected,omitempty"`
}

MemoryStat is an active memory annotated with its retrieval counts, for the Retrieval page's top-injected and stale lists.

type MemoryVector

type MemoryVector struct {
	ID          string
	Project     string
	Name        string
	Description string
	Kind        string
	UpdatedAt   time.Time
	Vec         []float32
}

MemoryVector pairs an active memory's index metadata with its stored embedding vector, for the gardener's pairwise dedup scan.

func ActiveMemoryVectors

func ActiveMemoryVectors(ctx context.Context, db *sql.DB, model string) ([]MemoryVector, error)

ActiveMemoryVectors returns the vectors of all active memories embedded under model, oldest-created first (stable ordering for deterministic pairing). It joins the embeddings table, so a memory with no vector for that model is omitted (the gardener simply cannot dedup it semantically).

type Migration

type Migration struct {
	Version int
	SQL     string
}

Migration is a single numbered schema migration.

type NamedCount

type NamedCount struct {
	ID    string `json:"id"`
	Name  string `json:"name"`
	Count int    `json:"count"`
}

NamedCount pairs an item's name with a count, for "top N" lists.

type NamedScore added in v0.4.1

type NamedScore struct {
	ID    string  `json:"id"`
	Name  string  `json:"name"`
	Score float64 `json:"score"`
}

NamedScore pairs an item's name with a float score, for "top N" lists.

type NavCounts struct {
	Sessions         int // all sessions
	Memories         int // active memories
	Notes            int // all notes
	OpenTasks        int // open or in_progress
	PendingProposals int // pending gardener proposals
	Projects         int // registered projects
	Plans            int // distinct plan:<slug> compositions (captures + composed)
	Labs             int // distinct trial labs
	Trials           int // all recorded trials
}

NavCounts are the cheap roll-up counts the console shows in its sidebar. It is a handful of COUNT queries, safe to run on every page load.

func GetNavCounts

func GetNavCounts(ctx context.Context, db *sql.DB) (NavCounts, error)

GetNavCounts computes the sidebar counts.

type OnceRecorder added in v0.4.10

type OnceRecorder interface {
	RecordOnce(ctx context.Context, e core.Event) (id string, recorded bool, err error)
}

OnceRecorder is the once-ever latch the milestone layer mints through -- events.Recorder.RecordOnce, named as a role interface so this package does not import internal/events (whose in-package tests import store).

type PlanRollup

type PlanRollup struct {
	Slug         string    `json:"slug"`
	Total        int       `json:"total"`
	Done         int       `json:"done"`
	InFlight     int       `json:"inFlight"`
	Claimable    int       `json:"claimable"`
	LastActivity time.Time `json:"lastActivity"`
}

PlanRollup is the per-plan aggregate the briefing surfaces: Total step tasks, how many are Done (closed), InFlight (in_progress), and Claimable (ready). LastActivity is the newest updated_at across the plan's steps.

func ActivePlans

func ActivePlans(ctx context.Context, db *sql.DB, project string) ([]PlanRollup, error)

ActivePlans returns a rollup for each not-yet-complete plan in a project (plans whose every step is closed are omitted, like a done stage). Claimable counts ready open steps (same readiness rule as ReadyTasksForPlan); the plan's status is derived, never stored. One grouped query covers every plan. Ordered most-recently-active first (newest step updated_at), ties broken by slug, so the briefing and the console lead with what just moved.

This is a briefing contract: dropping the complete plans is the whole point, so the filter lives here rather than in the shared query.

func PlanRollupsForProject added in v0.4.9

func PlanRollupsForProject(ctx context.Context, db *sql.DB, project string) ([]PlanRollup, error)

PlanRollupsForProject returns a rollup for EVERY plan in a project, complete or not, in the same order. The console's plan workspace partitions active from completed itself (a finished plan still gets a rollup line); ActivePlans is this set with the complete plans dropped, so the two cannot disagree.

func (PlanRollup) AtFinishLine added in v0.4.10

func (p PlanRollup) AtFinishLine() bool

AtFinishLine reports whether this rollup is an active plan within reach of done: at least PlanFinishLinePct of its steps closed, with work remaining. Integer math, never rounded up: 4/5 qualifies, 3/4 (75%) does not.

func (PlanRollup) FinishLinePhrase added in v0.4.10

func (p PlanRollup) FinishLinePhrase() string

FinishLinePhrase renders the distance-to-done phrase both momentum surfaces share, so the card and the briefing cannot drift: "one step from shipped".

func (PlanRollup) StepsLeft added in v0.4.10

func (p PlanRollup) StepsLeft() int

StepsLeft is how many steps remain open or in flight.

type PlanSearchRow

type PlanSearchRow struct {
	Slug    string
	Project string
	Title   string
	// Favorite is true when any of the plan's tagged notes is favorited. The
	// authoritative flag lives on the plan's primary note, but this query cannot
	// cheaply identify the primary; the two only disagree after deliberate
	// hand-editing of a secondary note's frontmatter.
	Favorite bool
	Updated  time.Time
}

PlanSearchRow is one plan hit. A plan is a composition, not a table, so a row is identified by (Project, Slug); Title is the best label found -- a matching note's title, or the slug itself when only tasks carry it.

func SearchPlans

func SearchPlans(ctx context.Context, db *sql.DB, q string, limit int) ([]PlanSearchRow, error)

SearchPlans returns plans whose slug or narrative-note title contains q, newest-updated first.

Plans are bi-sourced (a note tagged plan:<slug>, and tasks carrying plan_slug), and either source alone can carry a match: a plan whose steps exist but whose note does not, or vice versa. Both are queried and merged deduped by (project, slug), keeping the newer Updated and preferring a note's title over the slug fallback -- the same merge the Plans screen does, done here so a search hit cannot disagree with the page it links to.

func SearchPlansSince added in v0.3.9

func SearchPlansSince(ctx context.Context, db *sql.DB, q string, since time.Time, limit int) ([]PlanSearchRow, error)

SearchPlansSince is SearchPlans restricted to matching plan sources updated at or after since. Plans are merged before the bound is applied, so either a matching narrative or matching task can keep the composition in-window.

type PlanShippedRef added in v0.4.10

type PlanShippedRef struct {
	Project string `json:"project"`
	Slug    string `json:"slug"`
}

PlanShippedRef identifies one shipped plan: the plan.shipped settlement event's project and plan slug (its item_id).

func PlansShippedSince added in v0.4.10

func PlansShippedSince(ctx context.Context, db *sql.DB, since time.Time) ([]PlanShippedRef, error)

PlansShippedSince returns the plan.shipped settlements recorded at or after since, oldest first -- one per plan, because the settlement is latched once-ever per plan slug at mint time. It backs the Plans page's monthly shipped count and settle marks; a momentum-only surface, so callers gate on the feature before asking and a disabled install never runs the query.

type ProjectBoardRow

type ProjectBoardRow struct {
	Project      string    `json:"project"`      // slug; "" is the global scope
	Memories     int       `json:"memories"`     // active memories, project = slug (strict, matches GetProjectCounts.Memories)
	Sessions     int       `json:"sessions"`     // all sessions project_slug = slug (matches GetProjectCounts.Sessions)
	LiveSessions int       `json:"liveSessions"` // live sessions: active AND updated within core.SessionIdleTTL (matches the Sessions screen)
	OpenTasks    int       `json:"openTasks"`    // status IN ('open','in_progress') (matches GetProjectCounts.OpenTasks)
	Blocked      int       `json:"blocked"`      // open tasks with >=1 open/in_progress blocker (== len(BlockedTasks))
	Notes        int       `json:"notes"`        // notes project = slug (matches GetProjectCounts.Notes)
	LastActive   time.Time `json:"lastActive"`   // MAX(sessions.updated_at) for the project; zero when none
	TokensTotal  int       `json:"tokensTotal"`  // SUM(sessions.total_tokens): real model token burn harvested from transcripts
	Unregistered bool      `json:"unregistered"` // slug seen in data tables but absent from the projects table
	ReachRate    int       `json:"reachRate"`    // Surfaced/Active rounded %, from BuildRetrievalReport; 0 when absent
	Surfaced     int       `json:"surfaced"`     // distinct active memories surfaced >=1x in the window
	Active       int       `json:"active"`       // active memories (reach denominator) per BuildRetrievalReport
}

ProjectBoardRow is one project's health roll-up for the project board: the same strict per-slug counts GetProjectCounts computes for a single project, batched across every project in one pass, plus liveness (live sessions), backpressure (blocked tasks), recency (last session activity), and retrieval reach joined from BuildRetrievalReport.

Counts are STRICT per-slug (project = slug / project_slug = slug), never a global union: the global scope ("") is its own row whose counts are the global-scope counts, never folded into each named project. A slug that appears in the data tables but has no projects-table row (files are the source of truth and can drift ahead of the registry) still gets a row, flagged Unregistered so the caller can surface the drift rather than hide it.

func ProjectsWithCounts

func ProjectsWithCounts(ctx context.Context, db *sql.DB, window RetrievalWindow, now time.Time, idleTTL time.Duration) ([]ProjectBoardRow, error)

ProjectsWithCounts returns a ProjectBoardRow for every project -- every slug that appears in memories_index / sessions / tasks / notes_index unioned with the projects table -- with strict per-slug counts, liveness, blocked-task backpressure, and last-active recency computed in SQL, then retrieval reach (ReachRate/Surfaced/Active) joined by slug from BuildRetrievalReport over the given window. LiveSessions counts sessions that are active AND updated within idleTTL of now (<= 0 falls back to core.SessionIdleTTL), matching the Sessions screen's live bucket, so an active-but-idle session awaiting the reaper never inflates the board.

The counts mirror GetProjectCounts exactly (same strict per-slug predicates), so a single row equals the single-project peek. The global scope ("") is its own row (never folded into named projects); it is never flagged Unregistered because the global scope is legitimately never registered. Rows are ordered by slug (the global "" scope first).

type ProjectCounts

type ProjectCounts struct {
	Memories  int `json:"memories"`  // active memories in the project
	Sessions  int `json:"sessions"`  // sessions scoped to the project
	OpenTasks int `json:"openTasks"` // open or in_progress tasks
	Notes     int `json:"notes"`     // notes in the project
}

ProjectCounts are the per-project totals the console project peek shows. The channels do not overlap in the way coverage does; each is a plain count.

func GetProjectCounts

func GetProjectCounts(ctx context.Context, db *sql.DB, slug string) (ProjectCounts, error)

GetProjectCounts computes the per-project roll-up for one slug in a single round trip (scalar subqueries). It backs the console project peek.

type ProjectIsolation added in v0.4.10

type ProjectIsolation struct {
	Slug  string         `json:"slug"`
	State core.Isolation `json:"state"`
}

ProjectIsolation pairs a project slug with its isolation state.

func IsolatedSlugs added in v0.4.10

func IsolatedSlugs(ctx context.Context, db *sql.DB, slugs []string) ([]ProjectIsolation, error)

IsolatedSlugs returns the isolated projects among slugs, in the order given, each with the state that fences it. Topology writers call it to refuse a link before building one: isolation requires a standalone project, so a fenced slug may not join a family or take a parent. Blank and unregistered slugs are open by construction and never appear.

type ProjectPlan added in v0.4.10

type ProjectPlan struct {
	PlanRollup
	Project string `json:"project"`
}

ProjectPlan is a plan rollup paired with the project that owns it, for the cross-project surfaces (a plan is keyed by project+slug, never slug alone).

func AllPlanRollups added in v0.4.10

func AllPlanRollups(ctx context.Context, db *sql.DB) ([]ProjectPlan, error)

AllPlanRollups returns a rollup for every plan across every project, complete or not, most recently active first. It backs the Now screen's moving-plans rail; per-project views use PlanRollupsForProject.

type ProjectReach

type ProjectReach struct {
	Project   string `json:"project"`
	Surfaced  int    `json:"surfaced"`  // distinct active memories of this project surfaced
	Active    int    `json:"active"`    // total active memories in this project (reach denominator)
	ReachRate int    `json:"reachRate"` // Surfaced / Active, rounded %
	Injects   int    `json:"injects"`   // injection volume attributable to this project's memories
	Created   int    `json:"created"`   // memories created within the window (incl. since-retired ones)
	Retired   int    `json:"retired"`   // memories retired (superseded/archived) within the window
}

ProjectReach is one project's reach within the window: how many of its active memories were surfaced at least once, out of its total active memories, plus the injection volume attributable to them. It lets the global reach number be read per knowledge base -- a project whose memories never surface shows 0% even while the global rate looks healthy. Project "" is the global scope.

type ProjectStage added in v0.4.10

type ProjectStage string

ProjectStage is a project's latched maturity stage.

const (
	StageSeedling    ProjectStage = "seedling"
	StageSprouting   ProjectStage = "sprouting"
	StageEstablished ProjectStage = "established"
	StageDeepRooted  ProjectStage = "deep-rooted"
)

func ComputeStage added in v0.4.10

func ComputeStage(in StageInputs, now time.Time) ProjectStage

ComputeStage evaluates the highest stage these inputs satisfy right now, with no latch applied.

func (ProjectStage) Rank added in v0.4.10

func (s ProjectStage) Rank() int

Rank orders the stages for the never-regress comparison. Unknown reads as seedling, so a corrupt stored value can only under-report, never invent.

type ProjectStageInfo added in v0.4.10

type ProjectStageInfo struct {
	Stage  ProjectStage `json:"stage"`
	Since  time.Time    `json:"since,omitzero"`
	Inputs StageInputs  `json:"inputs"`
}

ProjectStageInfo is one project's effective stage plus the inputs it was judged on, so the console can say why the next stage has not been reached.

type Proposal

type Proposal struct {
	ID         string         `json:"id"`
	Kind       string         `json:"kind"`
	Payload    map[string]any `json:"payload"`
	Status     string         `json:"status"`
	CreatedAt  time.Time      `json:"createdAt"`
	ResolvedAt *time.Time     `json:"resolvedAt,omitempty"`
	Result     map[string]any `json:"result,omitempty"`
}

Proposal is one gardener suggestion awaiting owner review. Payload carries the kind-specific detail; every payload includes a stable "key" string the gardener uses to avoid re-proposing the same thing on a later pass. Result is what applying it produced (the note it wrote, the task it opened, the memory it moved) -- nil until applied, and the handle undo uses to find the artifact again.

func CreateProposal

func CreateProposal(ctx context.Context, db *sql.DB, kind string, payload map[string]any) (Proposal, error)

CreateProposal inserts a pending gardener proposal and returns it. The caller supplies the kind and payload; the id and created_at are stamped here.

func HiddenProposals added in v0.4.10

func HiddenProposals(ctx context.Context, db *sql.DB) ([]Proposal, error)

HiddenProposals returns every forever-hidden proposal, newest decision first. It backs the console's hidden list, which is the only durable surface naming what the owner has blocked -- "Recently decided" scrolls a hide off within a handful of decisions, and a block nobody can see is a block nobody can lift.

func PendingProposals

func PendingProposals(ctx context.Context, db *sql.DB, kind string) ([]Proposal, error)

PendingProposals returns pending proposals, newest first. kind filters by proposal kind when non-empty.

func ProposalByID

func ProposalByID(ctx context.Context, db *sql.DB, id string) (Proposal, bool, error)

ProposalByID returns one proposal. found is false when absent.

func RecentResolvedProposals added in v0.4.6

func RecentResolvedProposals(ctx context.Context, db *sql.DB, limit int) ([]Proposal, error)

RecentResolvedProposals returns the most recently applied or dismissed proposals, newest resolution first, capped at limit. It backs the console's "Recently decided" section, where each row offers an undo.

type ProposalBlock added in v0.4.10

type ProposalBlock struct {
	Hard        bool
	DismissedAt time.Time
}

ProposalBlock is why one payload key is suppressed. Hard is the permanent block: the key belongs to a proposal that is pending, was applied, or was hidden forever, and no amount of new evidence should raise it again. When Hard is false the key was only ever regularly dismissed, and DismissedAt is the newest such decision -- evidence that postdates it is a recurrence the owner has not yet answered, so the pattern may be proposed again.

type RecallHitQuery added in v0.4.1

type RecallHitQuery struct {
	Project string
	Query   string
}

RecallHitQuery is the (project, query) of one successful recall tool call.

func RecallHitQueriesSince added in v0.4.1

func RecallHitQueriesSince(ctx context.Context, db *sql.DB, since time.Time) ([]RecallHitQuery, error)

RecallHitQueriesSince returns the query text of every recall tool call that surfaced hits since the cutoff. The memory-wanted pass uses these to suppress miss groups whose query also succeeds sometimes -- an intermittent miss is a ranking problem, not a knowledge gap.

type RecallMiss added in v0.4.1

type RecallMiss struct {
	TS        time.Time
	SessionID string
	Project   string
	Query     string
}

RecallMiss is one zero-hit recall tool call read back out of the event log: an agent deliberately searched and found nothing. The gardener's memory-wanted pass clusters these into proposals.

func RecallMissesSince added in v0.4.1

func RecallMissesSince(ctx context.Context, db *sql.DB, since time.Time) ([]RecallMiss, error)

RecallMissesSince returns the recall.miss events at or after the cutoff, oldest first. Rows whose payload carries no query text are unusable for clustering and are skipped.

type RecordCrossing added in v0.4.10

type RecordCrossing struct {
	Key   string // "tasks_day" | "memories_day" | "live_agents"
	Label string // owner-facing phrasing of the record
	N     int
	Prev  int
}

RecordCrossing is one record advancing: which record, the new value, and the value it beat. The console mints the once-only celebration event from it.

type RecordMark added in v0.4.10

type RecordMark struct {
	N   int    `json:"n"`
	Day string `json:"day"` // bucketKey-formatted local day
}

RecordMark is one personal best: the value and the local day it was set.

type RepoMapAdoption added in v0.4.6

type RepoMapAdoption struct {
	Slug     string   // the adopted project slug
	NewPath  string   // the repo root that now owns the slug
	OldPaths []string // the dead map entries that owned it, now removed
}

RepoMapAdoption reports that RegisterProjectForCWD re-pointed an existing project at a repo's new location instead of minting a fresh -N slug: every map entry owning the derived slug named a path that no longer exists on disk, so the repo was moved, not duplicated. The remap already happened; callers only surface it (an event, a console notice).

func RegisterProjectForCWD

func RegisterProjectForCWD(ctx context.Context, db *sql.DB, cwd string) (string, *RepoMapAdoption, error)

RegisterProjectForCWD resolves cwd to a project slug like ResolveProjectForCWD, but grows the map at runtime when cwd falls outside every mapped repo: it finds the enclosing git repository, derives a slug from that repo's directory name, records repoRoot -> slug in the repo_project_map, and ensures a projects-table row. This is how the repo->project map evolves as agents work in new repos -- no recompile, no manual map-repo. For an already-mapped cwd it still backfills the registry row, so project_list stays complete. A blank cwd, or a cwd outside any git repo, resolves to the global scope ("") and registers nothing.

A moved repo heals itself here: when the derived slug is owned only by map entries whose paths no longer exist, the repo was moved, and the existing project is adopted (the dead entries are replaced by the new root) instead of minting a -N slug that would silently split the project's history. The non-nil *RepoMapAdoption reports that remap; it is nil on every other path.

type RetrievalReport

type RetrievalReport struct {
	Window           RetrievalWindow `json:"window"`
	Injected         int             `json:"injected"`         // item-level injections in the window (volume) == sum(Trend)
	InjectedTokens   int             `json:"injectedTokens"`   // estimated tokens of injected context in the window (reach's cost side)
	MemoriesSurfaced int             `json:"memoriesSurfaced"` // distinct active memories surfaced >=1x
	ActiveMemories   int             `json:"activeMemories"`   // total active memories (reach denominator)
	ReachRate        int             `json:"reachRate"`        // MemoriesSurfaced / ActiveMemories, rounded %
	SessionsReached  int             `json:"sessionsReached"`  // distinct sessions that received >=1 injection
	CreatedInWindow  int             `json:"createdInWindow"`  // memories created within the window (incl. since-retired ones)
	RetiredInWindow  int             `json:"retiredInWindow"`  // memories retired (superseded/archived) within the window
	ByKind           []KindReach     `json:"byKind"`
	ByProject        []ProjectReach  `json:"byProject"` // reach sliced per project (denominator = that project's active memories)
	Top              []MemoryStat    `json:"top"`       // most-injected active memories, with sessions reached
	Trend            []TrendBucket   `json:"trend"`
	Hourly           bool            `json:"hourly"` // trend granularity: hourly (24h window) vs daily

	// Distinct-count series over the same axis as Trend. Volume and reach move
	// independently -- one memory injected a thousand times is a tall Trend and a
	// flat SurfacedTrend -- so a reach sparkline has to plot its own numerator
	// rather than borrow the volume curve.
	SurfacedTrend []TrendBucket `json:"surfacedTrend"` // distinct memories surfaced per bucket
	SessionTrend  []TrendBucket `json:"sessionTrend"`  // distinct sessions reached per bucket

	// Loop health: whether what briefings push is what agents actually pull.
	// Demand = query-gated signals only (recall hits, prompt matches, explicit
	// reads); passive briefing injections are exposure, not demand.
	BriefingSurfaced   int          `json:"briefingSurfaced"`   // distinct active memories briefings surfaced in the window
	DemandedOfSurfaced int          `json:"demandedOfSurfaced"` // of those, how many also saw demand in the window
	DemandRate         int          `json:"demandRate"`         // DemandedOfSurfaced / BriefingSurfaced, rounded %
	WasteShare         int          `json:"wasteShare"`         // % of injected tokens spent on memories with no demand in 30d
	PromptMatches      int          `json:"promptMatches"`      // prompt-recall injections in the window
	RecallMisses       int          `json:"recallMisses"`       // hook.prompt (matched nothing) events in the window
	MissRate           int          `json:"missRate"`           // RecallMisses / (RecallMisses+PromptMatches), rounded %
	ToolMisses         int          `json:"toolMisses"`         // recall.miss (zero-hit recall calls) events in the window
	DeadWeight         []MemoryStat `json:"deadWeight"`         // most-briefing-injected active memories with no demand in 30d
}

RetrievalReport is the retrieval-REACH rollup for a window, computed live from the injection event stream. It measures how far the knowledge base actually reaches agents -- how many distinct memories get surfaced, across how many sessions -- rather than a read-back rate. (Read-after-inject is not tracked here: agents almost never memory_read what a briefing already surfaced, so the only honest consumption signal for this system is reach, not re-reads.)

func BuildRetrievalReport

func BuildRetrievalReport(ctx context.Context, db *sql.DB, w RetrievalWindow, topN int) (RetrievalReport, error)

BuildRetrievalReport computes the windowed retrieval-reach rollup in a single pass over the injection event stream: total injection volume, how many distinct active memories were surfaced (reach), across how many sessions, plus the per-kind and most-injected breakdowns and the injection trend. topN caps the most-injected list (<=0 => 12). Archived/unknown injected ids still count toward the volume total and the trend, but drop out of the by-kind and most-injected breakdowns (scoped to active memories).

type RetrievalStat

type RetrievalStat struct {
	ItemID         string
	InjectCount    int
	ReadCount      int
	LastInjectedAt *time.Time
	LastReadAt     *time.Time
	Utility        float64
	Components     UtilityComponents
}

RetrievalStat mirrors one retrieval_stats row: how often an item has been surfaced (injected) to an agent and read back, when last, and its time-decayed utility score. It is a materialized projection of the append-only event log, rebuilt by RebuildRetrievalStats -- the events table is the source of truth.

func GetRetrievalStat

func GetRetrievalStat(ctx context.Context, db *sql.DB, itemID string) (RetrievalStat, bool, error)

GetRetrievalStat returns the stats row for an item. found is false when the item has never been injected or read.

type RetrievalWindow

type RetrievalWindow struct {
	Key   string    `json:"key"`
	Label string    `json:"label"`
	Since time.Time `json:"since"`
}

RetrievalWindow is a resolved trailing time window for the retrieval-health views: a stable key (URL + selector), a human label, and the inclusive lower bound. A zero Since means "all time" (unbounded).

func ResolveRetrievalWindow

func ResolveRetrievalWindow(key string, now time.Time) RetrievalWindow

ResolveRetrievalWindow maps a selector key to a window anchored at now. Unknown or empty keys fall back to "24h", the default: an unscoped console page answers about today rather than about all history.

type SearchHit

type SearchHit struct {
	ItemID string
	Kind   string
	Score  float64
}

SearchHit is one result of a cosine similarity search.

func CosineSearch

func CosineSearch(ctx context.Context, db *sql.DB, query []float32, model string, kinds, projects []string, limit int) ([]SearchHit, error)

CosineSearch brute-force-scans stored vectors for the given model and returns the top-limit most similar items, highest score first. An empty kinds filter searches all kinds. At the corpus scale this system targets (thousands of items) a full scan is milliseconds, which is why there is no vector index.

projects restricts hits to items whose project is in the list (recall passes the bound project plus "" for global); an empty filter searches all projects. Filtering inside the candidate query keeps the whole candidate depth in scope, so a corpus dominated by out-of-scope vectors cannot starve in-scope results out of the top-limit window. Embedding rows carry no project, so the scope is resolved by joining the index tables; an embedding orphaned from both indexes matches no scope.

Superseded and archived memories are excluded on the same grounds: a memory keeps its vector after it is invalidated, and a retired revision sits close to the replacement that superseded it, so filtering validity after the LIMIT let dead revisions eat the candidate depth. An embedding with no memories_index row (a note, or an orphan) has nothing to invalidate it and survives this predicate -- the scope filter is what drops orphans.

func CosineSearchSince added in v0.3.9

func CosineSearchSince(ctx context.Context, db *sql.DB, query []float32, model string, kinds, projects []string, memKind string, since time.Time, limit int) ([]SearchHit, error)

CosineSearchSince is CosineSearch restricted to knowledge updated at or after since. A zero since is unbounded. Filtering before the top-K scan keeps old neighbors from occupying the entire candidate window of a recent search. memKind, when non-empty, restricts hits to memories of that frontmatter kind (memories_index.kind); notes have no memories_index row, so a memKind filter excludes them regardless of the kinds (item-type) filter.

func FTSSearch

func FTSSearch(ctx context.Context, db *sql.DB, query string, kinds, projects []string, limit int) ([]SearchHit, error)

FTSSearch runs a full-text query over the unified fts table and returns hits ordered best-first. Scores are the negated bm25 rank (higher = better) so they share the "bigger is better" convention with CosineSearch. An empty kinds filter searches all kinds. A query with no usable terms yields no hits (not an error), so recall degrades quietly on punctuation-only input.

projects restricts hits to items whose project is in the list (recall passes the bound project plus "" for global); an empty filter searches all projects. Filtering inside the candidate query keeps the whole candidate depth in scope, so a corpus dominated by out-of-scope matches cannot starve in-scope results out of the top-limit window.

Superseded and archived memories are excluded for the same reason: the fts table is self-contained and keeps a full row for a memory that is no longer valid, so validity is resolved by joining memories_index. Filtering it after the LIMIT (as callers used to) let retired revisions of a name -- which match their replacement's queries almost as well, by construction -- eat the candidate depth and starve the live memory that replaced them. An fts row with no index row (notes, or an orphan) has nothing to invalidate it and is kept.

func FTSSearchAllTerms added in v0.4.1

func FTSSearchAllTerms(ctx context.Context, db *sql.DB, terms []string, kinds, projects []string, limit int) ([]SearchHit, error)

FTSSearchAllTerms is FTSSearch with AND semantics: a hit must contain every term. The OR of ftsQuery maximizes recall for ranking pipelines, but a presence probe -- "does knowledge covering all of this exist?" -- needs precision instead: with OR, any item sharing one common word would count. The gardener's memory-wanted liveness guard is the canonical caller. Terms are sanitized exactly like ftsQuery's tokens (split, short tokens dropped, quoted); unusable input yields no hits, not an error.

func FTSSearchSince added in v0.3.9

func FTSSearchSince(ctx context.Context, db *sql.DB, query string, kinds, projects []string, since time.Time, limit int) ([]SearchHit, error)

FTSSearchSince is FTSSearch restricted to index rows updated at or after since. A zero since keeps the unbounded behavior. The predicate lives before LIMIT so old, highly-ranked rows cannot crowd in-window matches out of the candidate set.

type SessionCoverage

type SessionCoverage struct {
	Total    int `json:"total"`    // all sessions (the denominator)
	Covered  int `json:"covered"`  // sessions with >=1 durable artifact
	Findings int `json:"findings"` // sessions whose findings are non-empty
	Memories int `json:"memories"` // sessions that wrote >=1 memory
	Notes    int `json:"notes"`    // sessions that created >=1 note
	Trials   int `json:"trials"`   // sessions that recorded >=1 trial
}

SessionCoverage measures how much Claude Code session knowledge Seamless is retaining: a session is "covered" when it left a durable artifact behind -- non-empty findings, or at least one written memory, note, or recorded trial. It is a rough proxy for "how much of what happened in a session did we keep". The per-channel counts (Findings/Memories/Notes/Trials) overlap -- a session can be covered several ways -- so only Total and Covered partition the set.

func GetSessionCoverage

func GetSessionCoverage(ctx context.Context, db *sql.DB, since time.Time) (SessionCoverage, error)

GetSessionCoverage computes the coverage roll-up in a single pass over the sessions created within the window (a zero `since` means all time), testing each against the event log for durable artifacts. It reads the event log directly rather than retrieval_stats, so it needs no rebuild.

func GetSessionCoverageForProject

func GetSessionCoverageForProject(ctx context.Context, db *sql.DB, project string, since time.Time) (SessionCoverage, error)

GetSessionCoverageForProject computes the session-coverage roll-up (see GetSessionCoverage) restricted to one project's sessions (project_slug = ?).

It rejects an empty project: an empty project_slug marks real global sessions, so "" is ambiguous between "all sessions" and "global-only" -- the caller who wants the all-sessions number must use GetSessionCoverage instead.

type SnippetHit

type SnippetHit struct {
	SearchHit
	Snippet string
}

SnippetHit is an FTS hit carrying the matched text in context. Snippet is the raw item text with the matched terms wrapped in SnippetStartMark/SnippetEndMark.

func FTSSearchSnippets

func FTSSearchSnippets(ctx context.Context, db *sql.DB, query string, kinds, projects []string, limit int) ([]SnippetHit, error)

FTSSearchSnippets is FTSSearch plus a generated snippet per hit: the same query, filters, and ordering, with the matched terms marked in context. The two share ftsSearch so the validity predicate, the scope filter, and the ordering cannot drift between the snippet and no-snippet paths -- callers rely on both returning identical hits for identical inputs.

func FTSSearchSnippetsSince added in v0.3.9

func FTSSearchSnippetsSince(ctx context.Context, db *sql.DB, query string, kinds, projects []string, memKind string, since time.Time, limit int) ([]SnippetHit, error)

FTSSearchSnippetsSince is the windowed form of FTSSearchSnippets. See FTSSearchSince for why the time predicate is part of the candidate query. memKind, when non-empty, restricts hits to memories of that frontmatter kind (memories_index.kind); notes have no memories_index row, so a memKind filter excludes them regardless of the kinds (item-type) filter.

type SpotlightMemory added in v0.4.10

type SpotlightMemory struct {
	ItemID  string  `json:"itemId"`
	Name    string  `json:"name"`
	Kind    string  `json:"kind"`
	Project string  `json:"project"`
	Utility float64 `json:"utility"` // windowed utility gain (house weights, per-session dedup, decay)
	Reads   int     `json:"reads"`   // memory.read events in the window
	Readers int     `json:"readers"` // distinct sessions that read or pulled it
	Injects int     `json:"injects"` // query-gated injections in the window
}

SpotlightMemory is the momentum "memory of the month": the active memory with the highest utility gain inside the spotlight window, with the counts that back the claim up.

func MemorySpotlight added in v0.4.10

func MemorySpotlight(ctx context.Context, db *sql.DB, since, now time.Time) (SpotlightMemory, bool, error)

MemorySpotlight returns the active memory that gained the most utility since the given instant -- the same signal classes, weights, per-session dedup, and decay as RebuildRetrievalStats, restricted to the window -- with its windowed read and reader counts. found is false when no active memory earned any query-gated utility in the window: the honest empty state. A momentum-only surface: callers gate on the feature before asking.

type StageCrossing added in v0.4.10

type StageCrossing struct {
	Project string
	From    ProjectStage
	To      ProjectStage
}

StageCrossing is a latch advance minted by one evaluation.

func EvaluateProjectStages added in v0.4.10

func EvaluateProjectStages(ctx context.Context, db *sql.DB, rows []ProjectBoardRow, now time.Time) (map[string]ProjectStageInfo, []StageCrossing, error)

EvaluateProjectStages computes the stage for every named project on the board, latches forward (never back), persists any advances, and returns the effective stages plus the crossings this evaluation minted (the caller records those as events). The global "" scope is not a project and earns no stage. A momentum-only surface: callers gate on the feature, so a disabled install neither computes nor latches.

type StageInputs added in v0.4.10

type StageInputs struct {
	CreatedAt time.Time `json:"createdAt"` // registration; zero = unregistered
	Memories  int       `json:"memories"`  // active memories
	Events    int       `json:"events"`    // total event volume, all kinds
	ReachRate int       `json:"reachRate"` // windowed surfaced/active %, when HasReach
	HasReach  bool      `json:"hasReach"`
}

StageInputs are one project's maturity facts at evaluation time.

func (StageInputs) AgeDays added in v0.4.10

func (in StageInputs) AgeDays(now time.Time) int

AgeDays is the whole days since registration (0 for an unregistered slug).

type SurfaceFunnel added in v0.4.4

type SurfaceFunnel struct {
	Surface    string `json:"surface"`
	Injections int    `json:"injections"` // item-level injections via this surface in the window (volume)
	Items      int    `json:"items"`      // distinct items this surface injected in the window
	ItemsRead  int    `json:"itemsRead"`  // of Items, how many were pulled within the follow window of an injection
	ReadRate   int    `json:"readRate"`   // ItemsRead / Items, rounded %
}

SurfaceFunnel is the read-after-inject funnel for one injection surface: how much the surface pushed, and how much of it agents pulled back.

func ReadAfterInjectFunnel added in v0.4.4

func ReadAfterInjectFunnel(ctx context.Context, db *sql.DB, since time.Time, follow time.Duration) ([]SurfaceFunnel, error)

ReadAfterInjectFunnel segments the read-after-inject funnel by injection surface over the events at or after since (zero = all time). An item converts for a surface when a deliberate pull -- an explicit memory.read or a recall-tool hit -- lands after one of that surface's injections of the item and within follow of it (<=0 = DefaultFunnelFollow); a pull with no preceding in-window injection converts nothing. Prompt-recall matches do not count as conversions: they are themselves automated injections, and Claude Code children have no UserPromptSubmit hook, so counting them would skew the session-start vs subagent-start comparison this funnel exists for.

The aggregation is read-only over the existing event log: it records nothing, adds no utility inputs, and leaves RebuildRetrievalStats untouched (closed-loop-utility-signal-contract).

type TaskPatch

type TaskPatch struct {
	Status       *core.TaskStatus
	Title        *string
	Body         *string
	ProjectSlug  *string
	AddDependsOn []string
}

TaskPatch is the set of mutable fields UpdateTask may change; nil fields are left untouched. AddDependsOn edges are added (existing edges are kept). ProjectSlug reassigns the task to another project (used when a split moves a project's open work to a child); the holder-lock still applies.

type TrendBucket

type TrendBucket struct {
	Label string `json:"label"`
	Count int    `json:"count"`
}

TrendBucket is one time bucket of the injection trend: a pre-formatted tick label and the item-level injection count that fell in it. The report's total Injected equals the sum of bucket counts, so the hero number and the chart always describe the same quantity over the same window.

func ProjectRetrievalTrend added in v0.3.6

func ProjectRetrievalTrend(ctx context.Context, db *sql.DB, w RetrievalWindow, project string) ([]TrendBucket, error)

ProjectRetrievalTrend builds the injection trend for a single project's active memories over the window: the same time-bucketed series as RetrievalReport.Trend, but counting only injections of memories whose project is `project`. Attribution matches RetrievalReport.ByProject (by the injected memory's own project, not the injecting session's), so a project's trend agrees with the reach numbers shown beside it. Injections of archived/unknown ids -- which have no active project -- are excluded (unlike the global trend, they cannot be attributed to a slice).

type TrialFilter

type TrialFilter struct {
	Lab           string
	Outcome       string
	Project       string
	SessionID     string
	MetricsEquals map[string]any
	Limit         int
}

TrialFilter parameterizes QueryTrials. Empty string fields are not filtered; MetricsEquals matches trials whose metrics contain each given key with an equal value (compared after JSON normalization). A zero Limit uses DefaultTrialQueryLimit; negative or oversized values are rejected.

type UsageSummary

type UsageSummary struct {
	Memories struct {
		Active int            `json:"active"`
		ByKind map[string]int `json:"byKind"`
	} `json:"memories"`
	Notes     int            `json:"notes"`
	Sessions  map[string]int `json:"sessions"` // status -> count
	Tasks     map[string]int `json:"tasks"`    // status -> count
	Retrieval struct {
		Injections  int          `json:"injections"`
		Reads       int          `json:"reads"`
		TopInjected []NamedCount `json:"topInjected"`
		TopUtility  []NamedScore `json:"topUtility"` // highest decayed-demand scores
		// FunnelBySurface is the all-time read-after-inject funnel segmented by
		// injection surface (conversions within DefaultFunnelFollow).
		FunnelBySurface []SurfaceFunnel `json:"funnelBySurface"`
	} `json:"retrieval"`
	GardenerPending map[string]int `json:"gardenerPending"` // kind -> count
	EventsByKind    map[string]int `json:"eventsByKind"`
}

UsageSummary is a point-in-time roll-up of activity across the store, backing the usage_summary MCP tool and (later) the console's overview. It is derived entirely from the DB-of-record tables and the event log.

func GetUsageSummary

func GetUsageSummary(ctx context.Context, db *sql.DB) (UsageSummary, error)

GetUsageSummary computes the usage roll-up. It reads current retrieval_stats; callers wanting fresh numbers should RebuildRetrievalStats first.

type UtilityActivation added in v0.4.1

type UtilityActivation struct {
	Projects map[string]UtilityProjectState `json:"projects"`
}

UtilityActivation is the stored activation map, keyed by project slug (project "" -- the global scope -- is a valid key).

func GetUtilityActivation added in v0.4.1

func GetUtilityActivation(ctx context.Context, db *sql.DB) (UtilityActivation, error)

GetUtilityActivation loads the activation state; an absent row is an empty (nothing active) state, not an error.

func (UtilityActivation) Active added in v0.4.1

func (a UtilityActivation) Active(project, mode string) bool

Active reports whether utility ranking applies to a project's briefing under the given mode ("auto", "on", or "off" -- config.Briefing.UtilityMode).

type UtilityComponents added in v0.4.1

type UtilityComponents struct {
	Read   float64 `json:"read,omitempty"`
	Recall float64 `json:"recall,omitempty"`
	Prompt float64 `json:"prompt,omitempty"`
}

UtilityComponents is the decayed raw demand per signal class, kept alongside the normalized score so the console can show WHY a memory ranks where it does.

func (UtilityComponents) IsZero added in v0.4.1

func (c UtilityComponents) IsZero() bool

IsZero reports no demand in any class (exported for display-layer callers).

type UtilityProjectDemand added in v0.4.1

type UtilityProjectDemand struct {
	Earliest       time.Time // first query-gated demand event ever (zero = none)
	RecentEvents   int       // session-deduped demand events within the window
	RecentMemories int       // distinct memories those recent events surfaced
}

UtilityProjectDemand summarizes a project's query-gated demand history for the readiness decision: when demand first appeared, and how much of it -- in session-deduped events and distinct memories -- the trailing window holds.

func (UtilityProjectDemand) Ready added in v0.4.1

func (d UtilityProjectDemand) Ready(now time.Time) bool

Ready reports whether this demand history satisfies every readiness threshold as of now.

type UtilityProjectState added in v0.4.1

type UtilityProjectState struct {
	ReadyAt *time.Time `json:"ready_at,omitempty"`
	Forced  string     `json:"forced,omitempty"`
}

UtilityProjectState is one project's activation record. ReadyAt is the latch: once the gardener sets it, the project stays active (no flapping). Forced is the owner override: "on" and "off" both win over the latch; "" defers to it.

type WindowVitals added in v0.4.10

type WindowVitals struct {
	Injections       int `json:"injections"`       // item-level injections in the window
	MemoriesSurfaced int `json:"memoriesSurfaced"` // distinct active memories surfaced >=1x
	SessionsReached  int `json:"sessionsReached"`  // distinct sessions that received >=1 injection
	ActiveMemories   int `json:"activeMemories"`   // active memories as of now (reach denominator)
	ReachRate        int `json:"reachRate"`        // MemoriesSurfaced / ActiveMemories, rounded %
	CovTotal         int `json:"covTotal"`         // sessions started in the window (continuity denominator)
	Covered          int `json:"covered"`          // of those, how many retained knowledge
	Coverage         int `json:"coverage"`         // Covered / CovTotal, rounded %
}

WindowVitals is a bounded window's comparable rollup: reach, injection volume, the sessions that received context, and knowledge continuity.

ActiveMemories is the knowledge base as it stands NOW, not as it stood inside the window -- the same convention RetrievalReport documents. That is what makes a reach delta readable: both sides divide by one denominator, so the movement is the numerator's (how much of today's knowledge base was reaching agents then vs now) rather than a mix of reach and churn.

func GetWindowVitals added in v0.4.10

func GetWindowVitals(ctx context.Context, db *sql.DB, since, until time.Time) (WindowVitals, error)

GetWindowVitals computes the rollup across every scope over [since, until). A zero bound is open on that side.

func GetWindowVitalsForProject added in v0.4.10

func GetWindowVitalsForProject(ctx context.Context, db *sql.DB, project string, since, until time.Time) (WindowVitals, error)

GetWindowVitalsForProject computes the rollup restricted to one project: its own active memories are the reach denominator and only its sessions count toward continuity. It rejects an empty project for the same reason GetSessionCoverageForProject does -- an empty project_slug marks real global sessions, so "" cannot mean "every scope" here.

Jump to

Keyboard shortcuts

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