pi-dispatch v1.2.0
Three opt-in bounds on how far a resumed conversation may run, and a fix to the service wrapper that made a stop during startup look like it had worked.
Pull the new job image
One of the three bounds is measured inside the container, so upgrading the packages alone is not enough. PI_SESSION_MAX_CONTEXT_PCT reads pi's own context usage in the runner and reports it on the exit line. Keep yesterday's image and that field never arrives: the gate passes, the lineage resumes exactly as before, and nothing says so. The other two bounds are host side and work the moment you set them.
docker pull ghcr.io/edgehero/pi-job:latest && docker tag ghcr.io/edgehero/pi-job:latest pi-job:latest
What the bounds do
run.resume decided eligibility with six gates on the transcript file: does it exist, is it a regular file, is it too big, is its mtime inside the TTL, did the same pi write it, does its first line parse. None of them measured what an operator actually worries about once a lineage gets long. These three do, each off by default, each a cold start with its own named reason rather than a refused job.
PI_SESSION_MAX_AGE_DAYS bounds conversation age. PI_SESSIONS_TTL_DAYS reads the transcript's mtime, which the promotion refreshes, so it measures time since the last completed run. A pull request that keeps getting work done on it never expires however far back its first turn goes. pi's session header already carries the creation instant, so the second clock costs no new state.
PI_SESSION_MAX_RESUME_CHAIN bounds how many times in a row one key may be resumed. Age and size grow slowly; a chain grows once per run. It counts the host's own deliveries rather than what pi made of the file, because the agent owns /session and a transcript whose payload pi's parser drops would otherwise reset the counter on every run.
PI_SESSION_MAX_CONTEXT_PCT bounds context fullness, and it is a safety bound before an economic one. Past pi's compaction threshold a resumed job replays a model written summary produced while that model was reading attacker authored text.
The repair underneath them
The three reasons would have been invisible. A refused read stages a zero byte file rather than nothing, PI_SESSION_FILE is emitted whenever a session exists at all, and pi's own refusal is gated on size, so the container opened the empty file, found no messages, and reported absent on every host refusal. The merge took the container's word for it. expired and pi-version-changed therefore reached no completed record in this feature's whole life, while the docs promised every cold start was nameable there. A resolve path gate that refused now outranks that one runner token.
Also in this release
The service wrapper now stops what it started (#221). A stop arriving during startup, while the environment setup script was still running, was accepted and then ignored: the command launched anyway, into a half built environment, and the daemon reported success.
Versions
@edgehero/pi-dispatch 1.2.0, @edgehero/pi-dispatch-admin 1.2.0. @edgehero/pi-dispatch-receiver stays at 1.1.0: nothing in it changed, and its dependency range already admits the new worker.