feat(tasks): autonomous task loop — assign → dispatch → drive → reconcile (#138)#204
Merged
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When the assignee agent posts into its task sub-channel, touch last_progress_at. Non-assignee posts are silent no-ops (WHERE clause self-restricts via assignee_agent_id match). Adds task_lease_is_stale helper for the reconcile loop to detect stalled tasks. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds `src/agent/reconcile_tasks.rs` with `spawn_task_reconciler` that ticks every 60 s, calls `list_stale_assigned_tasks(600)`, and for each stale task either re-dispatches (attempts < 2) or escalates, publishing the returned StreamEvents through the EventBus so the agent is woken. Wired into `serve.rs` after `spawn_trace_writer`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `blocked` to TaskStatus union, NEXT_STATUS, ADVANCE_LABEL, STATUS_LABEL
in TaskDetail and TaskEventMessage; seed groupTasksByStatus with a blocked
bucket so blocked tasks are never silently dropped
- Add `assigneeName: string | null` and `attempts: number` to TaskInfo DTO
- Add `blocked` column to TasksPanel board (styled with --color-destructive)
- Add assignTask() client: POST /api/conversations/{id}/tasks/{n}/assign
- TaskDetailView: render assignee picker (select populated from listAgents()),
show "assigned to <name>" when set, show "attempt N" chip when attempts > 1,
render blocked status chip via data-status="blocked" + CSS warning token
- Fix UpdateTaskStatusRequest to reference TaskStatus (was a hard-coded union)
- Vitest: 19 tests in TaskDetail covering new paths; all 132 tests green
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…us change; assign-on-create test; unify blocked token Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tus call Dogfood found the assigned agent did the work but couldn't close the task: the board lives on the parent channel while the agent is sole member of the sub-channel, so its list_tasks/update_task_status hit the empty sub-channel board. The internal task routes are claimer-gated (no parent-membership needed), so naming the board channel + the explicit update_task_status call in the directive lets the agent drive status to completion.
CI Format check failed on the merge commit; rustfmt-only, no behavior change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- CHANGELOG: autonomous task loop (#138) - VERSION: 0.16.1 -> 0.17.0 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A human assigns a task to an agent and the agent autonomously wakes, works it, and drives its status — Linear-style — with the status staying truthful if the agent dies or stalls. Closes the upstream half of #138 (a fired task previously woke/assigned nobody).
The loop
assign_task(one tx): setsassignee_agent_id+ authoritative claim + makes the assignee the sole agent member of the task sub-channel + writes a directive (a normal message authored by the human assigner — the agent inbox excludes its own messages, so the author must differ from the assignee).update_task_statusMCP tool. Newblockedstatus lets an agent self-report a blocker (cooperative resign); reconcile also sets it.tasks.last_progress_at, renewed when the assignee posts in its sub-channel): stale → re-dispatch (≤2 attempts) → elseblocked(claim cleared, assignee kept). Lease, not process-liveness (process-existence ≠ "working this task").New
taskscolumnsassignee_agent_id, assigned_by_id, attempts, last_progress_at+idx_tasks_sub_channel;TaskStatus::Blocked;POST /api/conversations/{id}/tasks/{n}/assign(+assigneeIdon create); UI assignee picker + assignee/attempt/blocked display.Design + review
Brainstormed → spec → plan, then two codex review passes (RETHINK → SHIP-WITH-FIXES) folded in (lease over process-liveness; atomic dispatch; sole-agent-member targeting; reuse existing restart-replay; own-message author rule). Spec/plan are local under
docs/superpowers/.Verification
cargo clippy --all-targets -- -D warnings; Rust tests (server 86, e2e 10, store::tasks 16); UItscclean; vitest 135.in_reviewby its ownupdate_task_statuscall, zero human pokes. The first run exposed a real gap (the directive didn't name which channel held the board) — fixed so the directive names the board channel + the exact tool call.Deferred (P2)
Inbox
decisionon escalation; agent-to-agent delegation; process-liveness fast-path; hung-agent cancel/restart. A substantial code-task dogfood additionally needs the agent to have a real repo working directory.🤖 Generated with Claude Code