Documentation
¶
Overview ¶
Package agenttask implements a shared, project-local queue of work items scheduled by internal producers (the daemon, doctor, scripts) for the next available AI coworker to execute — typically as a fresh-context subagent.
It is deliberately NOT a beads (bd) replacement: bd tracks human-facing project work, agenttask tracks ephemeral, machine-scheduled chores run on behalf of the developer's live session. See docs/specs/agent-task-scheduling.md for the full design.
Index ¶
- Constants
- Variables
- func CreateSchema(db *sql.DB) error
- func Enqueue(projectRoot string, task *Task) (bool, error)
- func NormalizeAgentType(s string) string
- func QueueExists(projectRoot string) bool
- func QueuePath(projectRoot string) string
- func ValidKind(kind string) bool
- type ClaimOptions
- type Status
- type Store
- func (s *Store) Add(task *Task) (added bool, err error)
- func (s *Store) Cancel(id, reason string) error
- func (s *Store) Claim(opts ClaimOptions) (*Task, error)
- func (s *Store) Close() error
- func (s *Store) Complete(id, result string) error
- func (s *Store) ExtendLease(id string, lease time.Duration) error
- func (s *Store) Get(id string) (*Task, error)
- func (s *Store) List(includeTerminal bool) ([]*Task, error)
- func (s *Store) ListView(includeTerminal bool) ([]*Task, error)
- func (s *Store) Ready(agentType string) ([]*Task, error)
- func (s *Store) ReadyView(agentType string) ([]*Task, error)
- type Task
Constants ¶
const ( KindDoctor = "doctor" KindSessionFinalize = "session-finalize" KindAntiEntropy = "anti-entropy" KindPlanFeedback = "plan-feedback" KindCustom = "custom" )
Known task kinds. The vocabulary is closed on purpose: the surfacing/guidance layer maps each kind to a FIXED ox action (a playbook), so the agent never derives what to run from free-form task text. An unknown kind has no playbook and must not be auto-executed.
const ( MaxTitleLen = 200 MaxBodyLen = 4096 MaxPayloadLen = 8192 // total bytes across payload keys+values )
Size limits bound a single task so a hostile or buggy producer cannot flood the agent's context or wedge the queue with an oversized row.
const DefaultLease = 15 * time.Minute
DefaultLease is how long a claimed task stays in_progress before the store reconciles it back to ready (assuming the claimer did not complete or extend it). Picked to be long enough for a subagent to summarize a session but short enough that a crashed agent's work is rescheduled promptly.
const SchemaVersion = 1
SchemaVersion is the on-disk schema generation. There is no migration tool: the queue is ephemeral (gitignored, rebuildable), so a schema change just bumps this constant — NewStore detects a mismatched PRAGMA user_version on an existing DB and recreates it from scratch rather than migrating. Durable stores (e.g. internal/codedb) hand-write Go migrations; this one does not need to because it carries no data worth preserving across a schema change.
Variables ¶
var ErrTaskNotFound = errors.New("task not found")
ErrTaskNotFound is returned when an operation targets an unknown task id.
Functions ¶
func CreateSchema ¶
CreateSchema initializes the task table and indexes and stamps the schema version. Idempotent.
func Enqueue ¶
Enqueue is a convenience wrapper that opens a store for projectRoot, adds a single task, and closes the store. Intended for producers (the daemon, doctor) that schedule work without holding a long-lived store handle.
func NormalizeAgentType ¶
NormalizeAgentType folds known agent-type aliases to their canonical slug so "claude-code" and "claude" target the same tasks. Unknown values are returned unchanged.
func QueueExists ¶
QueueExists reports whether a project has a task queue yet. Read-only callers (the prompt hook, ox status) use it to avoid NewStore's MkdirAll side effect — a status/surface read must not materialize the queue directory.
Types ¶
type ClaimOptions ¶
type ClaimOptions struct {
AgentID string // ox internal agent id of the claimer
AgentType string // claimer's agent type (for target matching)
PID int // claimer's process id (host-local liveness)
Lease time.Duration // how long to hold the claim; defaults to DefaultLease
}
ClaimOptions parameterizes Claim.
type Status ¶
type Status string
Status is the lifecycle state of a task.
const ( // StatusReady is a task waiting to be claimed by an agent. StatusReady Status = "ready" // StatusInProgress is a task claimed by an agent and currently leased. StatusInProgress Status = "in_progress" // StatusCompleted is a task an agent finished successfully (terminal). StatusCompleted Status = "completed" // StatusCanceled is a task abandoned without completion (terminal). StatusCanceled Status = "canceled" )
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is an embedded-SQLite queue of agent tasks, shared per repo (not per user): the queue exists so the next available agent — whoever that is — can pick up work. Atomicity, dedup, and lease reclaim are enforced by the database, replacing the hand-rolled flock + JSONL rewrite of earlier versions.
The store is local-only and ephemeral; it is gitignored and rebuildable. NFS is unsupported (embedded SQLite locking is undefined there) — the same local-working-dir assumption the JSONL store carried.
func NewStore ¶
NewStore opens (or creates) a task store for the given project root, creating the .sageox/agent_tasks/ directory if needed. Callers should Close the store; for one-shot producers prefer Enqueue.
func (*Store) Add ¶
Add inserts a new task. The id, created-at, and status are filled in when empty. If the task carries a DedupKey and an active (non-terminal) task with that key already exists, Add is a no-op and reports added=false. The active cap and dedup are enforced inside one transaction (plus a DB unique index), so concurrent producers cannot exceed the cap or double-enqueue a key.
func (*Store) Cancel ¶
Cancel marks a task canceled with an optional reason. Idempotent on already-terminal tasks.
func (*Store) Claim ¶
func (s *Store) Claim(opts ClaimOptions) (*Task, error)
Claim atomically pops the highest-priority ready task the claimer is eligible for, marks it in_progress, and stamps the lease. Returns (nil, nil) when no eligible task is available. The guarded UPDATE (WHERE status='ready') makes a concurrent double-claim impossible: a racing claimer affects zero rows and moves to the next candidate.
func (*Store) Complete ¶
Complete marks a task completed with an optional result note. Idempotent on already-terminal tasks.
func (*Store) ExtendLease ¶
ExtendLease pushes out the lease deadline of an in_progress task, letting a long-running agent keep its claim. The task must still be in_progress.
func (*Store) List ¶
List returns active tasks priority-sorted (lower first), then oldest-first. When includeTerminal is false, terminal tasks are omitted. Reconciles stale leases and prunes expired/old-terminal rows first.
func (*Store) ListView ¶
ListView is retained for API compatibility. With the SQLite store the reconcile is a cheap indexed UPDATE/DELETE (it writes only when something actually expired), so the read-only fast path collapsed into List.
type Task ¶
type Task struct {
ID string `json:"id"` // UUIDv7 (time-sortable)
Title string `json:"title"` // short summary shown to the agent
Body string `json:"body,omitempty"` // fuller instruction for the executor
Kind string `json:"kind,omitempty"` // category: doctor, session-finalize, anti-entropy, custom
Priority int `json:"priority"` // lower = higher priority (matches agentwork.WorkItem)
Status Status `json:"status"` // ready | in_progress | completed | canceled
Source string `json:"source,omitempty"` // producer: daemon, doctor, cli
TargetAgent string `json:"target_agent,omitempty"` // restrict to an agent type; "" = any
DedupKey string `json:"dedup_key,omitempty"` // at most one active (non-terminal) task per key
Payload map[string]string `json:"payload,omitempty"` // optional structured data for the executor
CreatedAt time.Time `json:"created_at"`
ExpiresAt time.Time `json:"expires_at,omitempty"` // zero = never; dropped once past
// Lease fields — populated only while Status == in_progress.
ClaimedByAgentID string `json:"claimed_by_agent_id,omitempty"` // ox internal agent id (e.g. Oxa7b3)
ClaimedByPID int `json:"claimed_by_pid,omitempty"` // PID of claiming process (host-local liveness)
ClaimedHost string `json:"claimed_host,omitempty"` // hostname; PID only meaningful on the same host
ClaimedAt time.Time `json:"claimed_at,omitempty"`
LeaseExpiresAt time.Time `json:"lease_expires_at,omitempty"` // reverts to ready if not completed by this time
Attempts int `json:"attempts,omitempty"` // incremented each time the task is (re)claimed
// Terminal fields.
CompletedAt time.Time `json:"completed_at,omitempty"` // when it reached a terminal state
Result string `json:"result,omitempty"` // optional note on completion/cancellation
}
Task is a single unit of scheduled agent work.
Field tags mirror the JSONL on-disk format. Optional fields are omitempty so the ledger stays compact and so that older rows (missing newer fields) round-trip cleanly through last-write-wins reads.
func (*Task) ClaimableBy ¶
ClaimableBy reports whether a task targeted at a particular agent type may be claimed/surfaced to the given agent type. Exported wrapper over the internal match so callers outside the package (surfacing) use identical semantics.
func (*Task) IsExpired ¶
IsExpired reports whether the task has an expiry that has passed. Tasks without an ExpiresAt never expire.
func (*Task) IsTerminal ¶
IsTerminal reports whether the task has reached a terminal state.