Drain closed agent-run logs shortly after container exit, not only at keep-10 eviction #510

Closed
opened 2026-07-02 04:58:41 +00:00 by coilysiren · 3 comments
Owner

Infrastructure logdy watches ~/.ward/agent-logs/ and the operator requirement is now explicit: newly closed ward agent runs should appear there shortly after the container stops, not only when the exited container ages out of the keep-10 retention window.

Current behavior:

  • ward drains console.log, transcript.jsonl, and meta.json to ~/.ward/agent-logs/<container>/ only during the keep-10 stale-container sweep, right before docker rm of older exited containers
  • infrastructure logdy correctly watches that tree, but therefore cannot show a just-closed run until much later

Required outcome:

  • shortly after a ward agent container exits, its drained run artifacts become available under ~/.ward/agent-logs/<container>/
  • the keep-10 retention policy for exited containers can remain, but observability for closed runs must not wait on eviction from that window

Acceptance criteria:

  • design and implement the earliest safe host-side point to drain a just-closed run's console/transcript/meta into ~/.ward/agent-logs/<container>/
  • preserve the current safety properties: no secret spill, best-effort drain, and no destructive interaction with live containers
  • make the behavior idempotent if the later stale-container sweep sees a run that was already drained
  • add or update tests around drain timing/order and duplicate-drain handling
  • update ward observability / cleanup docs to describe the new timing
  • link the infrastructure consumer issue: coilyco-flight-deck/infrastructure#424
Infrastructure logdy watches `~/.ward/agent-logs/` and the operator requirement is now explicit: newly closed `ward agent` runs should appear there shortly after the container stops, not only when the exited container ages out of the keep-10 retention window. Current behavior: - ward drains `console.log`, `transcript.jsonl`, and `meta.json` to `~/.ward/agent-logs/<container>/` only during the keep-10 stale-container sweep, right before `docker rm` of older exited containers - infrastructure logdy correctly watches that tree, but therefore cannot show a just-closed run until much later Required outcome: - shortly after a `ward agent` container exits, its drained run artifacts become available under `~/.ward/agent-logs/<container>/` - the keep-10 retention policy for exited containers can remain, but observability for closed runs must not wait on eviction from that window Acceptance criteria: - design and implement the earliest safe host-side point to drain a just-closed run's console/transcript/meta into `~/.ward/agent-logs/<container>/` - preserve the current safety properties: no secret spill, best-effort drain, and no destructive interaction with live containers - make the behavior idempotent if the later stale-container sweep sees a run that was already drained - add or update tests around drain timing/order and duplicate-drain handling - update ward observability / cleanup docs to describe the new timing - link the infrastructure consumer issue: coilyco-flight-deck/infrastructure#424
Owner

🔒 Reserved by ward agent --driver goose — container engineer-goose-ward-510 on host kais-macbook-pro-2.local is carrying this issue (reserved 2026-07-02T04:58:49Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Goose, via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver goose` — container `engineer-goose-ward-510` on host `kais-macbook-pro-2.local` is carrying this issue (reserved 2026-07-02T04:58:49Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Goose, via `ward agent`
Owner

🔒 Reserved by ward agent --driver claude — container engineer-claude-ward-510 on host kais-macbook-pro-2.local is carrying this issue (reserved 2026-07-02T16:58:16Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-ward-510` on host `kais-macbook-pro-2.local` is carrying this issue (reserved 2026-07-02T16:58:16Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

WARD-OUTCOME: done - detached container drain-exit waiter + full-set idempotent sweep land run logs shortly after exit; merged to main.

The shape of this one clicked once I accepted the real constraint: a detached ward agent run is fire-and-forget, so there is genuinely no host process alive to notice the container exit. That ruled out the tempting "just watch it" answers and pointed at a detached grandchild waiter blocking on docker wait. Spawning something that outlives its parent (Setsid + Release + nulled stdio) felt slightly heavier than a normal ward change, so I leaned on the keep-10 sweep as a second, idempotent drain point - which also happens to be exactly what acceptance criterion 3 asks for.

What fought back was entirely the pre-commit suite, not the logic: the ≤2-line comment-block rule and the 80-line/4000-char doc caps. Both target docs were already sitting right at the char cap, so every sentence I added had to be paid for by cutting one elsewhere - I ended up splitting the detail into a new docs/drain-timing.md. A subtle gotcha: a //-only line does NOT break a comment block for the linter, so blank lines had to be real blanks.

Confidence is high on the sweep/idempotency half (unit-tested, and I drove the full docker wait -> drain -> marker path end-to-end with a fake docker, including the secret-allowlist and the skip-on-marker case). The one thing untested by a real run is the detached spawn on a live Mac host - the argv/marker logic is unit-tested, but nobody has watched an actual orphaned waiter drain a real container yet. If it ever misbehaves, the sweep still backstops it, so the failure mode is "slightly later", not "lost". Possible follow-up: prune stale .drained/ sentinels for containers that no longer exist (today they are cleared on removal, which covers the normal path).

WARD-OUTCOME: done - detached `container drain-exit` waiter + full-set idempotent sweep land run logs shortly after exit; merged to main. The shape of this one clicked once I accepted the real constraint: a detached `ward agent` run is fire-and-forget, so there is genuinely no host process alive to notice the container exit. That ruled out the tempting "just watch it" answers and pointed at a detached grandchild waiter blocking on `docker wait`. Spawning something that outlives its parent (Setsid + Release + nulled stdio) felt slightly heavier than a normal ward change, so I leaned on the keep-10 sweep as a second, idempotent drain point - which also happens to be exactly what acceptance criterion 3 asks for. What fought back was entirely the pre-commit suite, not the logic: the ≤2-line comment-block rule and the 80-line/4000-char doc caps. Both target docs were already sitting right at the char cap, so every sentence I added had to be paid for by cutting one elsewhere - I ended up splitting the detail into a new `docs/drain-timing.md`. A subtle gotcha: a `//`-only line does NOT break a comment block for the linter, so blank lines had to be real blanks. Confidence is high on the sweep/idempotency half (unit-tested, and I drove the full `docker wait -> drain -> marker` path end-to-end with a fake docker, including the secret-allowlist and the skip-on-marker case). The one thing untested by a real run is the detached spawn on a live Mac host - the argv/marker logic is unit-tested, but nobody has watched an actual orphaned waiter drain a real container yet. If it ever misbehaves, the sweep still backstops it, so the failure mode is "slightly later", not "lost". Possible follow-up: prune stale `.drained/` sentinels for containers that no longer exist (today they are cleared on removal, which covers the normal path).
Commenting is not possible because the repository is archived.
No milestone
No project
No assignees
2 participants
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/ward#510
No description provided.