task

package
v0.52.2 Latest Latest
Warning

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

Go to latest
Published: Oct 17, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package task stores durable tasks that den agents, external MCP clients and the operator hand to each other. Design: design/tasks-mvp.md.

Index

Constants

View Source
const (
	StatusTodo       = "todo"
	StatusInProgress = "in_progress"
	StatusReview     = "review"
	StatusBlocked    = "blocked"
	StatusDone       = "done"
	StatusCancelled  = "cancelled"
)

Task statuses.

View Source
const (
	EventCreated  = "created"
	EventComment  = "comment"
	EventClaimed  = "claimed"
	EventReleased = "released"
	EventStatus   = "status"
	EventAssigned = "assigned"
	EventSubtask  = "subtask"
	EventEdited   = "edited"
)

Event kinds recorded in a task's thread.

View Source
const (
	AgentPrefix    = "agent:"
	ExternalPrefix = "ext:"
	Human          = "human"
)

Member ID prefixes. A member ID is "agent:<name>", "ext:<key name>" or Human, so a den agent and an API key with the same name never collide.

View Source
const (
	MaxTitleLen  = 200
	MaxBodyLen   = 20000
	MaxReasonLen = 500
)

Input limits.

View Source
const (
	DefaultMaxDepth    = 3
	DefaultMaxChildren = 20
)

Default subtask limits.

Variables

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

ErrNotFound is returned when a task does not exist.

Functions

func AgentID

func AgentID(name string) string

AgentID returns the member ID for a den agent.

func ExternalID

func ExternalID(keyName string) string

ExternalID returns the member ID for an API key name.

func IsClosed

func IsClosed(status string) bool

IsClosed reports whether status is terminal.

func Key

func Key(id int64) string

Key formats a task ID for display.

func MemberName

func MemberName(id string) string

MemberName strips the prefix from a member ID.

func ValidStatus

func ValidStatus(s string) bool

ValidStatus reports whether s is a known status.

Types

type Change

type Change struct {
	TaskID int64
	Kind   string
	Actor  string
}

Change describes a mutation, for live-update fan-out.

type ConflictError

type ConflictError struct {
	TaskID  int64
	Holder  string
	Message string
}

ConflictError is returned when a claim or update loses to another member. Message is written for an agent to act on: it names the holder and what to do instead, and never invites a retry.

func (*ConflictError) Error

func (e *ConflictError) Error() string

type CreateInput

type CreateInput struct {
	Actor    string
	Title    string
	Body     string
	Assignee string
	Reviewer string
	ParentID *int64
}

CreateInput describes a new task. Member fields hold member IDs.

type CreateRequest

type CreateRequest struct {
	Title    string
	Body     string
	Assignee string
	ParentID *int64
	NoReview bool
}

CreateRequest is a new task as callers describe it. Assignee is a name Resolve understands. The filer reviews by default; NoReview skips review.

type Detail

type Detail struct {
	Task     Task    `json:"task"`
	Events   []Event `json:"events"`
	Children []Task  `json:"children"`
	Parent   *Task   `json:"parent,omitempty"`
}

Detail is a task with its thread, its subtasks and its parent.

type Directory

type Directory interface {
	Members(ctx context.Context) ([]Member, error)
}

Directory lists the den agents and external members tasks can go to. The operator (Human) is always a member and is added by the Service.

type DirectoryFunc

type DirectoryFunc func(ctx context.Context) ([]Member, error)

DirectoryFunc adapts a function to Directory.

func (DirectoryFunc) Members

func (f DirectoryFunc) Members(ctx context.Context) ([]Member, error)

Members implements Directory.

type Event

type Event struct {
	ID     int64     `db:"id" json:"id"`
	TaskID int64     `db:"task_id" json:"task_id"`
	At     time.Time `db:"at" json:"at"`
	Actor  string    `db:"actor" json:"actor"`
	Kind   string    `db:"kind" json:"kind"`
	Body   string    `db:"body" json:"body"`
	Data   string    `db:"data" json:"data,omitempty"`
}

Event is one row of a task's thread.

type Filter

type Filter struct {
	Statuses    []string
	Assignee    string
	Unassigned  bool // only tasks nobody is assigned to; ignored when Assignee is set
	ParentID    *int64
	TopLevel    bool
	ClosedSince time.Time
	Limit       int
}

Filter narrows List. Zero values mean "no filter", except ClosedSince: closed tasks are returned only when they closed at or after it.

type Member

type Member struct {
	ID    string `json:"id"`
	Name  string `json:"name"`
	Kind  string `json:"kind"` // "agent", "external" or "human"
	Label string `json:"label,omitempty"`
}

Member is someone a task can be assigned to.

type Option

type Option func(*Store)

Option configures a Store.

func WithMaxChildren

func WithMaxChildren(n int) Option

WithMaxChildren caps the number of subtasks under one parent.

func WithMaxDepth

func WithMaxDepth(n int) Option

WithMaxDepth caps subtask nesting. A top-level task has depth 0.

type RuleError

type RuleError struct{ Message string }

RuleError is returned when the actor may not make the change, or the change breaks a task rule (depth, review, missing reason).

func (*RuleError) Error

func (e *RuleError) Error() string

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service is the one entry point for task reads and writes. REST and both MCP surfaces call it, so rules, audit and notifications can't drift apart.

func NewService

func NewService(store *Store, dir Directory, auditor audit.Emitter) *Service

NewService wires a store to a member directory. auditor may be nil.

func (*Service) Checkout

func (s *Service) Checkout(ctx context.Context, actor string, id int64) (*Task, error)

Checkout claims a task for actor.

func (*Service) Comment

func (s *Service) Comment(ctx context.Context, actor string, id int64, body string) (*Event, error)

Comment adds a comment to a task's thread.

