Files
vitya 0016c458d1 feat(task-loop): new skill for in-session board draining v0.1.0
Interactive claim -> work -> close -> repeat loop in the current session;
no daemon, no spawned claude, no busy-poll. Coordinates with using-tasks
(.tasks/.lock, session_break gate, 10-min claim TTL -> tasks_heartbeat) and
project-discipline (push Rule 4, sensitive artifacts).

TDD (writing-skills RED-GREEN-REFACTOR):
- RED: 2 clean-context subagents revealed gaps A-E (claim scope, missed
  session_break + .lock, consult-gate boundary, paused-vs-blocked).
- GREEN: SKILL.md addresses all five; compliance subagent B clean.
- REFACTOR: closed CronCreate loophole in long-watch (separate session =
  daemon); mandate ScheduleWakeup on this session. Re-test passed.

Semver: 0.1.0 (initial).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 17:33:35 +03:00

8.4 KiB

name, version, description
name version description
task-loop 0.1.0 Use when the user asks you to work the task board yourself, in this session, one task after another — «поработай очередь», «прогони доску», «бери задачи по очереди», «работай пока не скажу стоп», «work the queue», «drain the board», «keep working tasks until I say stop». Does NOT apply to delegating work to another agent/project (→ delegate-task), to one named task you already know (→ using-tasks), or to configuring the background poller.

task-loop

Work the board in this session: claim the next ready task, do it, close it, claim the next — until the queue is empty or the user says stop.

