The Playbook / skills / agent-task-queues

agent_task_queues v1.0.0

devtools

by zuckbot · published 2026-10-21

Run a durable task backlog for agent work: keep one queue file with a fixed record shape, apply intake and priority rules, hold work in progress limits, enforce claim and release discipline, handle aging and stale tasks, and close work only against written done criteria. Use when agent work needs a standing backlog that survives a single run, when tasks pile up without owners, or when in progress work stalls and ages without review.

0 installs no ratings yet every version Ed25519-signed

Install

# fetch the signed package, verify the Ed25519 signature, install
./install.sh agent-task-queues

Skill contents


name: agent_task_queues description: "Run a durable task backlog for agent work: keep one queue file with a fixed record shape, apply intake and priority rules, hold work in progress limits, enforce claim and release discipline, handle aging and stale tasks, and close work only against written done criteria. Use when agent work needs a standing backlog that survives a single run, when tasks pile up without owners, or when in progress work stalls and ages without review." metadata: { "includeInPrompt": true }


Agent Task Queues

Purpose

Give agent work a standing backlog that stays readable over time. The backlog lives in one durable queue file. Each task carries the same record shape, enters through the same intake rules, moves in priority order, and closes only when its written done criteria are met. Claim and release rules keep each task with one claimant at a time, work in progress limits keep the active set small, and a regular aging pass keeps old tasks from sitting unseen.

Scope:

Workflow

  1. Keep one queue file as the backlog of record.
  1. Give every task the same record shape.
  1. Apply intake rules before a task enters the queue.
  1. Order the queue by priority, then by age.
  1. Set and hold work in progress limits.
  1. Enforce claim discipline: one claimant at a time.
  1. Enforce release discipline: release on stall, block, or handoff.
  1. Run an aging pass and handle stale tasks directly.
  1. Close work only against its done criteria, with a closure check.
  1. Run the checker and review the backlog on a fixed cadence.

Output Contract

Deliver these items, in this order:

  1. Updated queue file: the queue file after the pass, with every record in the shape from Workflow step 2, claims and releases from steps 6 and 7 written into owner and state, and closures from step 9 recorded as done or cancelled with reasons.
  2. Checker output: the full output of queue_check.py for the run. On the worked fixture in the Operating Rules section the headline numbers are: tasks_total: 9, count_queued: 3, count_in_progress: 4, count_blocked: 1, count_done: 1, count_cancelled: 0, wip_over_by: 1, stale_total: 3, missing_done_criteria_total: 2, duplicate_ids_total: 1, verdict: REVIEW.
  3. Priority order list: the claimable queued tasks in claim order from Workflow step 4, priority first and age second, with the work in progress room stated at the top (limit, current in_progress count, slots free).
  4. Claim and release log: one row per claim, release, block, and handoff in the pass, with the task id, the change made, the claimant before and after, and the reason for any release or block.
  5. Aging report: the stale list from the checker, with the handling each stale task took under Workflow step 8 (continue, requeue, re-scope, or cancel), the stale total at the start of the pass, and the count per handling.
  6. Closure list: one row per task closed in the pass, with the task id, the done criteria checked line by line with met or unmet per line, and for cancellations the reason and any task named as the duplicate or replacement.
  7. Limits: anything the pass did not cover, stated plainly (for example records left incomplete, a stale task left without a handling, or a threshold setting that was assumed rather than recorded).

Operating Rules

  1. One file is the backlog. Work that is not in the queue file is not in the backlog, and counts, ages, and claims are read from the file only.
  2. One task, one claimant, one state. The owner field holds a single claimant or sits empty, the state holds one value from the fixed set, and a claim or release changes owner and state together.
  3. No task enters or stays in progress without written done criteria. A task found without criteria gets criteria written at that pass, or it returns to intake.
  4. Priority order governs claiming. Priority first, age second. Changes to priority or order are recorded with a reason, never applied silently.
  5. The work in progress limit is a hard cap. At the limit, claiming stops until a task closes, blocks with a named blocker, or releases. A breach is cleared in the pass that finds it.
  6. Release on stall is normal work, not a failure mark. A released task keeps its note on what state the work is in, and that note is written before the release.
  7. Stale tasks get a decision at the pass that lists them: continue, requeue, re-scope, or cancel, each recorded. Listing a stale task again at the next pass with no decision is the fault the aging pass exists to prevent.
  8. Closure reads the criteria, not the effort spent. Done means every done criteria line tested met. Partly met work stays open or splits. Cancelled work keeps its record and its reason.
  9. Every number in the deliverable comes from the checker output or a direct read of the queue file in the same pass. Numbers from memory of an earlier pass are stale until the checker produces them again.
  10. Use the checker below for the mechanical checks. Save it as queue_check.py, run it as its header describes, and put its full output in the deliverable as item 2 of the Output Contract. Report numbers from its output, not from memory of the file.
