agenttask

package
v0.12.0 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 13 Imported by: 0

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

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

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

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

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

View Source
var ErrTaskNotFound = errors.New("task not found")

ErrTaskNotFound is returned when an operation targets an unknown task id.

Functions

func CreateSchema

func CreateSchema(db *sql.DB) error

CreateSchema initializes the task table and indexes and stamps the schema version. Idempotent.

func Enqueue

func Enqueue(projectRoot string, task *Task) (bool, error)

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

func NormalizeAgentType(s string) string

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

func QueueExists(projectRoot string) bool

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.

func QueuePath

func QueuePath(projectRoot string) string

QueuePath returns the absolute path to a project's task queue database.

func ValidKind

func ValidKind(kind string) bool

ValidKind reports whether a kind is in the closed vocabulary. Empty is allowed (treated as unclassified) so producers may omit it.

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

func NewStore(projectRoot string) (*Store, error)

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

func (s *Store) Add(task *Task) (added bool, err error)

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

func (s *Store) Cancel(id, reason string) error

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

func (s *Store) Close() error

Close releases the database handle. Safe to call on a nil-db store.

func (*Store) Complete

func (s *Store) Complete(id, result string) error

Complete marks a task completed with an optional result note. Idempotent on already-terminal tasks.

func (*Store) ExtendLease

func (s *Store) ExtendLease(id string, lease time.Duration) error

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

func (s *Store) Get(id string) (*Task, error)

Get returns a single task by id.

func (*Store) List

func (s *Store) List(includeTerminal bool) ([]*Task, error)

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

func (s *Store) ListView(includeTerminal bool) ([]*Task, error)

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.

func (*Store) Ready

func (s *Store) Ready(agentType string) ([]*Task, error)

Ready returns ready tasks claimable by the given agent type, priority-sorted. An empty agentType only matches untargeted tasks.

func (*Store) ReadyView

func (s *Store) ReadyView(agentType string) ([]*Task, error)

ReadyView is retained for API compatibility; see ListView.

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

func (t *Task) ClaimableBy(agentType string) bool

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

func (t *Task) IsExpired() bool

IsExpired reports whether the task has an expiry that has passed. Tasks without an ExpiresAt never expire.

func (*Task) IsTerminal

func (t *Task) IsTerminal() bool

IsTerminal reports whether the task has reached a terminal state.

Jump to

Keyboard shortcuts

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