func (*Service) Create

func (s *Service) Create(ctx context.Context, actor string, req CreateRequest) (*Task, error)

Create files a task for actor.

func (*Service) Detail

func (s *Service) Detail(ctx context.Context, id int64) (*Detail, error)

Detail returns a task with its thread, subtasks and parent.

func (*Service) Get

func (s *Service) Get(ctx context.Context, id int64) (*Task, error)

Get returns one task.

func (*Service) List

func (s *Service) List(ctx context.Context, f Filter) ([]Task, error)

List returns tasks matching f.

func (*Service) Members

func (s *Service) Members(ctx context.Context) ([]Member, error)

Members returns everyone a task can be assigned to, the operator first.

func (*Service) Release

func (s *Service) Release(ctx context.Context, actor string, id int64) (*Task, error)

Release drops the hold on a task.

func (*Service) Resolve

func (s *Service) Resolve(ctx context.Context, name, actor string) (string, error)

Resolve turns a name into a member ID. It accepts member IDs, bare agent or key names, "me" (the actor), and "you"/"human" (the operator). An empty name stays empty, meaning unassigned.

func (*Service) SetOnChange

func (s *Service) SetOnChange(fn func(Change))

SetOnChange registers the live-update hook. Safe to call while serving.

func (*Service) Update

func (s *Service) Update(ctx context.Context, actor string, id int64, req UpdateRequest) (*Task, error)

Update applies req in the order edit, reassign, status, so a handoff and a status change in one call land the way a reader expects. Inputs are all checked before the first write.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store persists tasks and their threads. Every mutation runs in a BEGIN IMMEDIATE transaction, so a rule check and the write it guards see the same row even when two members race.

func NewInMemoryStore

func NewInMemoryStore(opts ...Option) (*Store, error)

NewInMemoryStore creates a private in-memory store for tests.

func NewSQLiteStore

func NewSQLiteStore(dbPath string, opts ...Option) (*Store, error)

NewSQLiteStore opens the task tables in the database at dbPath.

func (*Store) Assign

func (s *Store) Assign(ctx context.Context, actor string, id int64, assignee string) (*Task, error)

Assign hands a task to assignee (a member ID, or "" to unassign). It drops any hold and sends in-progress work back to To do.

func (*Store) Checkout

func (s *Store) Checkout(ctx context.Context, actor string, id int64) (*Task, error)

Checkout claims a task for actor. It succeeds when the task is open, not held by someone else, and unassigned or assigned to actor; an unassigned task becomes actor's. Claiming a task you already hold is a no-op.

func (*Store) Close

func (s *Store) Close() error

Close closes the database handle.

func (*Store) Comment

func (s *Store) Comment(ctx context.Context, actor string, id int64, body string) (*Event, error)

Comment adds a comment to a task's thread. Closed tasks take comments too.

func (*Store) Create

func (s *Store) Create(ctx context.Context, in CreateInput) (*Task, error)

Create inserts a task and its "created" event. A subtask also adds a "subtask" event to its parent's thread.

func (*Store) Detail

func (s *Store) Detail(ctx context.Context, id int64) (*Detail, error)

Detail returns a task with its thread, subtasks and parent.

func (*Store) Edit

func (s *Store) Edit(ctx context.Context, actor string, id int64, title, body string) (*Task, error)

Edit changes a task's title and body. Only the operator and the filer may edit.

func (*Store) Get

func (s *Store) Get(ctx context.Context, id int64) (*Task, error)

Get returns one task with its subtask counts.

func (*Store) List

func (s *Store) List(ctx context.Context, f Filter) ([]Task, error)

List returns tasks newest first.

func (*Store) Release

func (s *Store) Release(ctx context.Context, actor string, id int64) (*Task, error)

Release drops the hold on a task and returns it to To do. Only the holder and the operator may release.

func (*Store) SetStatus

func (s *Store) SetStatus(ctx context.Context, actor string, id int64, status, reason string) (*Task, error)

SetStatus moves a task to status. reason is required for blocked.

type Task

type Task struct {
	ID            int64      `db:"id" json:"id"`
	Title         string     `db:"title" json:"title"`
	Body          string     `db:"body" json:"body"`
	Status        string     `db:"status" json:"status"`
	Assignee      string     `db:"assignee" json:"assignee"`
	CreatedBy     string     `db:"created_by" json:"created_by"`
	Reviewer      string     `db:"reviewer" json:"reviewer"`
	ParentID      *int64     `db:"parent_id" json:"parent_id,omitempty"`
	Depth         int        `db:"depth" json:"depth"`
	Holder        string     `db:"holder" json:"holder"`
	HeldAt        *time.Time `db:"held_at" json:"held_at,omitempty"`
	BlockedReason string     `db:"blocked_reason" json:"blocked_reason,omitempty"`
	CreatedAt     time.Time  `db:"created_at" json:"created_at"`
	UpdatedAt     time.Time  `db:"updated_at" json:"updated_at"`
	ClosedAt      *time.Time `db:"closed_at" json:"closed_at,omitempty"`
	ChildrenTotal int        `db:"children_total" json:"children_total"`
	ChildrenDone  int        `db:"children_done" json:"children_done"`
}

Task is one row of the tasks table plus its subtask counts.

func (*Task) Closed

func (t *Task) Closed() bool

Closed reports whether the task is done or cancelled.

func (*Task) Key

func (t *Task) Key() string

Key is the display ID, e.g. "DK-131".

type UpdateRequest

type UpdateRequest struct {
	Title    *string
	Body     *string
	Assignee *string
	Status   *string
	Reason   string
}

UpdateRequest changes any of a task's editable fields. Nil fields are left alone. Assignee is a name Resolve understands; "" unassigns.

Jump to

Keyboard shortcuts

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