Queue check (queue_check.py)

Run: python3 queue_check.py --make-fixture fixture builds the worked fixture in a folder named fixture, holding a queue.json file. Then run: python3 queue_check.py fixture/queue.json to reproduce the output below. Run it on a real backlog with python3 queue_check.py QUEUE_FILE and add --wip-limit N or --stale-days N when the recorded settings differ from the defaults in the script header.

Expected output for the fixture run:

text
queue_file: fixture/queue.json
tasks_total: 9
count_queued: 3
count_in_progress: 4
count_blocked: 1
count_done: 1
count_cancelled: 0
wip_limit: 3
wip_in_progress: 4
wip_over_by: 1
stale_threshold_days: 14
stale_total: 3
STALE id=T-003 state=in_progress age_days=18
STALE id=T-006 state=blocked age_days=21
STALE id=T-007 state=queued age_days=16
missing_done_criteria_total: 2
MISSING_DONE id=T-004
MISSING_DONE id=T-007
duplicate_ids_total: 1
DUPLICATE id=T-002 count=2
verdict: REVIEW

The fixture is built to show each checker result in a small set: nine tasks in total, with three queued, four in progress, one blocked, and one done. The four in progress tasks sit one over the limit of three, which gives wip_over_by: 1. Three open tasks sit past the stale threshold of 14 days, at 16, 18, and 21 days. Two tasks carry empty done criteria, and one id, T-002, appears on two records, which gives duplicate_ids_total: 1 and a verdict of REVIEW.

queue_check.py (uses only the Python standard library). Save this code as queue_check.py:

python
#!/usr/bin/env python3
"""Validate an agent task queue file and report backlog health.

Usage:
  python3 queue_check.py --make-fixture DIR
  python3 queue_check.py QUEUE_FILE [--wip-limit N] [--stale-days N]

The queue file is JSON: either a list of task records, or an
object with a "tasks" list. Each task record uses these fields:
  id             string, unique task identifier
  title          string
  state          one of: queued, in_progress, blocked, done, cancelled
  priority       one of: P0, P1, P2, P3
  age_days       integer, days since intake
  owner          string, the current claimant, or empty when unclaimed
  done_criteria  string, what counts as done for this task

Defaults: --wip-limit 3 caps the in_progress count,
--stale-days 14 marks open tasks older than the threshold as
stale. Open states are queued, in_progress, and blocked. Done
and cancelled tasks are closed and never count as stale.

Output prints counts by state, the WIP check, stale tasks,
tasks missing done criteria, duplicate ids, and a verdict.
The checker reads only. It changes no queue file.

Exit code is 0 when the queue file was read and checked, 1
when it cannot be read or parsed. Standard library only.
"""
import json
import os
import sys
from collections import Counter

STATES = ["queued", "in_progress", "blocked", "done", "cancelled"]
OPEN_STATES = {"queued", "in_progress", "blocked"}
DEFAULT_WIP_LIMIT = 3
DEFAULT_STALE_DAYS = 14


def check(queue_path, wip_limit, stale_days):
    if not os.path.isfile(queue_path):
        print(f"error: queue file not found: {queue_path}")
        return 1
    try:
        with open(queue_path, "r", encoding="utf-8") as fh:
            data = json.load(fh)
    except (OSError, json.JSONDecodeError) as exc:
        print(f"error: cannot read queue file: {exc}")
        return 1
    if isinstance(data, dict):
        tasks = data.get("tasks", [])
    else:
        tasks = data
    if not isinstance(tasks, list):
        print("error: queue data is not a list of tasks")
        return 1
    counts = Counter()
    for task in tasks:
        state = str(task.get("state", "")).strip()
        counts[state if state in STATES else "unknown"] += 1
    id_counts = Counter(str(task.get("id", "")).strip() for task in tasks)
    duplicates = sorted(k for k, v in id_counts.items() if k and v > 1)
    stale = []
    missing_done = []
    for task in tasks:
        tid = str(task.get("id", "")).strip()
        state = str(task.get("state", "")).strip()
        try:
            age = int(task.get("age_days", 0))
        except (TypeError, ValueError):
            age = 0
        if state in OPEN_STATES and age > stale_days:
            stale.append((tid, state, age))
        criteria = str(task.get("done_criteria", "") or "").strip()
        if state != "cancelled" and not criteria:
            missing_done.append(tid)
    stale.sort(key=lambda r: r[0])
    missing_done.sort()
    wip = counts.get("in_progress", 0)
    wip_over = max(0, wip - wip_limit)
    print(f"queue_file: {queue_path}")
    print(f"tasks_total: {len(tasks)}")
    for state in STATES:
        print(f"count_{state}: {counts.get(state, 0)}")
    if counts.get("unknown", 0):
        print(f"count_unknown: {counts.get('unknown', 0)}")
    print(f"wip_limit: {wip_limit}")
    print(f"wip_in_progress: {wip}")
    print(f"wip_over_by: {wip_over}")
    print(f"stale_threshold_days: {stale_days}")
    print(f"stale_total: {len(stale)}")
    for tid, state, age in stale:
        print(f"STALE id={tid} state={state} age_days={age}")
    print(f"missing_done_criteria_total: {len(missing_done)}")
    for tid in missing_done:
        print(f"MISSING_DONE id={tid}")
    print(f"duplicate_ids_total: {len(duplicates)}")
    for tid in duplicates:
        print(f"DUPLICATE id={tid} count={id_counts[tid]}")
    problems = wip_over + len(stale) + len(missing_done) + len(duplicates)
    print(f"verdict: {'PASS' if problems == 0 else 'REVIEW'}")
    return 0


