# keepwtach **Repository Path**: cloudrose/keepwtach ## Basic Information - **Project Name**: keepwtach - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-29 - **Last Updated**: 2026-10-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # keepwatch > **Keep work alive when the process that started it is not.** > A 300-line, zero-dependency ledger for agents that run unattended. [![ci](https://github.com/ReZeroStarting/keepwatch/actions/workflows/ci.yml/badge.svg)](https://github.com/ReZeroStarting/keepwatch/actions/workflows/ci.yml) [![pypi](https://img.shields.io/pypi/v/keepwatch.svg)](https://pypi.org/project/keepwatch/) [![python](https://img.shields.io/pypi/pyversions/keepwatch.svg)](https://pypi.org/project/keepwatch/) --- ## The problem An unattended pipeline does not fail loudly. It goes quiet: - a task sits in `pending/` and nobody claims it - a worker dies mid-task and the task is stuck in `processing/` forever - a worker finishes but never marks it done, so the next round re-dispatches it - the agent says "all tests pass" and you have nothing to check that against All four look identical from the outside: **nothing happened, and the logs are clean.** ## What it does Three folders, one ledger, four verbs. ``` /agents//pending/ tasks a worker can pick up /agents//processing/ tasks someone is holding (with a lease) /agents//done/ closed tasks, never deleted /audit/YYYY-MM.jsonl append-only record of everything ``` | Verb | Meaning | |---|---| | `check` | what can I pick up | | `claim` | take one, and hold a lease on it | | `done` | close it — **only if a passing `verify` record exists** | | `verify` | record a check result (`--rc 0`) against a task | | **`run`** | **claim → run a command → record its exit code → close or release** | | `doctor` | deterministic self-check, exit 0 when healthy | | `checkpoint` | write a resume note: what is stuck, what to run next | | `pipeline` | open or close the gate for new work (exit 14 when closed) | Plus `send`, `renew`, `release`, `reap`, `status`. ## The one command you actually need ```bash keepwatch run --who builder -- python -m pytest ``` That does the whole loop in one go: 1. claims the oldest pending task (or `--id `) 2. runs the command with inherited stdio, so you still see the output 3. writes `verify ... --rc ` into the ledger 4. `rc=0` → `done`; `rc!=0` → back to `pending` for another try 5. after `--max-tries` (default 3) failures → left in `processing` with a `suspect` record If the process is killed mid-command, nothing is lost: the lease expires and `reap` puts the task back. **You do not have to remember to write the ledger — the wrapper does it.** ## Why it matters `claim` starts a lease. If the worker dies, the lease expires and `reap` puts the task back with a trail (`tries`, `prev_owners`). No task is ever stranded, and no task is silently done twice. `done` refuses to close a task unless the ledger holds a `verify ... rc=0` for it. That is the whole point: **the model can say "it passed"; the ledger is what proves it.** ```console $ keepwatch done --who builder --id 20261023T083729-8c4fae keepwatch: refusing to close 20261023T083729-8c4fae: no verify record with rc=0. Run `verify --id ... --name --rc 0` first, or pass --force. $ echo $? 13 ``` ## Install Python 3.9+, no dependencies. ```bash pip install keepwatch ``` From a checkout instead: ```bash pip install -e . PYTHONPATH=src python -m keepwatch.cli --help ``` ## 60-second demo ```bash keepwatch --root /tmp/ns init builder reviewer keepwatch --root /tmp/ns send \ --from reviewer --to builder --subject "batch 3: add login page" keepwatch --root /tmp/ns check --who builder keepwatch --root /tmp/ns claim --who builder --id --ttl 60 ``` Now pretend the worker dies. Wait for the lease, then: ```console $ keepwatch --root /tmp/ns status root: /tmp/ns builder pending 0 processing 1 done 0 hb 61s (alive) reviewer pending 0 processing 0 done 0 never seen (unknown) next: - builder: 20261023T083750-ef2c5d held by builder, 1s past its lease - run `reap` $ keepwatch --root /tmp/ns reap reaped 1 task(s) 20261023T083750-ef2c5d back to pending (tries=1) $ keepwatch --root /tmp/ns claim --who builder --id 20261023T083750-ef2c5d claimed 20261023T083750-ef2c5d (try 2) lease until 2026-10-23T08:42:31Z ``` The task came back with `tries=1` and the previous owner recorded. Close it, but only after proof: ```bash keepwatch --root /tmp/ns verify --id --name pytest --rc 0 keepwatch --root /tmp/ns done --who builder --id --note "e2e green" ``` ## Exit codes Exit codes are the interface; agents read them instead of parsing prose. | Code | Meaning | |---|---| | 0 | ok | | 1 | bad arguments, IO error | | 2 | task not found / already taken | | 3 | task held by another worker (or busy) | | 13 | blocked: no passing verify, or `status` found something that needs attention | ## Design notes **Files are the source of truth.** No database, no daemon, no network. `ls` and `cat` are the debugging tools. **Exclusive create is the mutex, not rename.** On at least one Windows filesystem two concurrent `os.replace()` calls on the same source *both report success*. Claiming is therefore guarded by `O_CREAT|O_EXCL` on a short-lived token file. This is documented in [`docs/PROTOCOL.md`](docs/PROTOCOL.md) because it cost a real bug to find. **One worker per agent box.** Leases are per agent directory, so `builder` running two processes is a configuration error, not a supported mode. **`status` exits 13 when something needs attention** — so `keepwatch status || alert` works in cron. ## What it is not - not a mailbox (see [docs/COMPETITION.md](docs/COMPETITION.md) for what exists) - not an orchestrator: it never decides *what* to do next - not an agent: it makes no model calls - not durable execution: it will not replay your side effects ## Docs | File | Contents | |---|---| | [docs/PROTOCOL.md](docs/PROTOCOL.md) | layout, task format, lease rules, exit codes | | [docs/COMPETITION.md](docs/COMPETITION.md) | why this exists next to agent-mailbox / A2A / Temporal | | [docs/ROADMAP.md](docs/ROADMAP.md) | v0.1 → v1.0 and the release checklist | | [docs/zh/](docs/zh/) | 中文原文 (Chinese originals) | Tests: `PYTHONPATH=src python -m unittest discover -s tests -v` ## License MIT. See [LICENSE](LICENSE).