Roadmap

Delivered in v0.2.0

  1. Runnable containment shell. processkit-cli run executes one shell-free command through the public processkit API, echoes child stdout/stderr, and preserves the child exit code. Timeouts and cancellation use a distinct, documented runner-owned exit-code band.
  2. JSONL schema v1. The normative event schema and golden fixtures cover lifecycle events, cleanup, runner failures, and terminal exit. Events are written to --jsonl, never stdout, and argv is redacted by default.
  3. Bounded diagnostic capture. --capture-dir writes separate bounded stdout/stderr transcripts with full byte counts, hashes, and truncation metadata while preserving live echoed output.
  4. Live-run control plane. The per-user registry and local IPC back inspect, cancel, kill, list, and prune; stale entries are visible and safely reaped without addressing a process by PID.
  5. End-to-end proof. Through-the-binary tests cover leaked descendants, nonzero roots, inherited pipe handles, concurrent runs, control-plane cancellation, and platform-specific teardown behavior. The heavier e2e tier additionally covers abrupt runner death, nested Windows Jobs, PID reuse, and Ctrl-C.
  6. Distribution. Releases publish six prebuilt archives: Windows x86_64 and aarch64, Linux x86_64 glibc and musl plus aarch64 glibc, and Apple Silicon macOS. Source installation remains available through cargo install.

Delivered in v0.2.1

  1. Explicit stdin sources. --inherit-stdin shares the runner's input handle with the child, while --stdin-file <file> streams a checked file through ProcessKit and closes stdin at EOF. Closed/null stdin remains the safe default.

Current development

  1. Interactive inherited stdio. --inherit-stdio passes stdin, stdout, and stderr directly to the child, preserving an existing console or terminal while retaining ProcessKit containment, lifecycle JSONL, the control plane, cleanup, and exit-code fidelity. The default remains pipe + echo; transcript capture and no-console mode intentionally conflict with direct inheritance.
  2. Cross-platform terminal proof. Through-the-binary tests cover piped I/O, a real Windows console, and a POSIX pseudo-terminal, including input, terminal detection, JSONL completion, and descendant cleanup.

Remaining ProcessKit-rs dependencies

The processkit 3 graceful-shutdown contract is now fully consumed. Windows console children can opt into CTRL_BREAK with --windows-graceful-ctrl-break; every runner-imposed graceful ending probes ProcessGroup::soft_stop_scope() before the attempt and records the resulting ShutdownReport in cleanup_finished.shutdown. ProcessGroup::members_info() is likewise consumed for members_snapshot/inspect enrichment (see docs/schema.md, "Enriched member fields").

Whole-tree cleanup after an abrupt runner death is also a core dependency on Unix. The current public primitive kills only the direct child on Linux and is a no-op on FreeBSD, macOS, and other BSDs; cgroups, process reapers, and process groups do not disappear with their owner. Until ProcessKit exposes an additive, identity-safe whole-tree owner-death primitive, the CLI reports direct_child_only or none in run_started and does not claim the Windows guarantee on those platforms. Any stronger contract requires additive, identity-safe ProcessKit-rs support and cross-platform abrupt-death proof.

Runtime resource-limit attribution is now consumed through processkit 3.2.0. The existing limit_hit event still covers only the pre-spawn "could not be applied" branch. Runs with a requested cap additionally emit the additive limit_evidence event with tripped, not_tripped, or unknown per axis, read before teardown consumes the group. The evidence is authoritative on Linux cgroup v2 only: Windows Job Objects, the FreeBSD process reaper, and POSIX process groups (macOS, other BSDs, and the Linux process-group fallback) do not provide the same successful Linux path. A successfully created Windows Job Object reports unknown for capped axes; a POSIX limit request fails before a capped group exists and therefore emits the existing pre-spawn limit_hit with no limit_evidence. This closes the runtime-attribution gap on Linux cgroup v2 only; it does not claim post-run limit attribution on Windows or POSIX fallback runs.

Whole-tree resource measurement — as distinct from that limit attribution — is now consumed through processkit 3.3.0's ProcessGroup::stats(). Every run that spawned a child emits the additive resource_summary event exactly once, from the same read point as limit_evidence and immediately after it, with a slot for peak memory, total CPU, IO bytes read and written, and peak process count, each independently nullable. This is deliberately the answer to a different question than limit_hit/limit_evidence — how much the tree used, not whether a cap engaged — and it is what partially closes the gap the paragraph above leaves open on Windows: post-run limit attribution there remains unknown by mechanism, but the measured peak_memory_bytes from the Job Object's own accounting block is now available as an honest substitute, which is the reason this was worth taking. The closure is specific to that mechanism and no wider. Axes stay unavailable by mechanism rather than by omission — peak_process_count on Windows (a Job Object keeps no peak-concurrency counter), both IO counters on FreeBSD, macOS, other BSDs, and the Linux process-group fallback, and on Linux the IO counters additionally need the cgroup v2 io controller, which this CLI does not enable. On Linux cgroup v2 memory and CPU are also bounded by the read point rather than by the controller set: stats() sums them from /proc over the members live when it runs, so a run that ended by its child exiting reports both as null, and only a runner-imposed ending (read while the tree still runs) populates them. The normative matrix, including that consequence and why the two platforms' IO counters are not comparable with each other, is in docs/resource-limits.md, "What the tree consumed".

That last point is an upstream limitation, not a CLI one, and is recorded here so a revisit starts from the right place: cgroup v2 does keep whole-tree accumulators that outlive their members (memory.peak, cpu.stat), but processkit's Linux backend deliberately folds per-member /proc counters instead, because the cgroup it creates enables no controller unless a cap asks for one. Closing the gap means asking processkit for a memory/CPU reading sourced from those controller files (and enabling the controllers to make them exist), not adding sampling here — a runner-side maximum over its own reads would report when this CLI looked rather than what the tree did, which the event's contract rules out.