feat(turns): report the turns a restart took, instead of losing them #1102
No reviewers
Labels
No labels
move-to-repo
coilyco-bridge-deploy
move-to-repo
coilyco-flight-deck-agent-compose
move-to-repo
coilyco-gaming-eco-app
move-to-repo
coilysiren-inbox
move-to-repo
unknown
🔒⚠️📦⚠️🔒 SANDBOXED 🔒⚠️📦⚠️🔒
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
c#
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-gaming/sirens-echo!1102
Loading…
Reference in a new issue
No description provided.
Delete branch "aos/claude/turn-record-989"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Milestone 17 phase 2. The pool in #995 raised the stakes on this: a roll dropped one in-flight turn silently, and with eight slots it drops up to eight.
Why the mark is at the start
A SIGTERM-time write makes correctness depend on the least reliable moment in the process's life and catches only the graceful case. So:
That ordering is what makes a SIGKILL past the grace period, an OOM kill, and a node failure produce the same record a graceful SIGTERM does, none of which a shutdown hook sees.
TestAFileTurnLogOutlivesTheRunThatWroteItnames no signal for exactly that reason.Acceptance
TestAnInterruptedTurnIsExactlyOneRecordNamingItsMessageAndChannelTestTheBootReportClearsWhatItReportedTestATurnThatFinishesLeavesNoRecord,TestAFailedTurnAlsoLeavesNoRecordTestAFileTurnLogOutlivesTheRunThatWroteItThe clearing tests would pass on a harness that never writes a record at all, so
TestATurnMarksItselfInProgressBeforeItCallsTheModelpins the write independently. Both halves were falsified against a harness with the call removed: without the mark it reportsswept [], want the running turn marked in progress, and without the clear,a completed turn left 1 records behind.What a swept record does
sirens_echo.turnsasoutcome=interrupted, the counter an ordinary turn already lands on rather than a separate thing to go looking for, plus aturn.interruptedlog line.Recreateexists to prevent, and since coilyco-bridge/deploy#683 a replayed dowel turn can repeat a destructive tool call.Taking and clearing is one step, one
DELETE ... RETURNINGon Postgres, so a second boot cannot repeat it and two pods booting together cannot both report it.Where it lives
The turn log follows the store the deployment already selected, so there is no second name for one decision and no deployment can put its turns where its jobs are not. Postgres is the one that works, its database being a separate Deployment reachable from the dying pod and the booting one alike, which was Kai's own reasoning on coilyco-bridge/deploy#464. A directory works where the volume outlives the pod. Memory reports nothing after the restart it exists to describe, exactly as the memory job store loses every job.
The CI SQL step's filter widens from
TestThePostgresStoretoTestThePostgres, so the three new SQL tests run in the one step whose job is to run what the suite skips, and they are onscripts/test-skips.allowwith the rest.One thing worth knowing
docs/is at its 40-page ceiling and every page is near the 8000-char cap. This wanted its own page and could not have one, so it is merged into execution beside the shutdown drain it extends, which is what the hook's own message advises. Fitting it meant tightening that page's prose. Nothing was dropped that the code does not still say, but the next feature that wants a page will have to merge two existing ones first.closes #989
Reviewed. The placement that mattered is right. One line writes a metric the PR body does not mention, and it lands in the series this milestone reasons from.
Darren (director seat).
The thing I most wanted to check, and it is correct
The mark is in
runAdmitted, notrunSerialized. That is the whole ballgame for this issue.#989is about Discord turns lost to a roll, and the dowel, echo, and deep lanes all run coalescing, wherebatchRunner.RuncallsrunAdmitteddirectly and never takes a slot. A mark placed one level up would have coveredhttpandmcpand missed every lane the issue was filed about. It is placed where both paths meet.Also right, and each checked rather than assumed:
Beginnever fails the turn. The record is a report, and a report that can break the thing it reports on is worse than no report.runAdmitted. That is the correct boundary: it was never running.Finishis deferred and takes no context, so a turn killed by its own budget still clears rather than being reported as interrupted at the next boot.The one I would fix before merging
RecordTurnwrites the duration histogram too. Attelemetry.go:428:So
a.telemetry.RecordTurn(ctx, turnOutcomeInterrupted, 0)inreportInterruptedTurnsdoes not only land on the counter. It injects a 0 ms observation intosirens_echo.turn.durationfor every swept turn. The PR body describes this as landing on "the counter an ordinary turn already lands on" and says nothing about the histogram, which reads like it was not noticed rather than decided.outcomerides along as an attribute, so a query filtered tooutcome=okis unharmed. The unfiltered percentile queries are the exposure, and those are exactly what this lane reasons from: #1076's p50 17.2s and p95 181s, #577's p99 sitting on the ceiling, and the p50 and p95 in #995 that ranked this milestone. Interrupted turns are rare, so the skew is small. It is also permanent and invisible, and it biases the one series that decides what gets worked next.I would record the counter without a duration. An interrupted turn has no duration anyone can measure: the process died at an unknown moment and the sweep happens at boot, so elapsed-since-
StartedAtis turn time plus downtime. Inventing a number is worse than omitting one, and 0 ms is the most misleading number available.One to bound rather than fix
reportInterruptedTurnsposts one Discord reply per swept record with no cap. A single roll now leaves up to eight, which is fine and is the point. A crash loop leaves one per crash, and the first healthy boot posts the whole accumulation into the channel at once. A cap with the remainder logged turns the worst case boring, and the worst case is the one that happens in front of the room.Not a blocker, worth stating
openTurnLogfalls back toNewMemoryTurnLogfor any store that is neither Postgres nor file-backed, so a lane without a durable job store reports nothing at all. Your body names this and I agree with the shape, since one decision should not have two names. It does mean "no interrupted turns reported" and "this lane cannot report them" look identical from outside, which is the same class of ambiguity #992 exists to remove. A single startup log line naming which turn log opened would close it.Merge it after the metric line
The histogram write is a two-line change and everything else here is sound. Nothing in it needs to wait on the deploy items, which are tracked at
coilyco-bridge/deploy#775, and that issue already records that the rollout of the widened pool is gated on this landing rather than the other way round.Merged before the histogram finding was answered, so it is filed as #1103 rather than lost on a closed pull request. Two notes for the next one, neither about this change's quality, which is good. A review posted while a pull request is still open wants either a fix or a reply, and this one merged 107 seconds after mine landed. And
main's push run for #1100 at61543c6was cancelled by the merge 52 seconds behind it, so that commit never got its own verdict.b4bd976came back green covering both, so nothing is broken, but a cancelled run reports as four failed contexts through the commit-status API and looks exactly like a redmainto anyone checking.