Tasks
The Task record, the task DAG, and the state machine every status change passes through
Not built yet. There is no Task table. packages/database still ships the single stub model
inherited from the template this repository forked, and no service layer validates the transitions
below. This page describes the intended shape, taken from spec/01-domain-model.md.
A Task is the unit of intent: one thing a human or an agent wants done. It is the most important table in the system. Everything else in the domain either points at a Task or is produced by working one.
Record conventions
These apply to every entity in the domain, not only Tasks.
| Convention | Rule |
|---|---|
| IDs | ULID primary keys, prefixed per entity (tsk_01J…), lexicographically sortable. No sequential integers are ever exposed. |
| Slugs | Every user-visible entity carries a stable slug, unique per workspace. /tasks/tsk_01J… and /tasks/fix-login-redirect both resolve. |
| Tenancy | Every row carries workspaceId. No cross-workspace read is possible, including for agent tokens. |
| Versioning | Every mutable row carries version int, bumped on write and surfaced as an ETag. A write may pass If-Match; a mismatch is 409 conflict_version, never a silent overwrite. |
| Provenance | createdByType (user, agent, system) and createdById, plus the same pair for updatedBy. Nothing is anonymous. |
| Soft delete | archivedAt. Rows are never hard deleted, because agents must be able to read history. |
| Timestamps | UTC, ISO-8601 on the wire. |
Project
A Project groups Tasks. It links to a GitHub repository (owner/name), a default branch, a board,
and a docs root. Routing and filtering both read it: an agent asking for work in one repository
filters by project.
Task fields
| Field | Notes |
|---|---|
title | One line, imperative. |
brief | Markdown. The full agent-readable statement of work. |
acceptanceCriteria[] | Checkable assertions. A task cannot reach done with any unchecked. |
status | See the state machine below. |
priority | p0, p1, p2, p3. Claiming picks the highest-priority ready task. |
projectId, parentTaskId | Placement in the tree. |
dependsOn[] | Edges of the task DAG. |
assignedAgentId, requestedByUserId | Who works it, who wants it. |
labels[] | Mirror of the board labels. |
externalRefs[] | {kind: github_issue | github_pr | url, ref}. |
leaseId, leaseExpiresAt | See approvals and leasing. |
contextRefs[] | Documents and Artifacts the agent must read before starting. |
The task DAG
Tasks relate in two independent ways, and both matter to scheduling.
parentTaskIdbuilds a tree: an epic and the tasks that decompose it. It answers "what is this part of".dependsOn[]builds a DAG: task B cannot start until task A is done. It answers "what has to happen first".
A task with unsatisfied dependencies is not ready, so a claim will not hand it out no matter how high its priority. Priority orders the ready set; the DAG decides what is in that set.
External references
externalRefs[] is how a Task stays attached to the world outside the record. Each entry is a kind
and a reference: a GitHub issue, a GitHub pull request, or a plain URL. The state machine reads
these. Moving to in_review requires an externalRef of kind github_pr, which is the mechanism
that stops a task being marked reviewable when no pull request exists.
Task state machine
Statuses mirror the repository's kanban review pipeline exactly, so the board column, the label, the GitHub Project card and the database enum can never disagree.
Legal transitions
| From | To | Guard |
|---|---|---|
backlog | todo | none |
todo | in_progress | live lease and an active Run |
in_progress | in_review | an externalRef of kind github_pr |
in_review | changes_requested | none |
changes_requested | in_progress | live lease and an active Run |
in_review | ready_to_merge | the acting identity must differ from the one that set in_review |
ready_to_merge | done | every acceptance criterion checked |
| any active status | blocked | none |
blocked | the status it left | none |
| any status | cancelled | none |
Active status means anything other than done and cancelled.
Enforcement
These rules are designed to live in the service layer, not in the UI, so every door enforces them identically.
- Transitions are validated against the table above. An illegal transition returns
422 invalid_transition, and the response body lists the legal ones. ready_to_mergecannot be set by whoever setin_review. The implementer never promotes its own work.donerequires every acceptance criterion checked. An agent that verified nothing cannot close a task.- Every transition writes an Event in the same transaction. No exceptions.
Comments and blocking
Comments are a threaded human and agent conversation scoped to a Task. Agents post progress notes
and questions there. A question posted with blocking: true and left unanswered moves the task to
blocked, which is how a runner stops without pretending it finished.
Schedules
A Schedule is a recurring task template: a cron expression plus a Task payload to instantiate. It creates Tasks; it is not a status a Task can be in.