Documentation
¶
Overview ¶
Package queue is the store CONTRACT module: the Store interface, Filter, and the Queue facade that embedders program against (ADR-0012).
Drivers live in separate modules and implement this contract: internal/queue/sqlite (embedded, one file, single serialized writer) and internal/queue/postgres (shared across machines, SKIP LOCKED claims); both are held to identical semantics by mirrored test suites (ADR-0007). Every mutating operation also appends a fact to the journal in the same transaction, so the journal is always a complete, consistent history of the queue.
Index ¶
Constants ¶
const ( // BacklogMax is the ceiling backlog computations clamp to: no scorer, // keyword rule, or importance value reaches the hot band. BacklogMax = 99 // HotMin is the floor of the hot band. HotMin = 100 // HotMax is the ceiling of the hot band. HotMax = 149 // MachineMin is the floor of the machine band. MachineMin = 150 )
Band boundaries of the priority scale (ADR-0015 §1).
const ( // PriorityAgingDaysPerPoint is the task age — keyed on created_at, the // only immutable timestamp in the row (updated_at moves on every // heartbeat) — that earns one priority point. PriorityAgingDaysPerPoint = 3 // PriorityAgingMaxBonus caps the age bonus. It stays below the smallest // marker-level gap (20, ADR-0015 §2) so aging can reorder within a band // but never across marker levels or bands. PriorityAgingMaxBonus = 10 )
Priority aging (ADR-0015 §4): ClaimDue orders by an EFFECTIVE priority — stored priority plus a bounded age bonus computed inside the claim query (scheduling, not state; the stored priority never changes). Defined once here, referenced by both backends, so the semantics cannot drift.
const PrioritySourceUnblock = "unblock"
PrioritySourceUnblock labels task.reprioritized facts written by the unblock bump (the ADR-0015 source enum).
const UnblockBumpPriority = 15
UnblockBumpPriority is the priority bump a task gains when ALL its dependencies have completed (ADR-0015 unblock bump): half a marker level — meaningful against importance spacing, never a full ladder jump. Clamped to the backlog band on use.
Variables ¶
var ErrEmptyType = errors.New("queue: task type must not be empty")
ErrEmptyType is returned by Enqueue when New.Type is empty (Normalize cannot invent a type; callers must choose an executor).
var ErrNoTaskDue = errors.New("queue: no due task")
ErrNoTaskDue is returned by ClaimDue when nothing is claimable right now.
Functions ¶
func ClampBacklog ¶ added in v0.3.0
ClampBacklog clamps a computed backlog priority into [0, BacklogMax] so no derived score can smuggle a task into the hot or machine band (ADR-0015 §1: crossing a band boundary requires an explicit act).
Types ¶
type Band ¶ added in v0.3.0
type Band string
Band names one range of the single priority scale (ADR-0015 §1). One scale, three reserved bands: backlog computations clamp below hot; crossing upward requires an explicit human/flag act.
const ( // BandBacklog: 0–99 — humans (markers), importance, AI scores, // keyword fallback. BandBacklog Band = "backlog" // BandHot: 100–149 — same-session urgency (--same-session-priority) // only. BandHot Band = "hot" // BandMachine: 150+ — machine-generated operational tasks (cqa fixes). BandMachine Band = "machine" )
type Filter ¶
type Filter struct {
Project *string
Status *task.Status
Type *string
// Query is a case-insensitive substring search over id, type, project,
// payload, lease owner and last error — pushed into SQL LIKE, not a
// post-filter.
Query string
Limit int
// Offset skips the first Offset matches (pagination); applied after
// ordering. Meaningful together with Limit.
Offset int
// SeverityOrder orders by display severity (dead, running, pending,
// cancelled, completed), newest first within a status — the dashboard
// table order, stable across pages. Default order stays priority then
// age (the queue's fairness order).
SeverityOrder bool
// Sort overrides the ordering with an allowlisted column sort for the
// dashboard's sortable headers: "age-asc", "age-desc",
// "priority-asc", "priority-desc", "attempts-asc", "attempts-desc".
// Unknown values fall back to the default order (never interpolated
// into SQL).
Sort string
// Since restricts the listing to tasks created at or after this time
// (inclusive) — pushed into SQL as a created_at comparison, not a
// post-filter.
Since *time.Time
// Parked restricts the listing to rate-limit-parked tasks: pending
// with not_before in the future (the "11 tasks parked until 19:40"
// one-glance view). false or nil leaves the filter off.
Parked *bool
// PriorityMin/PriorityMax bound the listing to a STORED-priority
// range - the band filter's pushdown (hot = [100,149], machine =
// [150,infinity)). nil leaves the bound open. Aging is scheduling,
// not state: the range sees the stored value, not the effective rank.
PriorityMin *int
PriorityMax *int
}
Filter selects tasks for List.
type PriorityScore ¶ added in v0.3.0
type PriorityScore struct {
ItemKey string `json:"itemKey"`
Score int `json:"score"` // 0-100 (clamped to the backlog band on use)
EffortMinutes int `json:"effortMinutes"` // estimated agent effort
Source string `json:"source"` // scorer identity, e.g. "ai:<model>"
Reasoning string `json:"reasoning"` // one-line why
Tokens int `json:"tokens"` // tokens the verdict cost
ScoredAt int64 `json:"scoredAt"` // unix millis
}
PriorityScore is one cached AI verdict for a TODO_LIST item, keyed by the SAME dedup-key derivation the harvester uses (harvest.ItemKey), so a score survives until the item text changes and re-keys (ADR-0015 score cache). Effort feeds budget-aware claims; tokens feed cost measurement.
type Queue ¶
type Queue struct {
Store
}
Queue is the facade most consumers use: a Store plus convenience methods.
func (*Queue) BumpUnblocked ¶ added in v0.3.0
BumpUnblocked lifts PENDING tasks whose dependencies have ALL completed by UnblockBumpPriority (source "unblock") — the queue's answer to "the blocker finished, now it matters" (ADR-0015). Designed for repri time: no worker coupling, no new backend SQL — the deps table and its dep_id index already answer the reverse lookup, and enumeration is Go-side over List/Get (deps-carrying tasks are rare).
Rules: hot/machine tasks are protected (backlog band only); a task that already carries an unblock fact is never bumped twice (the fact trail is the idempotency guard); same-value writes append nothing. dryRun reports without writing.
type ReprioritizeEvidence ¶ added in v0.3.0
type ReprioritizeEvidence struct {
OldPriority int `json:"old_priority"`
NewPriority int `json:"new_priority"`
Source string `json:"source"`
Reason string `json:"reason,omitempty"`
}
ReprioritizeEvidence is the structured detail on task.reprioritized facts (ADR-0015 §5): what the priority was, what it became, which source decided, and why.
type RequeueEvidence ¶
RequeueEvidence is the structured detail on task.requeued facts: why the executor refused to start and how long the task waits before it becomes claimable again (01:48 report f2: the reason was a plain error string).
type Store ¶
type Store interface {
// Enqueue persists a new task (ID and defaults assigned here) and records
// the task.enqueued fact.
Enqueue(ctx context.Context, n task.New) (task.Task, error)
// ClaimDue atomically claims at most one due task for owner: pending,
// NotBefore passed, all deps completed. Sets Running + lease. Returns
// ErrNoTaskDue when nothing is claimable.
ClaimDue(ctx context.Context, owner string, lease time.Duration) (task.Task, error)
// Complete marks a Running task Completed (lease must be held) and records
// the task.completed fact.
Complete(ctx context.Context, id task.ID, owner string, result jsontext.Value) error
// Fail records a failed attempt. When attempts remain the task returns to
// Pending with NotBefore = now + backoff(attempt); otherwise it is
// Dead-lettered. Facts: task.failed (+ task.dead-lettered). evidence,
// when non-empty (executor.FailureEvidence JSON), lands on the
// task.failed fact's detail — the forensics (exit code, output tail)
// that make a failed attempt debuggable from the journal alone.
Fail(
ctx context.Context,
id task.ID,
owner string,
errText string,
backoff time.Duration,
evidence jsontext.Value,
) error
// FailPermanent dead-letters a Running task immediately, regardless of
// the attempt budget: the error class makes retrying pointless. The
// attempt is still counted. Facts: task.failed (carrying evidence)
// + task.dead-lettered (class "permanent").
FailPermanent(ctx context.Context, id task.ID, owner string, errText string, evidence jsontext.Value) error
// Requeue returns a claimed task to Pending WITHOUT counting an
// attempt; it becomes claimable again after delay. For preflight
// refusals: the environment was not ready, not the task. Facts:
// task.requeued.
Requeue(ctx context.Context, id task.ID, owner string, errText string, delay time.Duration) error
// UpdatePendingPriority changes a PENDING task's priority (ADR-0015
// §5) and records the task.reprioritized fact (old/new, source,
// reason) in the SAME transaction. Running/terminal tasks are refused
// with task.ErrInvalidTransition — priority is enqueue-time truth for
// anything already claimed or finished. A same-value update is a
// no-op: no error, no fact (idempotency: reruns and racing sweepers
// never spam the journal).
UpdatePendingPriority(ctx context.Context, id task.ID, newPriority int, source, reason string) error
// Heartbeat extends the lease of a Running task held by owner.
Heartbeat(ctx context.Context, id task.ID, owner string, extend time.Duration) error
// Cancel withdraws a Pending task. A non-empty reason is stored in the
// task.cancelled fact's detail ("reason" key) so the journal records WHY
// the task was withdrawn.
Cancel(ctx context.Context, id task.ID, reason string) error
// CancelRunning records a cooperative cancel request for a Running
// task: the task.cancel-requested fact is the flag. The executing
// worker observes it at its next heartbeat, stops the execution, and
// finalizes with CancelOwned; an expired lease finalizes it at
// reclaim. A non-empty reason is stored on the request fact's detail
// and carried onto the final task.cancelled fact. Idempotent — a
// second request appends nothing.
CancelRunning(ctx context.Context, id task.ID, reason string) error
// CancelRequested reports whether a cooperative cancel request is
// pending for the task — the worker's heartbeat observation query.
CancelRequested(ctx context.Context, id task.ID) (bool, error)
// CancelOwned finalizes a cooperative cancel: Running -> Cancelled,
// recorded by the lease-holding worker after it stopped the execution.
CancelOwned(ctx context.Context, id task.ID, owner string) error
// MarkOrphaned appends a task.orphaned fact for every Running task
// whose lease expired before the cutoff and that has no orphaned fact
// yet (idempotent). It changes no state — orphans stay Running until a
// reclaim — it records WHY the task is stranded so the journal can
// explain it. Returns how many facts were appended.
MarkOrphaned(ctx context.Context, cutoff time.Time) (int, error)
// RescueDead re-queues a Dead task with a fresh attempt budget.
RescueDead(ctx context.Context, id task.ID, maxAttempts int) error
// DismissDead cancels a Dead task with a recorded reason (DLQ dismiss):
// the autopsy verdict "unfixable" or an operator's ruling. The
// task.cancelled fact's detail carries the reason and who dismissed it,
// so the journal keeps the full death evidence plus the WHY of the
// withdrawal. Dead is terminal otherwise; facts are never deleted.
DismissDead(ctx context.Context, id task.ID, reason, by string) error
// Get returns the current task record.
Get(ctx context.Context, id task.ID) (task.Task, error)
// List returns tasks matching the filter.
List(ctx context.Context, f Filter) ([]task.Task, error)
// Facts exposes the journal (same store, same transaction domain):
// facts with Seq strictly greater than after, in Seq order. limit
// bounds the result when > 0; 0 means unbounded (bulk exports).
Facts(ctx context.Context, after int64, limit int) ([]journal.Fact, error)
// LastFacts returns the most recent limit facts in ascending Seq
// order — the bounded read behind feed-style renders. limit <= 0
// returns the whole journal.
LastFacts(ctx context.Context, limit int) ([]journal.Fact, error)
// HeadSeq returns the current highest fact Seq (0 when the journal is
// empty): the O(1) watermark for tailers, bridges and resume points.
HeadSeq(ctx context.Context) (int64, error)
// Watermark returns the persisted read cursor for a journal consumer
// and whether the consumer ever checkpointed (seq 0 is a valid cursor:
// "consumed nothing yet"): the resume point for bridges and sweepers
// after a restart.
Watermark(ctx context.Context, consumer string) (seq int64, exists bool, err error)
// SaveWatermark checkpoints a consumer cursor as a monotonic upsert
// (never regresses). It records consumer progress, not task state, so
// no fact is appended.
SaveWatermark(ctx context.Context, consumer string, seq int64) error
// SavePriorityScore upserts one cached item score (ADR-0015 score
// cache): an AI verdict persists and re-derives the same priority
// until the item text changes and re-keys. No fact — the cache is a
// projection input, not queue history.
SavePriorityScore(ctx context.Context, score PriorityScore) error
// PriorityScore returns the cached verdict for an item key, if any.
PriorityScore(ctx context.Context, itemKey string) (PriorityScore, bool, error)
// FactsForTask returns one task's facts in Seq order, bounded to the
// most recent limit when > 0 (0 = unbounded).
FactsForTask(ctx context.Context, id string, limit int) ([]journal.Fact, error)
// CountFacts counts facts of one type recorded at or after since —
// the SQL pushdown behind spend projections and stats.
CountFacts(ctx context.Context, ftype journal.FactType, since time.Time) (int64, error)
// StatusCounts counts tasks per status — the GROUP BY behind dashboard
// counters: O(statuses) work instead of a full task scan.
StatusCounts(ctx context.Context) (map[task.Status]int, error)
// ProjectCounts counts tasks per project per status — the GROUP BY
// behind the overview chips and per-project views.
ProjectCounts(ctx context.Context) (map[string]map[task.Status]int, error)
// CountTasks counts the tasks matching the filter — the COUNT(*)
// pushdown behind pagination ("page 2 of 14").
CountTasks(ctx context.Context, f Filter) (int, error)
// Close releases resources.
Close() error
}
Store is the persistence boundary for tasks and facts.
type UnblockChange ¶ added in v0.3.0
type UnblockChange struct {
TaskID task.ID `json:"task_id"`
OldPriority int `json:"old_priority"`
NewPriority int `json:"new_priority"`
CompletedDeps int `json:"completed_deps"`
}
UnblockChange records one applied (or would-be) unblock bump.
type WatermarkEntry ¶
WatermarkEntry is one consumer cursor row (tq watermarks show). Shared by every Store backend's ListWatermarks read.