Documentation
¶
Overview ¶
Free-text search over the structured entities -- tasks, sessions, projects, plans. Memories and notes are not here: they are indexed in the fts table and searched through FTSSearch (fused with semantic hits by internal/retrieve). These four have no FTS mirror, so they match with LIKE over the one or two columns a human would search by (a task's title, a session's name, a project's slug/name). That is a deliberate floor, not a stopgap: they are short, low-cardinality labels where substring matching is what the observer expects, and mirroring them into fts would mean maintaining index rows for high-churn state the files layer does not own.
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
- Variables
- func ActiveAmbientByCWD(ctx context.Context, db *sql.DB, cwd string) ([]core.Session, error)
- func ActiveAmbientProjects(ctx context.Context, db *sql.DB, within time.Duration) ([]string, error)
- func ActiveAmbientSessionsForProject(ctx context.Context, db *sql.DB, project string, within time.Duration) ([]core.Session, error)
- func ActiveMemories(ctx context.Context, db *sql.DB, project string) ([]core.Memory, error)
- func ActiveMemoriesForProjects(ctx context.Context, db *sql.DB, slugs []string) ([]core.Memory, error)
- func ActiveMemoriesForScope(ctx context.Context, db *sql.DB, project string, extra []string) ([]core.Memory, error)
- func ActiveSessionIDs(ctx context.Context, db *sql.DB, ids []string) (map[string]bool, error)
- func ActiveSessionsByClaudeID(ctx context.Context, db *sql.DB, claudeSessionID string) ([]core.Session, error)
- func AddFamilyMembers(ctx context.Context, db *sql.DB, family string, slugs []string) ([]string, error)
- func AddRepoMapping(ctx context.Context, db *sql.DB, repoPath, slug string) error
- func AllActiveMemories(ctx context.Context, db *sql.DB) ([]core.Memory, error)
- func AllMemoriesIncludingInvalid(ctx context.Context, db *sql.DB) ([]core.Memory, error)
- func AllProposalKeys(ctx context.Context, db *sql.DB) (map[string]struct{}, error)
- func AllReadyTasks(ctx context.Context, db *sql.DB) ([]core.Task, error)
- func AllRetrievalStats(ctx context.Context, db *sql.DB) (map[string]RetrievalStat, error)
- func AllTasksByStatus(ctx context.Context, db *sql.DB, status core.TaskStatus) ([]core.Task, error)
- func BriefingConfig(ctx context.Context, db *sql.DB, base config.Briefing) (cfg config.Briefing, overridden bool, err error)
- func ClearBriefingConfig(ctx context.Context, db *sql.DB) error
- func CompletedSessionsSince(ctx context.Context, db *sql.DB, since time.Time) ([]core.Session, error)
- func Cosine(a, b []float32) float64
- func CreateProject(ctx context.Context, db *sql.DB, p core.Project) error
- func CreateSession(ctx context.Context, db *sql.DB, s core.Session) error
- func CreateTask(ctx context.Context, db *sql.DB, t core.Task) error
- func CreateTrial(ctx context.Context, db *sql.DB, tr core.Trial) error
- func DecodeVector(b []byte) []float32
- func DeleteSetting(ctx context.Context, db *sql.DB, key string) error
- func DistinctPlanSlugsForProject(ctx context.Context, db *sql.DB, project string) ([]string, error)
- func EncodeVector(vec []float32) []byte
- func EnsureProject(ctx context.Context, db *sql.DB, slug, name string) (core.Project, error)
- func ExpireStaleSessions(ctx context.Context, db *sql.DB, cutoff time.Time) ([]core.Session, error)
- func ForceReleaseTask(ctx context.Context, db *sql.DB, id string, now time.Time) (core.Task, error)
- func GetSetting(ctx context.Context, db *sql.DB, key string) (string, bool, error)
- func LatestActiveAmbientSessionForProject(ctx context.Context, db *sql.DB, project string, within time.Duration) (core.Session, bool, error)
- func ListNotes(ctx context.Context, db *sql.DB) ([]core.Note, error)
- func ListProjects(ctx context.Context, db *sql.DB) ([]core.Project, error)
- func ListSessions(ctx context.Context, db *sql.DB, status core.SessionStatus, since time.Time, ...) ([]core.Session, error)
- func ListSessionsForProject(ctx context.Context, db *sql.DB, project string, status core.SessionStatus, ...) ([]core.Session, error)
- func ListTasks(ctx context.Context, db *sql.DB, project string, status core.TaskStatus) ([]core.Task, error)
- func ListTasksForPlan(ctx context.Context, db *sql.DB, project string, status core.TaskStatus, ...) ([]core.Task, error)
- func MemoriesByIDs(ctx context.Context, db *sql.DB, ids []string) (map[string]core.Memory, error)
- func MemoriesForSession(ctx context.Context, db *sql.DB, sessionName string) ([]core.Memory, error)
- func MemoriesSuperseding(ctx context.Context, db *sql.DB, id string) ([]core.Memory, error)
- func MemoryByID(ctx context.Context, db *sql.DB, id string) (core.Memory, bool, error)
- func MemoryByName(ctx context.Context, db *sql.DB, project, name string) (core.Memory, bool, error)
- func MemoryByNameIncludingInvalid(ctx context.Context, db *sql.DB, project, name string) (core.Memory, bool, error)
- func NoteByID(ctx context.Context, db *sql.DB, id string) (core.Note, bool, error)
- func NoteBySlug(ctx context.Context, db *sql.DB, project, slug string) (core.Note, bool, error)
- func NotesByIDs(ctx context.Context, db *sql.DB, ids []string) (map[string]core.Note, error)
- func NotesByTag(ctx context.Context, db *sql.DB, project, tag string) ([]core.Note, error)
- func NotesByTagPrefix(ctx context.Context, db *sql.DB, project, prefix string) ([]core.Note, error)
- func Open(dbPath string) (*sql.DB, error)
- func ProjectBySlug(ctx context.Context, db *sql.DB, slug string) (core.Project, bool, error)
- func ProjectFamilies(ctx context.Context, db *sql.DB) (map[string][]string, error)
- func ProjectMemoriesIncludingInvalid(ctx context.Context, db *sql.DB, project string) ([]core.Memory, error)
- func ProjectsByParent(ctx context.Context, db *sql.DB, parent string) ([]core.Project, error)
- func QueryTrials(ctx context.Context, db *sql.DB, f TrialFilter) ([]core.Trial, error)
- func ReactivateSessionByName(ctx context.Context, db *sql.DB, name, project string, now time.Time) (bool, error)
- func ReadyTasks(ctx context.Context, db *sql.DB, project string) ([]core.Task, error)
- func ReadyTasksForPlan(ctx context.Context, db *sql.DB, project, plan string) ([]core.Task, error)
- func RebuildRetrievalStats(ctx context.Context, db *sql.DB) error
- func RecentFindings(ctx context.Context, db *sql.DB, project string, limit int) ([]core.Session, error)
- func RegisterProjectForCWD(ctx context.Context, db *sql.DB, cwd string) (string, error)
- func ReleaseClaimsForSession(ctx context.Context, db *sql.DB, sessionID string, now time.Time) (int, error)
- func ReleaseTask(ctx context.Context, db *sql.DB, id, sessionID string, now time.Time) (core.Task, error)
- func RemoveFamilyMembers(ctx context.Context, db *sql.DB, family string, slugs []string) ([]string, error)
- func RepoProjectMap(ctx context.Context, db *sql.DB) (map[string]string, error)
- func ResolveProjectForCWD(ctx context.Context, db *sql.DB, cwd string) (string, error)
- func ResolveProposal(ctx context.Context, db *sql.DB, id, status string, at time.Time) error
- func RetireProject(ctx context.Context, db *sql.DB, slug string, at time.Time) error
- func SchemaVersion(db *sql.DB) (int, error)
- func SearchProjects(ctx context.Context, db *sql.DB, q string, limit int) ([]core.Project, error)
- func SearchSessions(ctx context.Context, db *sql.DB, q string, limit int) ([]core.Session, error)
- func SearchTasks(ctx context.Context, db *sql.DB, q string, limit int) ([]core.Task, error)
- func SessionByID(ctx context.Context, db *sql.DB, id string) (core.Session, bool, error)
- func SessionByName(ctx context.Context, db *sql.DB, name string) (core.Session, bool, error)
- func SetBriefingConfig(ctx context.Context, db *sql.DB, b config.Briefing) error
- func SetProjectFamilies(ctx context.Context, db *sql.DB, families map[string][]string) error
- func SetProjectParent(ctx context.Context, db *sql.DB, slug, parent string, now time.Time) error
- func SetSetting(ctx context.Context, db *sql.DB, key, value string) error
- func SiblingFindings(ctx context.Context, db *sql.DB, slugs []string, limit int) ([]core.Session, error)
- func SiblingProjects(ctx context.Context, db *sql.DB, project string) ([]string, error)
- func StaleMemories(ctx context.Context, db *sql.DB, cutoff time.Time) ([]core.Memory, error)
- func TableCount(db *sql.DB) (int, error)
- func TaskByID(ctx context.Context, db *sql.DB, id string) (core.Task, error)
- func TasksBlockedBy(ctx context.Context, db *sql.DB, id string) ([]core.Task, error)
- func TasksClaimedBy(ctx context.Context, db *sql.DB, sessionID string) ([]core.Task, error)
- func TouchSession(ctx context.Context, db *sql.DB, id string, now time.Time) error
- func TouchSessionByName(ctx context.Context, db *sql.DB, name string, now time.Time) error
- func UpdateProposalPayload(ctx context.Context, db *sql.DB, id string, payload map[string]any) error
- func UpdateSession(ctx context.Context, db *sql.DB, s core.Session) error
- func UpdateTask(ctx context.Context, db *sql.DB, id string, patch TaskPatch, actor string, ...) (core.Task, error)
- func UpsertEmbedding(ctx context.Context, db *sql.DB, itemID, kind, model string, vec []float32) error
- type BlockedTask
- type ClaimResult
- type CoverageBucket
- type KindReach
- type MemoryStat
- type MemoryVector
- type Migration
- type NamedCount
- type NavCounts
- type PlanRollup
- type PlanSearchRow
- type ProjectBoardRow
- type ProjectCounts
- type ProjectReach
- type Proposal
- type RetrievalReport
- type RetrievalStat
- type RetrievalWindow
- type SearchHit
- type SessionCoverage
- type SnippetHit
- type TaskPatch
- type TrendBucket
- type TrialFilter
- type UsageSummary
Constants ¶
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.
const ( ProposalPending = "pending" ProposalApplied = "applied" ProposalDismissed = "dismissed" )
Proposal statuses.
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 )
Proposal kinds (mirrors the gardener_proposals.kind CHECK constraint).
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.
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.
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.
Variables ¶
var ErrFamilyNotFound = errors.New("store: project family not found")
ErrFamilyNotFound is returned by RemoveFamilyMembers when the named family does not exist.
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.
var ErrSlugExists = errors.New("store: project slug already exists")
ErrSlugExists is returned by CreateProject when the slug is already taken.
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.
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 \"\"").
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.
var ErrTaskCycle = errors.New("dependency would create a cycle")
ErrTaskCycle is returned when adding a dependency edge would create a cycle.
var ErrTaskNotFound = errors.New("task not found")
ErrTaskNotFound is returned when a task id does not exist (e.g. a dangling depends_on reference).
var ProposalKinds = []string{ ProposalMerge, ProposalArchive, ProposalDigest, ProposalConsolidate, ProposalReproject, ProposalSplit, ProposalAbandonPlan, }
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.
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 ¶
ActiveAmbientByCWD returns the active ambient (cc/*) 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 ¶
ActiveAmbientProjects returns the distinct project slugs that have at least one active ambient (cc/*) 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/*) 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 ¶
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 ¶
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 ActiveSessionsByClaudeID ¶
func ActiveSessionsByClaudeID(ctx context.Context, db *sql.DB, claudeSessionID string) ([]core.Session, error)
ActiveSessionsByClaudeID returns every active session -- the ambient cc/* plus any explicit session_start that linked to it -- stamped with claudeSessionID, ambient first. The SessionEnd hook uses it to complete a whole Claude session's sessions the moment we know it ended, rather than waiting out the idle TTL. An empty id matches nothing.
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 ¶
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. The read-decode-write runs inside one transaction: the pool is capped at a single connection (see Open), so the whole mutation is serialized against concurrent mutators and two agents registering different repos at once can no longer clobber each other's entry.
func AllActiveMemories ¶
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 ¶
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 AllProposalKeys ¶
AllProposalKeys returns the set of payload "key" values across proposals of EVERY status. The gardener consults it before proposing, so a suggestion the owner already applied or dismissed is never raised again.
func AllReadyTasks ¶
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 ¶
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 ¶
AllTasksByStatus returns every task with the given status across all projects, newest-updated first.
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 ClearBriefingConfig ¶
ClearBriefingConfig removes the console briefing override, reverting the effective briefing config to the file/env base.
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 ¶
Cosine returns the cosine similarity of two equal-length vectors, in [-1, 1]. Mismatched lengths or a zero-magnitude vector yield 0.
func CreateProject ¶
CreateProject inserts a project. It returns ErrSlugExists if the slug is taken.
func CreateSession ¶
CreateSession inserts a session. The caller mints the ULID id; a duplicate name returns ErrSessionNameExists.
func CreateTask ¶
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 ¶
CreateTrial inserts a trial row. The caller mints the ULID id and timestamp.
func DecodeVector ¶
DecodeVector reverses EncodeVector. A byte slice whose length is not a multiple of four yields nil (corrupt row; skip it).
func DeleteSetting ¶
DeleteSetting removes a settings key. Deleting an absent key is a no-op.
func DistinctPlanSlugsForProject ¶
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 EncodeVector ¶
EncodeVector serializes a float32 vector to a little-endian byte slice, the on-disk form of the embeddings.vec BLOB column.
func EnsureProject ¶
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 ExpireStaleSessions ¶
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 ForceReleaseTask ¶
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 ¶
GetSetting returns the value for a settings key. found is false when unset.
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/*) 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 ¶
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 ¶
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 MemoriesByIDs ¶
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 ¶
MemoriesForSession returns the memories a session produced -- the index rows whose source_session matches the session NAME -- newest-updated first. Memory.SourceSession stores the session name (e.g. "cc/ab12cd34"), never its ULID, so callers must pass the name.
GUARD: if the argument looks like a bare session ULID (no '/', 26 chars, and parses as a ULID) it is almost certainly a mis-keyed id that would silently match nothing; the call is rejected with a message pointing at SessionByID to resolve the name.
func MemoriesSuperseding ¶
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 ¶
MemoryByID returns the memory with the given id. found is false when absent.
func MemoryByName ¶
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 NoteBySlug ¶
NoteBySlug returns the note with an exact (project, slug). found is false when none matches.
func NotesByIDs ¶
NotesByIDs returns the notes for the given ids keyed by ID; missing ids are simply absent.
func NotesByTag ¶
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 ¶
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 ¶
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 ProjectBySlug ¶
ProjectBySlug returns the project with the given slug. found is false when absent.
func ProjectFamilies ¶
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 ¶
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 QueryTrials ¶
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 ReactivateSessionByName ¶
func ReactivateSessionByName(ctx context.Context, db *sql.DB, name, project string, now time.Time) (bool, error)
ReactivateSessionByName resumes the named session with a single targeted UPDATE: status flips back to active, project_slug is re-scoped (only when project is non-empty), and updated_at is bumped -- findings and metadata are never touched. The SessionStart hook resumes ambient sessions through it instead of a full-row read-modify-write, so a concurrent transcript harvest (which writes findings via UpdateSession) cannot be clobbered by a racing resume that read the row before the harvest landed. found is false when no session has that name.
func ReadyTasks ¶
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 ¶
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 ¶
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 events contribute read_count + last_read_at. Events are read oldest-first so the last-seen timestamp wins.
func RecentFindings ¶
func RecentFindings(ctx context.Context, db *sql.DB, project string, limit int) ([]core.Session, error)
RecentFindings returns completed 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.
func RegisterProjectForCWD ¶
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.
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 RepoProjectMap ¶
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 ¶
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 ¶
ResolveProposal marks a proposal applied or dismissed and stamps resolved_at. It errors if the proposal is missing or already resolved (not pending).
func RetireProject ¶
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 SchemaVersion ¶
SchemaVersion returns the highest applied migration version.
func SearchProjects ¶
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 SearchSessions ¶
SearchSessions returns sessions whose name contains q, newest-updated first. An exact id also matches.
func SearchTasks ¶
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 SessionByID ¶
SessionByID returns the session with the given id. found is false when absent.
func SessionByName ¶
SessionByName returns the session with the given (unique) name. found is false when absent.
func SetBriefingConfig ¶
SetBriefingConfig persists b as the console briefing override. Callers validate first (config.Briefing.Validate); this only encodes and stores.
func SetProjectFamilies ¶
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 SetProjectParent ¶
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).
func SetSetting ¶
SetSetting upserts a settings key/value.
func SiblingFindings ¶
func SiblingFindings(ctx context.Context, db *sql.DB, slugs []string, limit int) ([]core.Session, error)
SiblingFindings returns the most recent completed 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. An empty slugs slice yields no results.
func SiblingProjects ¶
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 ¶
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 ¶
TableCount returns the number of user tables (excluding SQLite internals and FTS shadow tables). Useful for the doctor's DB check.
func TaskByID ¶
TaskByID returns a task with its dependency ids populated. found is false when absent.
func TasksBlockedBy ¶
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 ¶
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 TouchSession ¶
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 TouchSessionByName ¶
TouchSessionByName is TouchSession keyed by the unique session name, for the ambient hooks which know the cc/{prefix} name (not the ULID). Same active-only guard and no-op-on-empty semantics.
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 ¶
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.
Types ¶
type BlockedTask ¶
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 ¶
AllBlockedTasks returns open-but-not-ready tasks across every project, each with its still-open blockers.
func BlockedTasks ¶
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 ¶
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 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 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 ¶
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 NamedCount ¶
NamedCount pairs an item's name with a count, for "top N" lists.
type NavCounts ¶
type NavCounts struct {
}
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.
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"`
}
PlanRollup is the per-plan aggregate the briefing surfaces: Total step tasks, how many are Done (closed), InFlight (in_progress), and Claimable (ready).
func ActivePlans ¶
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.
type PlanSearchRow ¶
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 ¶
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.
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
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 ¶
GetProjectCounts computes the per-project roll-up for one slug in a single round trip (scalar subqueries). It backs the console project peek.
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
}
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 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"`
}
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.
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 PendingProposals ¶
PendingProposals returns pending proposals, newest first. kind filters by proposal kind when non-empty.
type RetrievalReport ¶
type RetrievalReport struct {
Window RetrievalWindow `json:"window"`
Injected int `json:"injected"` // item-level injections in the window (volume) == sum(Trend)
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
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
}
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
}
RetrievalStat mirrors one retrieval_stats row: how often an item has been surfaced (injected) to an agent and read back, and when last. It is a materialized projection of the append-only event log, rebuilt by RebuildRetrievalStats -- the events table is the source of truth.
func GetRetrievalStat ¶
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 ¶
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 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.
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 ¶
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 ¶
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.
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 ¶
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.
type TrialFilter ¶
type TrialFilter struct {
Lab string
Outcome string
Project 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 non-positive Limit defaults to 20.
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"`
} `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 ¶
GetUsageSummary computes the usage roll-up. It reads current retrieval_stats; callers wanting fresh numbers should RebuildRetrievalStats first.