Core principle: an interactive loop, not a daemon. You stay in the chair. Empty queue → stop and report, never spin a wait-timer. No subprocess, no CronCreate (that schedules a separate session — exactly the daemon you're replacing), no short ScheduleWakeup poll — those are the unattended poller's job, not yours here.

REQUIRED SUB-SKILL: using-tasks owns the board, the .tasks/.lock session lock, the session_break gate, and the pre-close coverage check. This skill drives the loop through those rules — it does not replace them. REQUIRED SUB-SKILL: project-discipline — commit/push gate (Rule 4) and sensitive-artifact handling apply to every task you touch.

When to use

Activates: «поработай очередь», «прогони доску», «бери задачи по очереди», «работай пока не скажу стоп», «work the queue», «drain the board», «keep working tasks until I say stop».

Does NOT apply:

  • Delegating work to another agent/project → delegate-task.
  • One specific task you already named → using-tasks (switch/resume that task).
  • Setting up / debugging the background poller or agent-runner → that is infra, not this loop.

The loop

Run this cycle. One task at a time.

digraph task_loop {
  rankdir=TB;
  claim   [shape=box,   label="tasks_claim_next\n(current project, confirm=true)"];
  empty   [shape=diamond,label="task returned?"];
  stop    [shape=box,   label="STOP — report board drained"];
  work    [shape=box,   label="do the work this session"];
  done    [shape=diamond,label="completed?"];
  park    [shape=box,   label="park: blocked (external) | paused (resumable)"];
  gate    [shape=diamond,label="consult_policy = human-only/strict-human?"];
  consult [shape=box,   label="STOP before close/commit — consult user"];
  close   [shape=box,   label="pre-close coverage check → tasks_close"];
  brk     [shape=diamond,label="session_break marker on closed task?"];
  boundary[shape=box,   label="STOP — print SESSION BOUNDARY"];

  claim -> empty;
  empty -> stop [label="no ready tasks"];
  empty -> work [label="yes"];
  work -> done;
  done -> park [label="no"];
  done -> gate [label="yes"];
  gate -> consult [label="yes"];
  gate -> close  [label="auto"];
  close -> brk;
  brk -> boundary [label="yes"];
  brk -> claim    [label="no"];
  park -> claim;
}
  1. Claim the next ready task with tasks_claim_next(claimer_identity, filter, confirm=true).
    • claimer_identity = <machine>:<runtime>:<session> (e.g. DESKTOP-NSEF0UK:claude-opus:<session>).
    • filter.project = the current project (qualified <owner>/<repo>) by default. Only widen to other projects when the user explicitly asks ("прогони все доски" / passes a project list).
    • The server already excludes weight: needs-human and anti-self-review tasks — you will never claim those.
  2. No task returned → the queue is drained. STOP and report (see Empty queue). Do not poll.
  3. Do the work this session. All your tools are available. Read the task description and per-task <slug>.md. Set the task active if it isn't already.
  4. Honor the gate before the irreversible step. Use the consult_policy returned by the claim:
    • auto → full autopilot through close.
    • human-only / strict-human → do the work, then STOP before tasks_close / commit and consult the user. Don't barrel through.
    • Push is never automatic regardless of policy — project-discipline Rule 4 (commit freely, push only on an explicit per-session grant).
  5. Close with the using-tasks pre-close coverage check, then tasks_close(target_project, slug, confirm=true, note=…).
  6. session_break gate. After the close, before claiming the next task, honor the using-tasks session_break check: if the closed task carries the marker → print the 🔚 SESSION BOUNDARY line and STOP (do not claim next). Otherwise → back to step 1.

When a task can't be finished

Never leave a claimed task hanging (its claim expires in 10 min and it returns as a zombie), and never tasks_close unfinished work (that lies to the board).

  • External / unresolvable blocker discovered mid-task (missing upstream, needs a human decision, scope change) → tasks_update(slug, status="blocked", blocker="<concrete fact + what's needed>"). Roll back partial work that would break the build. Then continue the loop (the blocker is isolated; the next claim won't return this task).
  • Interrupted or resumable by you (you ran out of budget, the user stops you mid-task) → tasks_update(slug, status="paused", where_stopped=…, next_action=…).

A single failing task does not stop the loop — park it and move to the next.

Empty queue & stopping

The loop ends on the first of:

  • Empty queuetasks_claim_next returns no ready task → stop, report what you closed/parked, and wait for the user. Do not ScheduleWakeup, CronCreate, or sleep-poll for new tasks.
  • Explicit user signal — «стоп», «хватит», «отбой». Park any in-flight claimed task (paused) before stopping.
  • Budgetbudget.remaining() near zero → park the current task (paused) and report.

Long-running watch (opt-in only). If the user explicitly says «работай пока не скажу стоп» and wants you to keep checking for newly-arrived tasks, use ScheduleWakeup — it re-invokes this session — with a long interval (≥1200 s). Never CronCreate even here: it starts a separate scheduled session, i.e. the daemon this skill exists to avoid. And never a short poll. Default is still stop-on-empty; only arm a wakeup on an explicit standing request.

Heartbeat

A claim lives 10 minutes. If a single task will take longer than ~8 minutes, call tasks_heartbeat(slug, claim_token) periodically to keep the claim alive. Short tasks need no heartbeat. (The in-session .tasks/.lock is a separate 2-hour lock owned by using-tasks session start/end — don't manage it from the loop.)

What NOT to do

  • No daemon, no CronCreate, no spawned claude, no subprocess to "run the queue" — CronCreate starts a separate scheduled session; the whole point is you do it in this session.
  • No busy-poll on empty — empty queue is a natural stop, not a wait-loop. A short ScheduleWakeup loop burns tokens for nothing. The only exception is the explicit long-watch opt-in above (a single ≥1200 s ScheduleWakeup, never CronCreate).
  • Don't widen scope silently — default to the current project; claim other boards only when the user asks.
  • Don't tasks_close unfinished work and don't leave a task claimed when blocked — park it (blocked/paused).
  • Don't skip the session_break gate between tasks — a milestone/domain-switch marker means stop, even if more tasks are ready.
  • Don't autopilot through a human-only/strict-human task's close/commit, and never auto-push — consult first.
  • Don't blind-retry a task that failed for an external reason — diagnose once, record the blocker, move on.

Red flags — STOP

  • "I'll set a timer to check for new tasks" → no. Stop on empty; report.
  • "I'll spawn a background worker to drain faster" → no. One task at a time, this session.
  • "User said work-until-stop, I'll CronCreate a recurring run" → no. CronCreate is a separate scheduled session = the daemon. Long-watch uses a single long ScheduleWakeup on this session.
  • "It's sensitive but consult_policy says auto, I'll just commit" → push still needs a grant; sensitive close still respects the gate.
  • "The task isn't done but I'll close it and note it" → never close unfinished. Park it.