--- title: internal/store description: Go API reference for the store package. --- # store ```go import "github.com/jbcom/radioactive-ralph/internal/store" ``` Package store owns the single user\-level SQLite database that is the durable memory for every project the supervisor knows about: project identity \(accumulated fingerprints, §5b of the supervisor\-architecture design\), DB\-resident project config, the plan DAG, the append\-only event log, worker/session tracking with heartbeats, and spend accounting. There is exactly ONE database per user \(XDG data dir\), not one per repo. Repos stay clean — nothing is committed by default. The schema is embedded under schema/\*.sql and applied in lexical order by Migrate. This package is a fresh port of internal/plandag's store/task/plan machinery onto the new schema \(project\_id instead of repo\_path, workers instead of session\_variants, no persona/variant columns\) with the PR \#63 safety properties \(DSN \_txlock=immediate \+ synchronous\(NORMAL\), checked RowsAffected on claims, error\-checked terminal writes\) carried forward. ## Index - [Constants](<#constants>) - [Variables](<#variables>) - [func DSN\(dbPath string\) string](<#DSN>) - [func Migrate\(db \*sql.DB\) error](<#Migrate>) - [type A2AMessage](<#A2AMessage>) - [type AppendMessageOpts](<#AppendMessageOpts>) - [type CreatePlanOpts](<#CreatePlanOpts>) - [type CreateTaskOpts](<#CreateTaskOpts>) - [type EmitOpts](<#EmitOpts>) - [type Event](<#Event>) - [type EventPayload](<#EventPayload>) - [type Fingerprint](<#Fingerprint>) - [func Fingerprints\(ctx context.Context, dir string\) \(\[\]Fingerprint, error\)](<#Fingerprints>) - [type Options](<#Options>) - [type Plan](<#Plan>) - [type PlanStatus](<#PlanStatus>) - [type RecordSpendOpts](<#RecordSpendOpts>) - [type RunningWorker](<#RunningWorker>) - [type SessionOpts](<#SessionOpts>) - [type StatusCounts](<#StatusCounts>) - [type Store](<#Store>) - [func Open\(ctx context.Context, opts Options\) \(\*Store, error\)](<#Open>) - [func \(s \*Store\) AddDep\(ctx context.Context, planID, taskID, dependsOn string\) error](<#Store.AddDep>) - [func \(s \*Store\) AddProjectIdentifiers\(ctx context.Context, projectID string, fps \[\]Fingerprint\) error](<#Store.AddProjectIdentifiers>) - [func \(s \*Store\) AppendMessage\(ctx context.Context, o AppendMessageOpts\) error](<#Store.AppendMessage>) - [func \(s \*Store\) ApproveTask\(ctx context.Context, planID, taskID string\) \(found, changed bool, err error\)](<#Store.ApproveTask>) - [func \(s \*Store\) Backup\(ctx context.Context, destDir string\) \(string, error\)](<#Store.Backup>) - [func \(s \*Store\) ClaimNextReady\(ctx context.Context, planID, sessionID, workerID string\) \(\*Task, error\)](<#Store.ClaimNextReady>) - [func \(s \*Store\) ClearWorkerTask\(ctx context.Context, workerID, status string\) error](<#Store.ClearWorkerTask>) - [func \(s \*Store\) Close\(\) error](<#Store.Close>) - [func \(s \*Store\) CloseSession\(ctx context.Context, sessionID string\) error](<#Store.CloseSession>) - [func \(s \*Store\) CountRunningWorkers\(ctx context.Context\) \(int, error\)](<#Store.CountRunningWorkers>) - [func \(s \*Store\) CreatePlan\(ctx context.Context, o CreatePlanOpts\) \(string, error\)](<#Store.CreatePlan>) - [func \(s \*Store\) CreateProject\(ctx context.Context, displayName string, fps \[\]Fingerprint\) \(string, error\)](<#Store.CreateProject>) - [func \(s \*Store\) CreateSession\(ctx context.Context, o SessionOpts\) \(string, error\)](<#Store.CreateSession>) - [func \(s \*Store\) CreateTask\(ctx context.Context, o CreateTaskOpts\) error](<#Store.CreateTask>) - [func \(s \*Store\) CreateWorker\(ctx context.Context, o WorkerOpts\) \(string, error\)](<#Store.CreateWorker>) - [func \(s \*Store\) DB\(\) \*sql.DB](<#Store.DB>) - [func \(s \*Store\) Emit\(ctx context.Context, o EmitOpts\) error](<#Store.Emit>) - [func \(s \*Store\) EventsAfter\(ctx context.Context, projectID string, afterID int64, limit int\) \(\[\]Event, error\)](<#Store.EventsAfter>) - [func \(s \*Store\) GetPlan\(ctx context.Context, id string\) \(\*Plan, error\)](<#Store.GetPlan>) - [func \(s \*Store\) GetProjectConfig\(ctx context.Context, projectID string\) \(map\[string\]string, error\)](<#Store.GetProjectConfig>) - [func \(s \*Store\) GetTask\(ctx context.Context, planID, id string\) \(\*Task, error\)](<#Store.GetTask>) - [func \(s \*Store\) HeartbeatSession\(ctx context.Context, sessionID string\) error](<#Store.HeartbeatSession>) - [func \(s \*Store\) HeartbeatWorker\(ctx context.Context, workerID string\) error](<#Store.HeartbeatWorker>) - [func \(s \*Store\) HeartbeatWorkerAndSession\(ctx context.Context, workerID string\) error](<#Store.HeartbeatWorkerAndSession>) - [func \(s \*Store\) ListMessages\(ctx context.Context, planID, taskID string\) \(\[\]A2AMessage, error\)](<#Store.ListMessages>) - [func \(s \*Store\) ListPlans\(ctx context.Context, projectID string, statuses \[\]PlanStatus\) \(\[\]Plan, error\)](<#Store.ListPlans>) - [func \(s \*Store\) ListProjectEvents\(ctx context.Context, projectID string, limit int\) \(\[\]Event, error\)](<#Store.ListProjectEvents>) - [func \(s \*Store\) ListRunningWorkers\(ctx context.Context\) \(\[\]RunningWorker, error\)](<#Store.ListRunningWorkers>) - [func \(s \*Store\) ListTaskEvents\(ctx context.Context, planID, taskID string, limit int\) \(\[\]Event, error\)](<#Store.ListTaskEvents>) - [func \(s \*Store\) ListTasks\(ctx context.Context, planID string, statuses \[\]TaskStatus\) \(\[\]Task, error\)](<#Store.ListTasks>) - [func \(s \*Store\) MarkBlocked\(ctx context.Context, planID, taskID, sessionID string, payload EventPayload\) error](<#Store.MarkBlocked>) - [func \(s \*Store\) MarkDone\(ctx context.Context, planID, taskID, sessionID string, evidenceJSON string\) \(\[\]Task, error\)](<#Store.MarkDone>) - [func \(s \*Store\) MarkFailed\(ctx context.Context, planID, taskID, sessionID, reason string, maxRetries int\) \(retried bool, err error\)](<#Store.MarkFailed>) - [func \(s \*Store\) MarkFailedWithPayload\(ctx context.Context, planID, taskID, sessionID string, payload EventPayload, maxRetries int\) \(retried bool, err error\)](<#Store.MarkFailedWithPayload>) - [func \(s \*Store\) MaxEventID\(ctx context.Context, projectID string\) \(int64, error\)](<#Store.MaxEventID>) - [func \(s \*Store\) ProjectAbsPath\(ctx context.Context, projectID string\) \(path string, found bool, err error\)](<#Store.ProjectAbsPath>) - [func \(s \*Store\) ProjectSpendByProvider\(ctx context.Context, projectID string\) \(map\[string\]float64, error\)](<#Store.ProjectSpendByProvider>) - [func \(s \*Store\) Ready\(ctx context.Context, planID string\) \(\[\]Task, error\)](<#Store.Ready>) - [func \(s \*Store\) ReclaimStale\(ctx context.Context, staleAfter time.Duration\) \(reclaimed int, err error\)](<#Store.ReclaimStale>) - [func \(s \*Store\) ReclaimWorker\(ctx context.Context, workerID string\) \(found bool, err error\)](<#Store.ReclaimWorker>) - [func \(s \*Store\) RecordSpend\(ctx context.Context, o RecordSpendOpts\) error](<#Store.RecordSpend>) - [func \(s \*Store\) ReleaseClaim\(ctx context.Context, planID, taskID, sessionID, reason string\) error](<#Store.ReleaseClaim>) - [func \(s \*Store\) ResolveProject\(ctx context.Context, fps \[\]Fingerprint\) \(projectID string, found bool, err error\)](<#Store.ResolveProject>) - [func \(s \*Store\) SetPlanStatus\(ctx context.Context, id string, status PlanStatus\) error](<#Store.SetPlanStatus>) - [func \(s \*Store\) SetProjectConfig\(ctx context.Context, projectID, key, value string\) error](<#Store.SetProjectConfig>) - [func \(s \*Store\) SetWorkerTask\(ctx context.Context, workerID, planID, taskID string\) error](<#Store.SetWorkerTask>) - [func \(s \*Store\) StatusCounts\(ctx context.Context\) \(StatusCounts, error\)](<#Store.StatusCounts>) - [func \(s \*Store\) TouchProjectLastSeen\(ctx context.Context, projectID string\) error](<#Store.TouchProjectLastSeen>) - [type Task](<#Task>) - [type TaskStatus](<#TaskStatus>) - [type WorkerOpts](<#WorkerOpts>) ## Constants Fingerprint identity kinds. A project is identified by the SET of these rows in project\_identifiers, not by a single fragile path — see §5b of the supervisor\-architecture design. ```go const ( FingerprintKindAbsPath = "abs_path" FingerprintKindGitRootCommit = "git_root_commit" FingerprintKindGitRemote = "git_remote" ) ``` ## Variables ErrDuplicateSlug is returned when a plan with \(project\_id, slug\) already exists. Callers either pick a new slug or update. ```go var ErrDuplicateSlug = errors.New("store: plan with slug already exists in this project") ``` ErrDuplicateTask is returned by CreateTask when a task with the same \(plan\_id, id\) already exists. It is the ONE benign CreateTask failure a concurrent dispatcher expects \(two dispatchers racing to materialize the same step\); callers match it via errors.Is and fall through to the claim attempt. Every OTHER CreateTask error \(disk full, DB busy, I/O, a validation failure\) is a real fault that must NOT be silently swallowed. ```go var ErrDuplicateTask = errors.New("store: task with this id already exists in this plan") ``` ErrNoReadyTask indicates ClaimNextReady found nothing claimable. ```go var ErrNoReadyTask = errors.New("store: no ready task") ``` ErrPlanNotFound is returned when an operation targets a plan id that no row matches. It is a typed sentinel so callers match with errors.Is rather than scraping the formatted message \(the drive API maps it to CodeNotFound\). ```go var ErrPlanNotFound = errors.New("store: plan not found") ``` ErrTaskNotOwnedRunning is returned by MarkFailed\* when the task is no longer both running AND claimed by the reporting session — i.e. it was reclaimed by the reaper and \(possibly\) reassigned to another worker between dispatch and this failure report. It is a BENIGN outcome, not a hard error: the stale report is correctly dropped instead of stomping the current owner. Callers distinguish it via errors.Is and treat it as "someone else owns this now; nothing to do." ```go var ErrTaskNotOwnedRunning = errors.New("store: task not running under the reporting session (stale failure report)") ``` ## func [DSN]() ```go func DSN(dbPath string) string ``` DSN builds the canonical modernc.org/sqlite DSN for the user\-level store database at dbPath. Every process that opens the store \(the durable supervisor, the TUI, and the CLI\) MUST use this so they share identical locking and durability semantics. \_txlock=immediate makes every transaction take the write lock up front, so a SELECT\-then\-UPDATE \(e.g. ClaimNextReady\) can never race another process into SQLITE\_BUSY\_SNAPSHOT — which busy\_timeout does NOT retry. busy\_timeout then actually serializes the concurrent writers instead of failing them immediately. synchronous=NORMAL is the documented\-safe pairing with WAL and avoids an fsync on every heartbeat/tick write. The path is percent\-encoded per the SQLite file: URI rules so a dbPath containing '?', '\#', or '%' is not misparsed as URI syntax and pointed at the wrong database. ## func [Migrate]() ```go func Migrate(db *sql.DB) error ``` Migrate brings db up to currentSchemaVersion by applying any pending \*.up.sql migrations in lexical order. Migrations are idempotent per\-version via SQLite's user\_version PRAGMA. Each migration runs inside a transaction. ## type [A2AMessage]() A2AMessage is one durable row in the a2a\_messages evidence/message log. content\_json holds the serialized a2a.Message \(see internal/a2a\); this package treats it as an opaque string so internal/store has no import dependency on internal/a2a. ```go type A2AMessage struct { ID int64 WorkerID string PlanID string TaskID string Role string ContentJSON string OccurredAt string } ``` ## type [AppendMessageOpts]() AppendMessageOpts configures AppendMessage. ```go type AppendMessageOpts struct { WorkerID string // optional PlanID string TaskID string Role string // e.g. "ROLE_AGENT" | "ROLE_USER" ContentJSON string // the serialized a2a.Message } ``` ## type [CreatePlanOpts]() CreatePlanOpts configures plan creation. The caller supplies slug \+ title; ID is generated via Store.uuid\(\). ```go type CreatePlanOpts struct { ProjectID string Slug string Title string SourceMarkdown string TagsJSON string } ``` ## type [CreateTaskOpts]() CreateTaskOpts configures task creation. ```go type CreateTaskOpts struct { PlanID string ID string // stable slug (operator-chosen or fixit-emitted) Description string ParallelGroup *int64 SequenceOrdinal *int64 AcceptanceJSON string ParentTaskID string // RequiresApproval materializes the task in 'ready_pending_approval' // instead of the default 'pending', holding it out of dispatch until an // operator approves it (ApproveTask → 'ready' → claimable). This is the // producer for the human-in-the-loop approval gate; a plan step carries it // via the [approval] marker (see internal/plan). RequiresApproval bool } ``` ## type [EmitOpts]() EmitOpts configures Emit. ```go type EmitOpts struct { ProjectID string PlanID string TaskID string Kind string Stream string Actor string PayloadJSON string } ``` ## type [Event]() Event is one append\-only audit\-log row. Project\-scoped; may additionally be plan/task\-scoped. Both the old per\-repo event log and task\_events collapse into this single table \(see schema §events\). ```go type Event struct { ID int64 ProjectID string PlanID string TaskID string Kind string Stream string Actor string PayloadJSON string OccurredAt time.Time } ``` ## type [EventPayload]() EventPayload keeps event payloads structured so the CLI, TUI, and tests can reason about approvals, handoffs, retries, and provider context without string scraping. ```go type EventPayload struct { Summary string `json:"summary,omitempty"` Reason string `json:"reason,omitempty"` Evidence []string `json:"evidence,omitempty"` HandoffTo string `json:"handoff_to,omitempty"` Retryable bool `json:"retryable,omitempty"` NeedsContext []string `json:"needs_context,omitempty"` ApprovalRequired bool `json:"approval_required,omitempty"` Provider string `json:"provider,omitempty"` ProviderSessionID string `json:"provider_session_id,omitempty"` OperatorAction string `json:"operator_action,omitempty"` } ``` ## type [Fingerprint]() Fingerprint is one accumulated identity signal for a project. ```go type Fingerprint struct { Kind string Value string } ``` ### func [Fingerprints]() ```go func Fingerprints(ctx context.Context, dir string) ([]Fingerprint, error) ``` Fingerprints computes the identity fingerprints for a directory: always the cleaned absolute path, plus — best\-effort, if dir is a git repository — the root\-commit sha and any "origin" remote URL. A fingerprint whose git command fails \(e.g. no origin remote configured\) is silently skipped rather than failing the whole call. ## type [Options]() Options configures Open. ```go type Options struct { // DSN is a modernc.org/sqlite DSN. Prefer building it with DSN() so // every opener shares identical locking/durability pragmas. DSN string // Clock is swappable for tests. Nil defaults to clockwork.NewRealClock(). Clock clockwork.Clock // UUID is swappable for tests. Nil defaults to uuid.NewV7(). UUID func() string } ``` ## type [Plan]() Plan is the durable row, keyed by project \(not repo\_path — see §5b\). ```go type Plan struct { ID string ProjectID string Slug string Title string Status PlanStatus SourceMarkdown string CreatedAt time.Time UpdatedAt time.Time LastSessionAt sql.NullTime TagsJSON string // JSON array } ``` ## type [PlanStatus]() PlanStatus enumerates the lifecycle states a plan can be in. ```go type PlanStatus string ``` Plan lifecycle states. ```go const ( PlanStatusDraft PlanStatus = "draft" PlanStatusActive PlanStatus = "active" PlanStatusPaused PlanStatus = "paused" PlanStatusDone PlanStatus = "done" PlanStatusFailedPartial PlanStatus = "failed_partial" PlanStatusArchived PlanStatus = "archived" PlanStatusAbandoned PlanStatus = "abandoned" ) ``` ## type [RecordSpendOpts]() RecordSpendOpts configures RecordSpend. ```go type RecordSpendOpts struct { ProjectID string WorkerID string // optional Provider string Model string InputTokens int OutputTokens int CachedTokens int CostUSD float64 } ``` ## type [RunningWorker]() RunningWorker is one currently\-running worker row, projected for the observe surface \(status/GUI\): the worker's own id — which WorkerKill/ReclaimWorker key on — plus what it is working on. Distinct from WorkerOpts \(create\-time input\) and from any provider\-session id. ```go type RunningWorker struct { ID string Provider string PlanID string TaskID string } ``` ## type [SessionOpts]() SessionOpts configures CreateSession. ```go type SessionOpts struct { ID string // optional; caller may pass an existing UUID. Empty → auto. Role string // supervisor|client PID int PIDStartTime string Host string } ``` ## type [StatusCounts]() StatusCounts is the aggregate view the status reply exposes: how many active plans exist and how many tasks sit in each status a client surfaces. Sourced from one pass over the store so \`status\` reports real numbers rather than the zeros the reply carried before \(the task/plan counters were never populated\). ```go type StatusCounts struct { ActivePlans int Ready int Running int Approval int // ready_pending_approval Blocked int Failed int } ``` ## type [Store]() Store is the user\-level store handle. It wraps a \*sql.DB plus a deterministic clock \+ UUID provider \(test\-swappable\). ```go type Store struct { // contains filtered or unexported fields } ``` ### func [Open]() ```go func Open(ctx context.Context, opts Options) (*Store, error) ``` Open returns a migrated, ready\-to\-use Store. ### func \(\*Store\) [AddDep]() ```go func (s *Store) AddDep(ctx context.Context, planID, taskID, dependsOn string) error ``` AddDep wires task → depends\_on for the same plan. Rejects cycles. ### func \(\*Store\) [AddProjectIdentifiers]() ```go func (s *Store) AddProjectIdentifiers(ctx context.Context, projectID string, fps []Fingerprint) error ``` AddProjectIdentifiers accumulates additional fingerprints onto an existing project. Idempotent and safe to call repeatedly: re\-adding a fingerprint THIS project already owns refreshes its added\_at \(so most\-recent\-wins ordering stays correct after a move\); a fingerprint owned by a DIFFERENT project is left untouched \(never stolen\), since \(kind, value\) is the primary key and the UPSERT is guarded on the owning project\_id — see insertIdentifiers. ### func \(\*Store\) [AppendMessage]() ```go func (s *Store) AppendMessage(ctx context.Context, o AppendMessageOpts) error ``` AppendMessage records one worker\<\-\>orchestrator A2A message \(most importantly, evidence a worker submits when it believes a task is done\). This ONLY logs the message — it never changes task status. Only internal/orch.VerifyAndComplete may transition a task to done. ### func \(\*Store\) [ApproveTask]() ```go func (s *Store) ApproveTask(ctx context.Context, planID, taskID string) (found, changed bool, err error) ``` ApproveTask clears the approval gate on a task: it transitions a ready\_pending\_approval task to ready so dispatch can pick it up. Idempotent on the desired end state — approving a task that is already ready \(or past it\) returns found=true, changed=false rather than erroring. found=false \(no error\) when the task doesn't exist, so the caller can surface CodeNotFound. ### func \(\*Store\) [Backup]() ```go func (s *Store) Backup(ctx context.Context, destDir string) (string, error) ``` Backup writes a consistent point\-in\-time copy of the store database into destDir using SQLite's \`VACUUM INTO\`, which — unlike a raw file copy — is safe to run against a live database \(it takes a read transaction and produces a compacted, fully\-consistent snapshot even while other connections are writing under WAL\). Returns the path to the backup file. ### func \(\*Store\) [ClaimNextReady]() ```go func (s *Store) ClaimNextReady(ctx context.Context, planID, sessionID, workerID string) (*Task, error) ``` ClaimNextReady is the atomic "claim the next ready task for this worker" operation. Returns the claimed task, or ErrNoReadyTask if none. Uses BEGIN \(with \_txlock=immediate at the DSN level\) \+ UPDATE with a checked RowsAffected so two parallel workers never claim the same task. ### func \(\*Store\) [ClearWorkerTask]() ```go func (s *Store) ClearWorkerTask(ctx context.Context, workerID, status string) error ``` ClearWorkerTask clears the active task from one worker row and marks it idle or terminated \(status defaults to "idle" when empty\). ### func \(\*Store\) [Close]() ```go func (s *Store) Close() error ``` Close releases DB resources. ### func \(\*Store\) [CloseSession]() ```go func (s *Store) CloseSession(ctx context.Context, sessionID string) error ``` CloseSession removes a session row. FK cascades clear its workers. ### func \(\*Store\) [CountRunningWorkers]() ```go func (s *Store) CountRunningWorkers(ctx context.Context) (int, error) ``` CountRunningWorkers returns how many worker rows are currently status='running' — i.e. actively assigned a task, not idle/terminated/ crashed. Used by the supervisor's HandleStatus to report a real ActiveWorkers count sourced from the store rather than an in\-process map that only the dispatcher owning the pty could ever populate. ### func \(\*Store\) [CreatePlan]() ```go func (s *Store) CreatePlan(ctx context.Context, o CreatePlanOpts) (string, error) ``` CreatePlan inserts a fresh plan in draft status and returns the newly generated UUID v7 id. ### func \(\*Store\) [CreateProject]() ```go func (s *Store) CreateProject(ctx context.Context, displayName string, fps []Fingerprint) (string, error) ``` CreateProject inserts a new project row and seeds it with the given fingerprints. Returns the newly generated UUID v7 id. ### func \(\*Store\) [CreateSession]() ```go func (s *Store) CreateSession(ctx context.Context, o SessionOpts) (string, error) ``` CreateSession inserts a session row. Returns the session id. The row lifetime matches one supervisor or client process attached to this DB. ### func \(\*Store\) [CreateTask]() ```go func (s *Store) CreateTask(ctx context.Context, o CreateTaskOpts) error ``` CreateTask inserts a new task — 'pending' by default, or 'ready\_pending\_approval' when o.RequiresApproval is set \(held behind the approval gate\). Callers wire dependencies via AddDep. ### func \(\*Store\) [CreateWorker]() ```go func (s *Store) CreateWorker(ctx context.Context, o WorkerOpts) (string, error) ``` CreateWorker registers a newly\-spawned agent subprocess against a session. Returns the worker row id. Successor to plandag's CreateSessionVariant — no persona; carries the provider capability \(native\_fanout\) instead of a variant name \(§9/§10 of the design\). ### func \(\*Store\) [DB]() ```go func (s *Store) DB() *sql.DB ``` DB returns the underlying \*sql.DB for callers that need raw access. Business\-logic callers should use Store's typed methods instead. ### func \(\*Store\) [Emit]() ```go func (s *Store) Emit(ctx context.Context, o EmitOpts) error ``` Emit appends one event row. Used for events not already covered by a more specific transactional helper \(e.g. task claim/done/failed emit their own events inline so the status transition and audit row commit atomically\). ### func \(\*Store\) [EventsAfter]() ```go func (s *Store) EventsAfter(ctx context.Context, projectID string, afterID int64, limit int) ([]Event, error) ``` EventsAfter returns a project's events with id strictly greater than afterID, in ascending id order, capped at limit. It is the tail query backing the Attach event stream: a client resumes from its last\-seen id and each call returns the next contiguous batch. Ascending order is deliberate — events are delivered oldest\-first so a live view applies them in the order they occurred \(the opposite of ListProjectEvents, which is newest\-first for a backlog snapshot\). Pass afterID=0 to start from the beginning. Scoping includes plan\-linked events \(see eventProjectScope\). ### func \(\*Store\) [GetPlan]() ```go func (s *Store) GetPlan(ctx context.Context, id string) (*Plan, error) ``` GetPlan loads a plan by id. ### func \(\*Store\) [GetProjectConfig]() ```go func (s *Store) GetProjectConfig(ctx context.Context, projectID string) (map[string]string, error) ``` GetProjectConfig returns all DB\-resident config key/value pairs for a project. Values are stored as JSON\-encoded scalars/arrays/objects \(the caller decodes\); this layer treats them as opaque strings. ### func \(\*Store\) [GetTask]() ```go func (s *Store) GetTask(ctx context.Context, planID, id string) (*Task, error) ``` GetTask loads one task by \(plan\_id, id\). ### func \(\*Store\) [HeartbeatSession]() ```go func (s *Store) HeartbeatSession(ctx context.Context, sessionID string) error ``` HeartbeatSession refreshes last\_heartbeat for a session. Called periodically by the durable supervisor. The reaper uses staleness to detect dead sessions. ### func \(\*Store\) [HeartbeatWorker]() ```go func (s *Store) HeartbeatWorker(ctx context.Context, workerID string) error ``` HeartbeatWorker refreshes one worker row's heartbeat. ### func \(\*Store\) [HeartbeatWorkerAndSession]() ```go func (s *Store) HeartbeatWorkerAndSession(ctx context.Context, workerID string) error ``` HeartbeatWorkerAndSession refreshes both a worker row AND its owning session row in one call. A worker's session \(spawnWorkerRows\) is not heartbeated by anything else — only the supervisor's own session is \(HeartbeatSession\) — so without this a live worker's session goes stale and the reaper's step\-2 session delete would CASCADE\-delete the still\-running worker, NULLing its task's claim and letting branch \(b\) re\-dispatch a live task \(double execution\). Beating both keeps the session as fresh as the worker for the reaper's staleness math. The two UPDATEs share one tx so a mid\-call failure never leaves the pair inconsistent. ### func \(\*Store\) [ListMessages]() ```go func (s *Store) ListMessages(ctx context.Context, planID, taskID string) ([]A2AMessage, error) ``` ListMessages returns every message logged for one task, oldest first. ### func \(\*Store\) [ListPlans]() ```go func (s *Store) ListPlans(ctx context.Context, projectID string, statuses []PlanStatus) ([]Plan, error) ``` ListPlans returns plans for a project matching filter. Empty filter → active \+ paused. Empty projectID lists across all projects. ### func \(\*Store\) [ListProjectEvents]() ```go func (s *Store) ListProjectEvents(ctx context.Context, projectID string, limit int) ([]Event, error) ``` ListProjectEvents returns the most recent events for one project, newest first. Scoping includes plan\-linked events \(see eventProjectScope\) — the headline lifecycle rows \(task.claimed/done/failed/…\) carry only plan\_id and no project\_id, so a bare \`project\_id = ?\` filter would silently drop exactly the events a backlog/overview view exists to show, and would DISAGREE with the live tail \(EventsAfter uses the same scope\). Keeping both on eventProjectScope means the macro/backlog snapshot and the live stream show the same set. ### func \(\*Store\) [ListRunningWorkers]() ```go func (s *Store) ListRunningWorkers(ctx context.Context) ([]RunningWorker, error) ``` ListRunningWorkers returns every worker row currently status='running', with the worker id and its current plan/task. It is the read backing the status reply's per\-worker detail so a client \(the GUI\) can name a specific worker to kill. Workers with no assigned task are still listed \(PlanID/TaskID empty\). ### func \(\*Store\) [ListTaskEvents]() ```go func (s *Store) ListTaskEvents(ctx context.Context, planID, taskID string, limit int) ([]Event, error) ``` ListTaskEvents returns the most recent events for one task first. ### func \(\*Store\) [ListTasks]() ```go func (s *Store) ListTasks(ctx context.Context, planID string, statuses []TaskStatus) ([]Task, error) ``` ListTasks returns tasks for one plan, optionally filtered by status. ### func \(\*Store\) [MarkBlocked]() ```go func (s *Store) MarkBlocked(ctx context.Context, planID, taskID, sessionID string, payload EventPayload) error ``` MarkBlocked releases a running task into the blocked set so an operator can later requeue or otherwise intervene. ### func \(\*Store\) [MarkDone]() ```go func (s *Store) MarkDone(ctx context.Context, planID, taskID, sessionID string, evidenceJSON string) ([]Task, error) ``` MarkDone transitions a running task to done, logs the event, and returns the set of newly\-ready downstream tasks. ### func \(\*Store\) [MarkFailed]() ```go func (s *Store) MarkFailed(ctx context.Context, planID, taskID, sessionID, reason string, maxRetries int) (retried bool, err error) ``` MarkFailed transitions a running task, owned by sessionID, to failed or retries. See MarkFailedWithPayload. ### func \(\*Store\) [MarkFailedWithPayload]() ```go func (s *Store) MarkFailedWithPayload(ctx context.Context, planID, taskID, sessionID string, payload EventPayload, maxRetries int) (retried bool, err error) ``` MarkFailedWithPayload transitions a running task to failed or retries while preserving structured payload details in the event log. Both UPDATEs are guarded by \`status = 'running' AND claimed\_by\_session = sessionID\`, the same owner guard MarkDone and MarkBlocked carry. Without the owner guard a stale failure report from a worker whose claim the reaper already reclaimed \(and possibly reassigned to a new worker\) would flip the task back to pending / failed and clear the NEW owner's claim — double execution plus a dropped completion. When the guard matches nothing the task has moved on under a different session; MarkFailed returns ErrTaskNotOwnedRunning so the caller can drop the stale report rather than resurrect the task. ### func \(\*Store\) [MaxEventID]() ```go func (s *Store) MaxEventID(ctx context.Context, projectID string) (int64, error) ``` MaxEventID returns the highest event id for a project, or 0 if the project has no events. It gives an Attach client the initial cursor to resume from \(the client reads this — or the backlog's max id — then attaches with it\), so the client owns a single monotonic cursor and no event slips through the gap between a backlog read and the live stream. Scoping matches EventsAfter \(it includes plan\-linked rows\) so the two agree on project membership. ### func \(\*Store\) [ProjectAbsPath]() ```go func (s *Store) ProjectAbsPath(ctx context.Context, projectID string) (path string, found bool, err error) ``` ProjectAbsPath returns the most recently recorded absolute\-path fingerprint for a project, or found=false when the project has no abs\_path identifier. The orchestrator uses this to launch workers in the project's own checkout: supervisor mode's working directory is deliberately irrelevant \(§4\), so a dispatch must resolve the target repo explicitly rather than inheriting the supervisor process's cwd. A project can accumulate more than one abs\_path \(the same repo cloned to two locations, or moved\); the most recently added one is returned as the best current guess at where the operator is working now. ### func \(\*Store\) [ProjectSpendByProvider]() ```go func (s *Store) ProjectSpendByProvider(ctx context.Context, projectID string) (map[string]float64, error) ``` ProjectSpendByProvider sums cost\_usd for one project, grouped by provider. Used to enforce a per\-provider spend cap before dispatch. ### func \(\*Store\) [Ready]() ```go func (s *Store) Ready(ctx context.Context, planID string) ([]Task, error) ``` Ready returns tasks that are ready to run — every dependency is in a terminal\-satisfied state \(\`done\`, \`skipped\`, or \`decomposed\`\) — and are in a claimable status: \`pending\` \(ungated\) or \`ready\` \(a task that WAS gated behind approval and has since been approved via ApproveTask\). A task still in \`ready\_pending\_approval\` is deliberately NOT returned: the approval gate holds it until an operator approves it. Result is ordered by created\_at for stable test output. ### func \(\*Store\) [ReclaimStale]() ```go func (s *Store) ReclaimStale(ctx context.Context, staleAfter time.Duration) (reclaimed int, err error) ``` ReclaimStale is the in\-store reaper: the old daemon never implemented this, so a crashed worker permanently wedged its claimed task. It: 1. Requeues tasks whose claiming worker's last\_heartbeat is older than staleAfter — increments reclaim\_count, sets status back to pending, and clears the claim so ClaimNextReady can hand it to a live worker. 2. Deletes worker and session rows stale beyond a longer window \(staleSessionMultiplier \* staleAfter\), once any of their claims have already been reclaimed by step 1. Returns the number of tasks reclaimed in step 1. ### func \(\*Store\) [ReclaimWorker]() ```go func (s *Store) ReclaimWorker(ctx context.Context, workerID string) (found bool, err error) ``` ReclaimWorker forcibly reclaims a worker's in\-flight task and marks the worker terminated — the store side of an operator/GUI "kill this worker" action. It mirrors the reaper's reclaim: the worker's running task \(if any\) goes back to 'pending' with its claim cleared \(so it re\-dispatches\), and the worker row is marked terminated. found=false \(no error\) when workerID is unknown, so a kill of an already\-gone worker is a benign no\-op the caller can surface as CodeNotFound. The actual subprocess is killed by the orchestrator/provider layer that owns the pty; this only does the store\-side bookkeeping. ### func \(\*Store\) [RecordSpend]() ```go func (s *Store) RecordSpend(ctx context.Context, o RecordSpendOpts) error ``` RecordSpend appends one spend row for a completed provider turn. Used by the orchestrator to accumulate provider.Usage.CostUSD per project so spend caps \(configured per provider/variant\) can be enforced before the next dispatch. ### func \(\*Store\) [ReleaseClaim]() ```go func (s *Store) ReleaseClaim(ctx context.Context, planID, taskID, sessionID, reason string) error ``` ReleaseClaim requeues a running task owned by sessionID back to pending WITHOUT charging a retry, for SYSTEM\-level aborts \(e.g. a fan\-out group whose later claim failed\) as opposed to task\-execution failures. Using MarkFailed here would penalize the retry budget for something the task never got a chance to attempt, and could terminally fail an otherwise\-valid task after a few transient orchestrator hiccups. Owner\-guarded like MarkFailed: a task no longer running under sessionID yields ErrTaskNotOwnedRunning \(benign — someone else owns it now\). ### func \(\*Store\) [ResolveProject]() ```go func (s *Store) ResolveProject(ctx context.Context, fps []Fingerprint) (projectID string, found bool, err error) ``` ResolveProject looks up an existing project by matching ANY of the given fingerprints against project\_identifiers. Returns found=false \(no error\) when no fingerprint matches. ### func \(\*Store\) [SetPlanStatus]() ```go func (s *Store) SetPlanStatus(ctx context.Context, id string, status PlanStatus) error ``` SetPlanStatus updates the plan's status column. ### func \(\*Store\) [SetProjectConfig]() ```go func (s *Store) SetProjectConfig(ctx context.Context, projectID, key, value string) error ``` SetProjectConfig upserts one DB\-resident config key/value pair for a project. ### func \(\*Store\) [SetWorkerTask]() ```go func (s *Store) SetWorkerTask(ctx context.Context, workerID, planID, taskID string) error ``` SetWorkerTask updates the currently assigned plan/task for one worker row and refreshes its heartbeat. ### func \(\*Store\) [StatusCounts]() ```go func (s *Store) StatusCounts(ctx context.Context) (StatusCounts, error) ``` StatusCounts returns the plan/task aggregates for the status reply, across all projects \(the supervisor is project\-agnostic\). Counts are derived from the live rows so they stay accurate without any in\-process bookkeeping. ### func \(\*Store\) [TouchProjectLastSeen]() ```go func (s *Store) TouchProjectLastSeen(ctx context.Context, projectID string) error ``` TouchProjectLastSeen updates last\_seen\_at to now. Called whenever a project is resolved/used so operators can see recency. ## type [Task]() Task is a DAG node. No variant/persona columns — the orchestrator assigns a worker from plan structure, not from a baked persona \(§10 of the supervisor\-architecture design\). ```go type Task struct { ID string PlanID string Description string Status TaskStatus ParallelGroup sql.NullInt64 SequenceOrdinal sql.NullInt64 AcceptanceJSON string ClaimedBySession string ClaimedByWorkerID string RetryCount int ReclaimCount int ParentTaskID string CreatedAt time.Time UpdatedAt time.Time } ``` ## type [TaskStatus]() TaskStatus enumerates valid task lifecycle states. ```go type TaskStatus string ``` Task lifecycle states. ```go const ( TaskStatusPending TaskStatus = "pending" TaskStatusReady TaskStatus = "ready" TaskStatusReadyPendingApproval TaskStatus = "ready_pending_approval" TaskStatusBlocked TaskStatus = "blocked" TaskStatusRunning TaskStatus = "running" TaskStatusDone TaskStatus = "done" TaskStatusFailed TaskStatus = "failed" TaskStatusSkipped TaskStatus = "skipped" TaskStatusDecomposed TaskStatus = "decomposed" ) ``` ## type [WorkerOpts]() WorkerOpts configures CreateWorker. ```go type WorkerOpts struct { SessionID string Provider string Model string NativeFanout bool SubprocessPID int SubprocessStartTime string } ``` Generated by [gomarkdoc]()