Skip to main content

task-handoff-check

Lint a task handoff (human or AI agent) for a missing owner, next step, blockers, done criteria or evidence before someone else picks it up.

license: MIT

Catch incomplete or contradictory task handoffs before someone else picks up the work. This small CLI and library checks an explicit JSON document for a clear owner, next action, blockers, completion criteria and evidence references.

A passing handoff is structurally consistent. It is not proof that its claims are true, that its references exist, or that anyone is authorized to act.

An open-source engineering utility from IT Custom Solution. Python 3.10+, no runtime dependencies, no network access, no commands executed. Version 0.1.0. MIT licensed.

Try it in one minute

git clone https://github.com/itcustomsolution/task-handoff-check.git
cd task-handoff-check
python -m task_handoff_check examples/in-progress.json
python -m task_handoff_check examples/complete.json --json
python -m task_handoff_check examples/invalid-complete.json --json

The first two checks pass. The third exits 1 because it claims complete while listing an open blocker. All examples, people and references are fictional; referenced paths deliberately need not exist. The tool never opens them.

After the first PyPI release: pipx install task-handoff-check (or pip install task-handoff-check).

To install from source in a virtual environment:

python -m venv .venv
. .venv/bin/activate
python -m pip install .
task-handoff-check --version

Installation uses setuptools as a build dependency. Running from the checkout needs only the Python standard library; no package registry release is required.

Handoff format

{
  "schema_version": 1,
  "objective": "Review the sample release notes",
  "owner": "Sample Maintainer",
  "status": "in_progress",
  "next_action": "Compare the notes with the sample changelog",
  "inputs": [
    {
      "label": "changelog",
      "path": "docs/changelog.md"
    }
  ],
  "evidence": [],
  "blockers": [],
  "completion_criteria": [
    {
      "description": "All sample changes reviewed",
      "met": false,
      "evidence": []
    }
  ],
  "updated_at": "2026-01-01T09:00:00Z"
}

All shown fields except updated_at are required. Unknown fields are errors.

Field Rule
schema_version Integer 1
objective, owner Meaningful nonempty text
status ready, in_progress, blocked, or complete
next_action Meaningful text for unfinished work; JSON null for complete
inputs, evidence Arrays of reference objects; may be empty
blockers Array of objects with description and owner; open blockers only
completion_criteria Nonempty array with description, boolean met, and evidence
updated_at Optional timestamp with seconds and timezone

A reference contains a meaningful label and exactly one path or url. Paths must be portable relative paths without traversal, control characters, reserved device names or Windows-reserved punctuation. URLs must be absolute HTTP(S) references without embedded credentials. Labels must be distinct within each array after Unicode NFC normalization and case folding.

Each criterion's evidence is an array of exact labels from the top-level evidence array. A met: true claim requires at least one such reference. Repeated evidence labels and repeated criterion descriptions are rejected. Reference content and accessibility are never checked.

blocked requires at least one open blocker and a concrete next action. Any open blocker requires blocked. complete requires all criteria to be met: true, no blockers and a null next_action. Other statuses may contain met criteria while further work remains. This is deliberately a small task state model; it is not a general workflow engine.

Blank text, control characters and exact placeholders such as TBD, todo, unknown, unassigned, n/a, none, - and ? are rejected. The tool does not understand free text semantically: an unhelpful sentence can still pass.

Optional, reproducible freshness checks

Freshness is never inferred from the machine's current time. Supply --as-of to compare timestamps; optionally specify an inclusive maximum age:

python -m task_handoff_check examples/in-progress.json \
  --as-of 2026-01-02T09:00:00Z --max-age-hours 24 --json

This example passes exactly at the 24-hour boundary. One second later it is stale. --max-age-hours must be finite, nonnegative and accompanied by --as-of. Supplying --as-of requires a valid updated_at and rejects a future update. --as-of alone compares chronology, without a maximum-age policy.

Accepted timestamps use YYYY-MM-DDTHH:MM:SS, optional 1–6 fractional digits, and Z or an explicit +HH:MM/-HH:MM offset. Leap seconds are unsupported. The timestamp remains an assertion from the document author.

Reports and exit codes

JSON reports contain ok, scope (handoff_consistency), freshness, and an issues array of code, location and message. freshness is one of not_evaluated, unavailable, timestamp_compared, future, within_limit or stale. Other schema errors can coexist with a valid timestamp comparison.

Exit Meaning
0 Consistency checks pass; or help/version displayed
1 Invalid input, inconsistent handoff or failed timestamp check
2 Invalid command-line usage or freshness options

Common issue codes: invalid_schema, unknown_field, missing_text, status_conflict, invalid_reference, duplicate_label, duplicate_criterion, incomplete_criterion, missing_evidence, unknown_evidence, invalid_timestamp, missing_timestamp, future_timestamp, stale, input_error. Messages are human-facing. --json validation reports use stdout; CLI usage errors use stderr.

Library

from task_handoff_check import load_handoff, validate_handoff

handoff = load_handoff("examples/complete.json")
report = validate_handoff(handoff)
assert report["ok"]
report = validate_handoff(
    handoff, as_of="2026-01-02T09:00:00Z", max_age_hours=24
)

validate_handoff returns issues for invalid documents and raises HandoffError for invalid freshness options. load_handoff raises HandoffError for input errors. Validation does not mutate the document.

Limits and development

Input must be a regular UTF-8 JSON file of at most 1 MiB; final-component symlinks, duplicate JSON keys and nonfinite numbers are rejected. Parent symlinks may be followed for the explicitly supplied input document. Use trusted local input locations; the input reader is not a filesystem sandbox.

The tool performs no authentication, authorization, link checking, ownership verification, work execution, scheduling, agent dispatch or compliance assessment. It never changes a task's state. See SECURITY.md.

python -m unittest discover -s tests -v

Tests use synthetic inputs and temporary directories. CI covers Python 3.10, 3.12 and 3.14 on Linux, plus Python 3.14 on macOS. See CONTRIBUTING.md.

Contributing

Issues and small pull requests are welcome; see CONTRIBUTING.md. Run python -m unittest discover -s tests before opening a PR. Release history is in CHANGELOG.md.

License

MIT, see LICENSE.

Metadata

Release files for task-handoff-check 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for task-handoff-check 0.1.0
File Size Uploaded
task_handoff_check-0.1.0.tar.gz 16.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for task-handoff-check 0.1.0
File Interpreter ABI Platform
task_handoff_check-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 27.4 kB

Release files / task_handoff_check-0.1.0.tar.gz

Download URL task_handoff_check-0.1.0.tar.gz
Size 16.0 kB
Tags Source
SHA-256 checksum
How to use checksums
03612a4d7a516eb089b0dd8159759d4c63dc9efd5766a6c6b8857164a5c21555
BLAKE2b-256 checksum
How to use checksums
0bf47bf3a7c412a4c3a5c6b8cb3fd7c41152e4b0af3f2590781b9de2e29ce6be
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release files / task_handoff_check-0.1.0-py3-none-any.whl

Download URL task_handoff_check-0.1.0-py3-none-any.whl
Size 11.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b579cc4a8a3adc9e3bf802b0d9669b29531f675d0a78534d6866cdb09ec8e034
BLAKE2b-256 checksum
How to use checksums
17abf4ed4378ff66d95dce0a3b3ab3d277b6ebafdaa000c589f884915f81b4ab
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page