Task board & automation
The board turns "things we intend to do" into "things that get executed": a card carries a project, an agent or a workflow, and the executor picks it up once it reaches the todo column. Status transitions, run records and comment loops all go through one service path. The frontend has no dedicated SSE stream; it relies on conditional polling.
State machine
| Status | Allowed transitions |
|---|---|
Backlog | Todo · Archived |
Todo | InProgress · Backlog · Blocked |
InProgress | InReview · Todo · Blocked |
InReview | Done · InProgress · Blocked |
Blocked | Todo · InProgress |
Done | Archived · InReview |
Archived | (terminal) |
The transition table is a hard-coded whitelist and invalid jumps are rejected. Timestamps follow one rule set: entering Done sets CompletedAt and leaving clears it; entering Archived sets ArchivedAt and leaving clears it; entering Blocked keeps or sets BlockedReason and leaving clears it. Priority values are Low / Medium / High / Urgent.
Card model
| Group | Fields |
|---|---|
| Core | Id · Title · Description · Status · Priority · Assignee · Tags · DueDate · SortOrder · BlockedReason · CompletedAt · ArchivedAt |
| Configuration | ProjectId / ProjectName · AgentId / AgentName · AgentPrompt / AgentPromptName · WorkflowId / WorkflowName |
| Runs & collaboration | HasAutomation · LastRunStatus · LastRunId · CommentCount |
| Comments | KanbanTaskCommentDto: AuthorType · AuthorName · Content · RunId · CreatedAt |
| Runs | KanbanTaskRunDto: Kind · Trigger · Status · Input · Output · Error · WorkflowRunId · StartedAt · CompletedAt |
| Detail aggregate | KanbanTaskDetailDto = task + comments + runs + steps |
HasAutomation is true when a project agent, a workflow or a custom prompt is configured. AgentPrompt / AgentPromptName are only stored when no project agent is selected (AgentId empty) — that is the real difference between the "pick an agent from the project's .github" path and the "write your own prompt" path.
Endpoints
| Endpoint | Purpose |
|---|---|
GET /api/kanban-tasks | Board data: seven columns plus stats; the Archived column only returns tasks archived in the last 7 days |
POST /api/kanban-tasks · PUT /api/kanban-tasks/{id} | Create / update; a save landing in Todo attempts to trigger automation |
PUT /api/kanban-tasks/{id}/move | Drag between columns: validates the whitelist, maintains timestamps and triggers automation when entering Todo |
GET /api/kanban-tasks/{id}/detail | Detail aggregate (task + comments + runs + steps) |
POST /api/kanban-tasks/{id}/comments | Comment; in InReview / Blocked with comment-triggered automation enabled it pulls the task back to InProgress and re-runs with trigger Comment |
POST /api/kanban-tasks/{id}/rerun | Re-run; only allowed from Todo |
GET /api/kanban-tasks/{id} · DELETE /api/kanban-tasks/{id} | Fetch or delete a single card |
Automation and status progression
- Triggers live in three places: after create, after update when the status changed, and when moving into
Todo; comment loops and manual re-runs add two more. - Execution has exactly one gate: the task must be in
Todoand have automation configured; otherwise only the status changes. - A run record is written before execution (
Triggerrecords the source: create / move / comment / rerun) and the run produces steps and comments. - Status progression happens in the service layer; failures land in the run's
Errorand inLastRunStatuson the task.
InProgress only changes status; it does not start execution on its own.Detail enrichment and frontend refresh
| Item | Rule |
|---|---|
| Name enrichment | List and detail both fill ProjectName (managed project), AgentName (project agent), WorkflowName, LastRunStatus and CommentCount; the list uses batched dictionary lookups to avoid N+1 |
| Display fallback | Project row: projectId ? projectName ?? projectId; agent row: shows agentName ?? agentPromptName ?? agentId when either agentId or agentPromptName exists, otherwise "not set"; workflow row works the same way |
| Detail ordering | Steps by Sequence then creation time; runs newest first; comments oldest first |
| Polling condition | Both board and detail refetch every 3000ms while a run is Running / WaitingAnswer, or an automated task sits in Todo / InProgress; otherwise polling stops |
| Refresh channel | React Query polling with invalidation; there is no task-board specific SSE event |
Problems of the form "project and agent are configured but the detail page shows nothing" usually come from the display fallback: when the agent is picked from the project's .github, the persisted fields are AgentPrompt / AgentPromptName rather than AgentId, so checking only AgentId renders empty. The current implementation shows both sources (selecting a project loads that project's .github agents and writes the prompt fields back).
Stats and archive window
- Stats:
Total/Active/Done/Archived/Overdue/DueSoon. - The archived column keeps a 7-day window so the board cannot grow without bound; long-term history lives in run records and comments.
Practice notes
- Automation only triggers from
Todo: put the card in the todo column instead of dragging it straight to in-progress. - Comments are a loop, not a note: commenting in review or blocked carries context back into a re-run, which feeds feedback to the agent.
- Polling is conditional by design: a page that stops refreshing while nothing is running is behaving correctly.
- Project agent and custom prompt are mutually exclusive: selecting an agent means a hand-written prompt is not stored.