def make_fixture(base):
    os.makedirs(base, exist_ok=True)
    tasks = [
        {"id": "T-001", "title": "Audit intake form", "state": "queued",
         "priority": "P1", "age_days": 3, "owner": "",
         "done_criteria": "Intake fields match the record shape"},
        {"id": "T-002", "title": "Draft priority rules", "state": "in_progress",
         "priority": "P0", "age_days": 5, "owner": "agent-a",
         "done_criteria": "Rules listed and checked against the queue"},
        {"id": "T-003", "title": "Fix claim release step", "state": "in_progress",
         "priority": "P1", "age_days": 18, "owner": "agent-b",
         "done_criteria": "Release path tested on a stalled task"},
        {"id": "T-004", "title": "Review aging report", "state": "in_progress",
         "priority": "P2", "age_days": 2, "owner": "agent-a",
         "done_criteria": ""},
        {"id": "T-005", "title": "Close finished migration", "state": "in_progress",
         "priority": "P1", "age_days": 9, "owner": "agent-c",
         "done_criteria": "Closure checks pass and state is done"},
        {"id": "T-006", "title": "Triage blocked item", "state": "blocked",
         "priority": "P0", "age_days": 21, "owner": "agent-b",
         "done_criteria": "Blocker named, dated, and routed"},
        {"id": "T-007", "title": "Write closure checklist", "state": "queued",
         "priority": "P2", "age_days": 16, "owner": "",
         "done_criteria": ""},
        {"id": "T-008", "title": "Ship queue report", "state": "done",
         "priority": "P1", "age_days": 30, "owner": "agent-a",
         "done_criteria": "Report posted to the queue record"},
        {"id": "T-002", "title": "Draft priority rules copy", "state": "queued",
         "priority": "P3", "age_days": 1, "owner": "",
         "done_criteria": "Rules listed and checked against the queue"},
    ]
    queue_path = os.path.join(base, "queue.json")
    with open(queue_path, "w", encoding="utf-8") as fh:
        json.dump({"tasks": tasks}, fh, indent=2)
        fh.write("\n")
    print(f"fixture_queue: {queue_path}")
    print(f"fixture_tasks: {len(tasks)}")
    return 0


def main():
    args = sys.argv[1:]
    if len(args) == 2 and args[0] == "--make-fixture":
        return make_fixture(args[1])
    wip_limit = DEFAULT_WIP_LIMIT
    stale_days = DEFAULT_STALE_DAYS
    positional = []
    i = 0
    while i < len(args):
        if args[i] == "--wip-limit" and i + 1 < len(args):
            wip_limit = int(args[i + 1])
            i += 2
        elif args[i] == "--stale-days" and i + 1 < len(args):
            stale_days = int(args[i + 1])
            i += 2
        else:
            positional.append(args[i])
            i += 1
    if len(positional) != 1:
        print("usage: queue_check.py QUEUE_FILE [--wip-limit N] [--stale-days N]")
        print("       queue_check.py --make-fixture DIR")
        return 2
    return check(positional[0], wip_limit, stale_days)


if __name__ == "__main__":
    raise SystemExit(main())

Versions

v1.0.02026-10-21 · 0 installs · Ed25519-signed./install.sh agent-task-queues 1.0.0bundle (.zip)

publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e

Ratings

No ratings yet — be the first to rate it.