queue

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Sep 13, 2026 License: MIT Imports: 8 Imported by: 0

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

View Source
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).

View Source
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.

View Source
const PrioritySourceUnblock = "unblock"

PrioritySourceUnblock labels task.reprioritized facts written by the unblock bump (the ADR-0015 source enum).

View Source
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

View Source
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).

View Source
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

func ClampBacklog(priority int) int

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"
)

func BandOf added in v0.3.0

func BandOf(priority int) Band

BandOf reports which band a priority value lands in. Values below zero are backlog (the scale's floor is open); the band ladder is total.

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 New

func New(s Store) *Queue

New wraps a Store.

func (*Queue) BumpUnblocked added in v0.3.0

func (q *Queue) BumpUnblocked(ctx context.Context, dryRun bool) ([]UnblockChange, error)

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.

func (*Queue) Enqueue

func (q *Queue) Enqueue(ctx context.Context, n task.New) (task.Task, error)

Enqueue normalized-and-enqueues a task.

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

type RequeueEvidence struct {
	Reason  string `json:"reason"`
	RetryIn int64  `json:"retry_in_ms"`
}

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

type WatermarkEntry struct {
	Consumer  string
	Seq       int64
	UpdatedAt int64 // unix millis
}

WatermarkEntry is one consumer cursor row (tq watermarks show). Shared by every Store backend's ListWatermarks read.

Directories

Path Synopsis
companion module
postgres module
postgresv4 module
sqlite module
sqlitev4 module

Jump to

Keyboard shortcuts

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