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
- Variables
- func AgentID(name string) string
- func ExternalID(keyName string) string
- func IsClosed(status string) bool
- func Key(id int64) string
- func MemberName(id string) string
- func ValidStatus(s string) bool
- type Change
- type ConflictError
- type CreateInput
- type CreateRequest
- type Detail
- type Directory
- type DirectoryFunc
- type Event
- type Filter
- type Member
- type Option
- type RuleError
- type Service
- func (s *Service) Checkout(ctx context.Context, actor string, id int64) (*Task, error)
- func (s *Service) Comment(ctx context.Context, actor string, id int64, body string) (*Event, error)
- func (s *Service) Create(ctx context.Context, actor string, req CreateRequest) (*Task, error)
- func (s *Service) Detail(ctx context.Context, id int64) (*Detail, error)
- func (s *Service) Get(ctx context.Context, id int64) (*Task, error)
- func (s *Service) List(ctx context.Context, f Filter) ([]Task, error)
- func (s *Service) Members(ctx context.Context) ([]Member, error)
- func (s *Service) Release(ctx context.Context, actor string, id int64) (*Task, error)
- func (s *Service) Resolve(ctx context.Context, name, actor string) (string, error)
- func (s *Service) SetOnChange(fn func(Change))
- func (s *Service) Update(ctx context.Context, actor string, id int64, req UpdateRequest) (*Task, error)
- type Store
- func (s *Store) Assign(ctx context.Context, actor string, id int64, assignee string) (*Task, error)
- func (s *Store) Checkout(ctx context.Context, actor string, id int64) (*Task, error)
- func (s *Store) Close() error
- func (s *Store) Comment(ctx context.Context, actor string, id int64, body string) (*Event, error)
- func (s *Store) Create(ctx context.Context, in CreateInput) (*Task, error)
- func (s *Store) Detail(ctx context.Context, id int64) (*Detail, error)
- func (s *Store) Edit(ctx context.Context, actor string, id int64, title, body string) (*Task, error)
- func (s *Store) Get(ctx context.Context, id int64) (*Task, error)
- func (s *Store) List(ctx context.Context, f Filter) ([]Task, error)
- func (s *Store) Release(ctx context.Context, actor string, id int64) (*Task, error)
- func (s *Store) SetStatus(ctx context.Context, actor string, id int64, status, reason string) (*Task, error)
- type Task
- type UpdateRequest
Constants ¶
const ( StatusTodo = "todo" StatusInProgress = "in_progress" StatusReview = "review" StatusBlocked = "blocked" StatusDone = "done" StatusCancelled = "cancelled" )
Task statuses.
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.
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.
const ( MaxTitleLen = 200 MaxBodyLen = 20000 MaxReasonLen = 500 )
Input limits.
const ( DefaultMaxDepth = 3 DefaultMaxChildren = 20 )
Default subtask limits.
Variables ¶
var ErrNotFound = errors.New("task not found")
ErrNotFound is returned when a task does not exist.
Functions ¶
func ExternalID ¶
ExternalID returns the member ID for an API key name.
Types ¶
type ConflictError ¶
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 ¶
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 ¶
DirectoryFunc adapts a function to 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 ¶
WithMaxChildren caps the number of subtasks under one parent.
func WithMaxDepth ¶
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).
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 ¶
NewService wires a store to a member directory. auditor may be nil.
func (*Service) Resolve ¶
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 ¶
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 ¶
NewInMemoryStore creates a private in-memory store for tests.
func NewSQLiteStore ¶
NewSQLiteStore opens the task tables in the database at dbPath.
func (*Store) Assign ¶
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 ¶
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) Create ¶
Create inserts a task and its "created" event. A subtask also adds a "subtask" event to its parent's thread.
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.
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.