taskproof
English · 简体中文
A conveyor belt for AI coding agents.
It does not decide what to do. It guarantees that whatever gets dispatched is independently verified and recorded — so you never have to trust an agent's own claim that it finished.
A card is dispatched. The agent says it finished. The record says it changed nothing — the acceptance command is what decides.
Status: stage 1 is usable, and the stage-2 desktop console is here. The
engine, CLI, a browser board, a local read-only REST API, and an Electron console
in desktop/ are implemented. See docs/DESIGN.md for the
full design and the decision log, and
docs/ARCHITECTURE.md for how each mechanism works today,
with diagrams.
Install
Two ways in: the desktop console if you do not write code, the command line if you do.
Desktop console
Download the installer for your platform from the GitHub Releases page:
- macOS arm64 (Apple silicon) —
.dmg - Windows x64 —
.exe - Linux x64 —
.AppImage/.deb
The installers are not signed and not notarised:
- macOS is arm64 only (no Intel or Rosetta build): on first launch,
right-click the app → Open, or run
xattr -d com.apple.quarantine /Applications/Taskproof.app. - Windows 10+: SmartScreen shows a warning on first launch.
Minimum versions are macOS 11+, Windows 10+, or a current mainstream Linux
distribution. Each installer ships its own Python runtime, so there is
nothing to pip install — but it does need a system git for worktrees and
the protected-path gate. Without git the settings page says so, and everything
else keeps working.
Command line
pipx install taskproof
uv tool install taskproof does the same thing. Python 3.11+, and the package
is pure standard-library Python — there is no dependency tree to resolve.
The latest development version, straight from the repository:
pipx install git+https://github.com/HaoHaoP/taskproof
Offline, or pinned to one build: download the wheel (or the sdist) from the Releases page and install the file. Its name carries the version, so take it from the release you downloaded — there is no unpinned wheel name to copy.
pipx install ./taskproof-<version>-py3-none-any.whl
Or run from a source checkout:
pip install -e .
Run a task
taskproof init
taskproof register /absolute/path/to/your/repo
taskproof run <project> "fix the failing test"
run is the whole lifecycle in one line: it dispatches the card to a
pluggable adapter (codex / claude / gemini / opencode, or custom:<cmd>), then
independently runs the acceptance command from the registry once the agent
exits, and finally records every step in the audit ledger. The agent's own
"done" is never the verdict.
taskproof tasks lists what is in flight; taskproof show <id> and
taskproof log <id> read one card back.
The desktop console shares the same ledger. On first launch it creates
~/.taskproof as an empty workspace — a [defaults] table and a commented
example, with no sample projects and no sample tasks — and the board's empty
state points you at the Projects page (or taskproof register <path>). Because
the console and the CLI read the same ~/.taskproof, either one sees the other's
cards.
docs/WALKTHROUGH.md runs this end to end on a throwaway
example — including the case where the agent reports success and the acceptance
command disagrees.
Dispatching work
The usage this project is extracted from is a discipline for the agent that hands out the cards, and it now ships in the repository as a skill:
skills/taskproof-dispatch/SKILL.md
skills/taskproof-dispatch/SKILL.zh-CN.md
It is the operating manual for an AI coding agent — the orchestrating one
that plans the work, registers the repositories, writes each card, and dispatches
it to a worker. Taskproof itself does not plan anything (see Non-goals); this
skill is the missing half: how to drive the tool without the failure modes the
hard way. Both languages ship: SKILL.md (English) and SKILL.zh-CN.md
(简体中文).
The rules in it are not invented; each is a lesson from breaking something.
pkill -f is the classic one, for instance: a card is passed as process argv, so
the pattern also matches the dispatcher itself and kills the run — exactly the
kind of practical rule the skill exists to record. It turns the
lessons of this build into steps you can copy: register a project,
put a card in a file and pass it with $(cat …), fire it with an explicit
--adapter and --timeout, then read state back with taskproof tasks --all
and taskproof log <id>. Above all it pins four things that were learned by
breaking them:
- Serialisation is a property of the group, not a habit. Different groups are meant to run in parallel up to the global cap; the only reason to serialise is "same group and overlapping files".
- The real registry is never a test target. UI verification runs against a
throwaway profile and workspace under
/tmp; a worker that deletes against the live~/.taskproof/projects.tomltakes a user's project with it. - Never
pkill -f. A card is passed as process argv, so its text is in the dispatcher's own command line —pkill -fmatches the dispatcher and kills the run (exit -15, no task, empty log). Kill by PID. - Review is not a self-report. The main agent reads the acceptance result the
machine already produced and asserts the dimensions it cannot see; re-running the
command is for the three exceptions only —
SKIPPED, a coverage gap, a green that smells false. "The worker said done" is not evidence.
The problem
Every orchestration project on GitHub competes on how many agents it can run, how many CLIs it supports, and how pretty the UI is. Almost none of them talk about the thing that actually bites you in daily use:
Agents lie.
Real incidents from the workflow this project is extracted from:
| What the agent said | What actually happened |
|---|---|
| "Verified in browser, screenshots attached" | Event stream contained zero browser activity |
| (silently, beyond scope) | Attempted an HTTP login against a local app using a guessed weak credential |
| Task complete, result file written | The JSON had been wrapped in a markdown fence, so the structured parse failed and the run looked like it did nothing |
| Task complete | The acceptance command failed when run independently |
None of these are exotic. They are the normal failure mode of handing file-writing work to an autonomous process.
What taskproof is
A small, mechanical dispatch layer that wraps every agent invocation in discipline:
- Independent verification — the agent's self-report never counts as success. The acceptance command from the project registry is run afterwards, by taskproof.
- Protected paths — a registry entry can declare paths an agent must not touch.
- Structured result contract — a defined output shape, with documented degradation for CLIs that cannot produce it.
- Append-only audit trail — every dispatch, verification, failure and retry is recorded, so "what happened yesterday" is a query, not a memory exercise.
- Meaningful exit codes — callers can distinguish "the agent failed" from "the agent succeeded but verification failed".
- No LLM inside — taskproof does not plan, decompose, or decide. Any agent (or a human, or a script) can drive it.
Shape
taskproof run <project> "<task>" # dispatch, then verify, then record
taskproof wait <id> # block until a card reaches a terminal state
taskproof tasks / show / log # inspect state
taskproof board # serve the board and open a browser (blocks)
taskproof board --serve 8788 # serve the board only, no browser (blocks)
taskproof api --port 8787 # local read-only REST API for clients
- Python, standard library only,
pipx install-able - SQLite for state (transactional claims, not lock files)
- JSONL audit stream, write-only, rotated monthly
- Pluggable adapters (codex / claude / gemini / opencode + any command)
The concurrency cap
The global cap is workspace-level and is always reported as a value plus where it came from, never a bare number. It has three sources, in precedence order:
run --cap N— a one-off for this dispatch only, never persisted;[defaults] concurrencyin the registry;- auto-detected from the machine.
The auto fallback is deliberately conservative — one slot is a whole extra coding agent:
cap = clamp(2, cores // 4, 6) # at most one slot per four cores
if RAM < 8 GiB: cap = 2 # a small box cannot afford cores // 4
if the core count is unknown: cap = 3
Inspect the effective value and its source:
taskproof config --show
Change the persisted value, comment-preserving:
taskproof config --concurrency N
run --cap N overrides it for one dispatch only. Lowering the cap is not
retroactive: it never touches a card already running — the CLI prints the cards
that now exceed the new cap and leaves the decision to you.
Terminal states and the two gates
Two terminal states are deliberately different:
blocked— a boundary breach: the run touched a path the registry forbids. The acceptance may even have passed; the gate is what stopped it.failed— the acceptance ran and went red, or the adapter itself failed; the work went wrong.
Both are cleared by accept, which is a human registration, not a fresh verdict:
taskproof accept <id> # blocked: a note is optional
taskproof accept <id> --note "why" # failed: a note is required
accept never re-runs acceptance and never rewrites the recorded verdict —
the run's own facts stay authoritative. Closing a failed card requires a note,
because that is a human sign-off and the reason has to survive in the ledger.
taskproof rerun <id> starts a fresh card from a terminal one and links the two
both ways (rerun_of / rerun_as).
On the board, blocked gets its own Needs review column, kept out of the
failure column.
The desktop console
desktop/ is the stage-2 console: an Electron + Vue 3 app (Element Plus,
UnoCSS, Pinia, vue-i18n). It is the same renderer taskproof board serves in a
browser — one rendering layer with two hosts. It is bilingual — zh-CN and en, following the system locale with an
English fallback.
The board — the run the agent called done sits under not passing; the run that really changed the file sits under done.
The process model is the interesting part. The app spawns the local service
itself — taskproof api --port 0 — reads the bound port from the child's stdout,
and terminates the child on exit. Data comes from that loopback, read-only
REST API; nothing leaves 127.0.0.1.
The REST surface is read-only. Every mutation lives in the CLI — registering a project, editing its registry entry, dispatching, cancelling, accepting and removing. The API itself only serves reads: it mints no session token and exposes no write endpoint, so a write-shaped request to an old path is a plain 404.
What the console does today:
- Columns — running / verifying / done / cancelled / needs review / not
passing. A column header can be hidden with the
?hide=query parameter. - Every column's display cap is yours to set, in the column's own header
(
- 10 +, five choices: 5 / 10 / 20 / 50 / All). The defaults are the board you already know -- done and cancelled fold to the ten most recent, the live columns stay unfolded. To show a whole column, choose All; there is no separate expand switch. - A "acceptance passed" badge on a blocked card whose acceptance went green.
- Writes are CLI-only. The console is a viewer over the read-only API:
dispatch, accept and registry edits all go through
taskproof, never the UI. - A settings page that shows the effective cap and where it came from, plus
the resolved launcher (bundled runtime /
taskproofon PATH / a custom command). - A tray icon, a Dock badge (the not-passing count) and a launch-at-login switch.
Registry
Everything taskproof may dispatch is declared in one TOML file in the workspace
(~/.taskproof/projects.toml) in two layers: a project (the repository and
its default path) and one or more taskgroups (the lanes it dispatches onto,
each with its own acceptance command and lock).
A lane may also declare a workspace of its own (workspace = "worktree"):
one git worktree beside the repository, created the first time a card actually
fires into that lane and reused from then on, with dependency directories
symlinked in from the main checkout (link = [...]) so nothing is reinstalled.
taskproof workspaces lists them; taskproof workspace-rm <lane> prints the
evidence and deletes one. --worktree remains the one-off. The fields are in
docs/REGISTRY.md.
[[project]]
id = "my-app"
path = "/absolute/path/to/my-app"
[[taskgroup]]
id = "my-app"
project = "my-app"
verify = "npm run build" # run by taskproof, after the agent exits
verify_kind = "build" # check | build | none
A flat, older [[project]] block that carries the acceptance command still
works unchanged: it reads as a project plus a same-named lane. taskproof register <path> appends a [[taskgroup]] (add --project to hang it off an
existing project). See
examples/projects.example.toml for a
commented starting point, and docs/REGISTRY.md for the
field reference and the list of hard errors.
The board's left rail groups by project — one row per project, with the lanes it owns shown on hover — and each task card names the lane it ran on.
Safety and hygiene
- Protected paths are fingerprinted, not just diffed. Forbidden paths are
snapshotted before and after a run, so a change inside
.git/or an ignored build/dependency directory is caught too, not only whatgit statushappens to report. A tree larger than 20000 entries is sampled, and the audit event recordssnapshot_truncated: true. - An asset scan runs before every push (
tools/scan_assets.py, wired throughcore.hooksPath). It ships generic shape rules only, so the scanner is never a no-op. - The organisation-specific words live outside the repository
(
~/.taskproof/asset-patterns.txt, or$TASKPROOF_ASSET_PATTERNS). A scanner that embedded the names it protects would leak exactly those names.
The enum contract
Status words, verify_kind values and exit-code meanings are one generated
artefact — contract/enums.json — produced from the
Python constants by tools/gen_contract.py. The TypeScript side imports it
instead of re-typing it; a status that exists in Python but not in the frontend
is a task that silently vanishes from the board. CI runs the check, so the two
languages cannot drift:
PYTHONPATH=src python3 tools/gen_contract.py --check
Development
Run the CLI from a source checkout:
pip install -e .
taskproof --version
Or, without installing:
PYTHONPATH=src python3 -m taskproof doctor
Two test suites, both green at the time of writing:
- Python —
PYTHONPATH=src python3 -m unittest discover -s tests(448 tests). - Desktop —
cd desktop && npm install && npm test(258 tests).npm run buildruns the type-check and a production build of all three targets.
CI runs three checks: the asset scan (tools/scan_assets.py), the enum-contract
consistency check (PYTHONPATH=src python3 tools/gen_contract.py --check), and
the desktop build + tests.
The animation at the top of this page is reproducible: tools/demo/ holds the
throwaway /tmp workspace builder and the vhs tape.
Known limitations
- Unsigned and not notarised. macOS shows a Gatekeeper prompt on first launch (right-click → Open, or clear the quarantine bit); Windows shows a SmartScreen warning.
- macOS is arm64 only. There is no Intel build and no Rosetta path.
- The gate needs
git. Without a systemgit, worktrees and the protected-path gate are unavailable; the settings page says so and everything else runs as usual. - The board fetches at most 2000 rows at a time. At that ceiling it shows "fetch limit reached" rather than implying it has everything.
- Windows and Linux installers are built by CI's native runners. A local machine can only build the macOS package.
Documentation
The docs ship in pairs, English and Simplified Chinese:
The dispatch discipline is a skill, in skills/taskproof-dispatch/SKILL.md (English) and skills/taskproof-dispatch/SKILL.zh-CN.md (简体中文).
Icon
docs/assets/icon.png is a 256 px copy of the app icon, small enough to live in
the repository. The source was an AI-generated mark; the checkerboard that got
baked into the PNG was cut out, the artwork was laid on Apple's 824/1024 grid
(the mark occupies 824 of the 1024 px canvas so the rounded-corner margin is
correct), and sips produced the 256 px copy used at the top of this page.
Non-goals
- No built-in LLM orchestration
- No agent-to-agent conversation layer
- No code generation or editing of its own
- No automated
gitwrite operations
Exit codes
Exit codes are part of the contract for callers (scripts, agents, CI): they tell you why a run ended without parsing stdout.
| Code | Meaning |
|---|---|
| 0 | success |
| 2 | registry problem (unknown project, missing/malformed registry, duplicate id) |
| 64 | usage error (bad flags, missing path, unknown task or adapter) |
| 70 | adapter (agent) failure — the CLI failed to run, or its result could not be parsed |
| 71 | verification or artifact self-check failure (acceptance command failed) |
| 75 | concurrency limit reached (same-group busy, or the global cap is full) |
License
Apache-2.0. See LICENSE.
Metadata
Release files for taskproof 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| taskproof-0.4.0.tar.gz | 1.6 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| taskproof-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 3.1 MB
Release files / taskproof-0.4.0.tar.gz
| Download URL | taskproof-0.4.0.tar.gz |
|---|---|
| Size | 1.6 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2d59412a4ca835b6fa71bf2a173e615bb1263985f64a6dbea955030071b72a2b
|
|
BLAKE2b-256 checksum How to use checksums |
aca1f49916dbaf3e8cfba3d85b6452860c1550be26d400423198b397140f26bd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / taskproof-0.4.0-py3-none-any.whl
| Download URL | taskproof-0.4.0-py3-none-any.whl |
|---|---|
| Size | 1.5 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b15febd86f67718f537fa6a939a51f6d1fa4f9aba2bbfdf1dcac267e302bbd77
|
|
BLAKE2b-256 checksum How to use checksums |
1aeb5dfe280e4ea15cf4274eccab4cbb2da7fb790496c6112084caf1dc0891b8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency log