A finished turn ends its run without the prompt's replay, and a reported run the idle cap ends is done #131
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "task/3721-a-finished-turn-ends-its-run-settle-with"
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?
Tracker: #3721
Step 1 of plan #3694. Fixes #3691, and takes the report rule of #3687.
What it delivers
dd25892). The pump counts only the messages handed to a run mid-run, and each adapter marks only their replays. The stream-json harness sometimes leaves out the prompt's replay: Planner run 358 (stream 7567) has noisReplayline in its 836 events, so its stdin never closed and the idle cap killed it. The stream-json adapter now takes the first replay for the prompt's only when it carries the prompt's text. The harness replays the prompt first or not at all, and the replay carries the text verbatim (checked on stream 7636). The app-server adapter takes the thread's first user-message item for the prompt's, and the ACP adapter the first prompt the agent names running. A turn that ends with no handed message outstanding now closes the harness's stdin, whether or not the prompt was replayed. A message handed mid-run still holds stdin open until its replay and the turn after it end.78d3241). The pump readsSAK_REPORTonce the harness has exited. A run the idle cap ends, whose report is a JSON object, endsdonewith it: the stream closesdonewith the report on its result, and the request isdone. This holds whether its turn had ended or a tool call never returned, as in run 360 (#3693), whose tool call waited on a macOS privacy prompt. A run with no report, or one that is not a JSON object, still endskilled · idle_timeout. The wall cap is unchanged. The stream's close carries the same report text the rule read.tracker.report_ofis the one test of "a JSON object" for both. TheOutcomedoc inrunner/run.sxsays so (aaac369).234bb3e).STEP_REPORT_HOWnow adds: "Write the report last, once your work is over, and wait in the foreground for long work, such as a gate, before your turn ends: your run ends when your turn does, and a run that falls silent once its report is written ends with that report."ec78bde). The flow'sfullgate step (job 75, on m5) failed this head only ontests/step_concurrency.sx"the slot a dropped tail left behind is handed on once its lease lapses". The cause is in sak's tail, not only in the machine.tail_producesent its: tickcomment every ten polls and counted each poll asPOLL_MS. A poll is a 100 ms sleep plus three reads, and on a runner whose timers the system stretches, a 100 ms sleep takes 100–250 ms. So the heartbeat came every 2.3–2.4 s, not "about once a second" asdocs/api.mdsays. A dropped tail renews its slot until its first heartbeat after the drop, so its lease lapsed after the test's reclaim at drop + 8 s. The heartbeat now goes out onceHEARTBEAT_MShas passed on the clock since the tail last sent framing.runner/README.md: One run 11 (the settle rule) and 14 (the idle-cap outcome), and the Harnesses section (which replay is the prompt's for each protocol).docs/api.mddoes not describe the close's status rules, so it is unchanged.One case stays open. Suppose the stream-json harness skips the prompt's replay, and a handed message's text is exactly the prompt's. That message's replay is then taken for the prompt's, and the run holds stdin open until the idle cap. It ends
donethere if its report is written.Flow run 36 (#3687) and master
git fetch origin mastergaveorigin/master=3ecd9d1at 00:57 EEST on 2026-10-24, and6e5e668at 03:30 and 05:20. Neither held a merge oftask/3687-a-run-whose-turn-ends-while-its-backgrou, so the commits above were built on3ecd9d1alone. At 07:17,origin/masterwas4bd0747, the merge of #3687's PR 128 (07:13). So this branch mergesorigin/master(c61164d), and builds on #3687's rules without reimplementing them. That merge also brings the rest of master since3ecd9d1into this PR: PR 130's stream media (6e5e668). Master'sfa0995cis the same tail fix asec78bde, sosrc/api.sxmerged clean.Two files conflicted, and each kept both sides:
runner/run.sx.pumpkeeps master's signature, withreportlast. Master's line on a session's running tasks, written when a turn ends without the report while background tasks run, counts as a handed message:p.handed += 1. So the turn it opens settles once that line's replay arrives, whether or not the harness replayed the prompt. The stream-json adapter never takes that line's replay for the prompt's, because its text is not the prompt's. A run that a session's background tasks hold endsdonethrough master's SIGTERM once a turn writes the report. A run the idle cap ends isdonethrough this branch's report rule.runner/README.mdOne run 11 is this branch's settle rule followed by master's background-task rule, which now says the runner's line counts "as a handed message is".At 09:56 EEST
origin/masterwas13c9709, the merge of #3651's PR 129. It carries3ee9597, which quotes the PATHtests/step9_release.sxhands install.sh (#3745), and the stream goldens that hold one width for each id and clock they hide. The branch merges it again (cbf1316), which brings PR 129 into this PR too. No runner file or runner test changes in that merge, so the regressions below stand at the head. The merge is clean:git diff-tree --cc cbf1316prints only its hash, andgit diff origin/master cbf1316is byte-identical togit diff 4bd0747 c61164d. The plan's branch was still3ecd9d1.Regressions
New checks in
tests/runner_core.sx, on two new modes of the stub stream-json harness:check_unreplayed. Theunreplayed 0mode skips the prompt's replay, writes its report and ends its turn. Its run must enddonewith the report before the 5 s idle cap. Theunreplayed 1mode also gets a handed message, and the run must enddoneafter that message's replay and the turn after it.check_hung_report. Thehung object|none|arraymode replays the prompt and writes a JSON object, no report or a JSON array. It then prints atool_useline with no result and stays silent past the 1 s idle cap.By-hand
sx run tests/runner_core.sxwith the gate's pinned sx 300086027f (the gate cache, on m5 unless the row says s32):3ecd9d1runner code, new tests and stubRUNNER CORE FAIL (3 checks), 382 okkilled · idle_timeoutat the 5 s cap; hung-object run 7killed · idle_timeout; runs 8 (none) and 9 (array)killed · idle_timeoutaaac369, before the mergeRUNNER CORE OK, 385 okdonein the second they started; run 7doneat the 1 s cap; runs 8 and 9killed · idle_timeout4bd0747(#3687 merged), new tests and stub; on s32RUNNER CORE FAIL (3 checks), 388 okkilled · idle_timeoutat the 5 s cap; runs 7, 8 and 9killed · idle_timeoutc61164d; on s32RUNNER CORE OK, 391 okdone; runs 8 and 9killed · idle_timeout; #3687'scheck_background_wait(run 13) andcheck_server_report(run 14)doneThe three checks that fail at the base, and at master with #3687 merged:
The no-report and not-an-object checks pass at every arm, as they must.
check_second_turn,check_driver,check_session_line,check_wakeupand the app-server and ACP steer and queue checks pass unchanged at the head.A by-hand probe drops the stream-json adapter's text check, so its first replay is always taken for the prompt's.
runner_corethen readsRUNNER CORE FAIL (1 checks): only the handed-message check fails, its run endingdoneat the 5 s idle cap through the report rule.The tail's heartbeat
Master carries the same fix, as
fa0995c. The regression is the existingstep_concurrencycheck that failed at the gate step. An instrumented copy of the tree printed each tail's ticks. At the pre-fix code, a dropped tail's last tick came 2.33 s after the drop, and the other tails' ticks were 2.34–2.44 s apart. With the fix, the last tick came 1.08 s after the drop, and ticks were 1.0–1.25 s apart.By-hand
sx run tests/step_concurrency.sxon s32 with the gate's pinned sx 300086027f, between 03:31 and 03:40 EEST, at a one-minute load of 2–3.4. Python'stime.sleep(0.1)took 100–250 ms in the same shell.234bb3e(pre-fix tail, same as the base)3ecd9d1ec78bdeThe failing runs read:
Base and fix runs alternated four times, so both arms saw the same machine. Base failed 3 of 4 and the fix passed 4 of 4. A deterministic fail-before would need a hook that slows a poll, and the tail has none. A check of the heartbeat's spacing would depend on timer speed in the same way.
Gates
Golden set: none.
At the head
cbf1316, on s32:Gate run 291 (
sak-gate full#1, 10:23:58–10:51:56 EEST, clean tree) passed every step but the last:npm run build, with vitest at 401 of 401 in 5.56 s;tests/*.sxfile,runner_core.sxandstep_concurrency.sxincluded;scripts/release.sh, its own vitest again 401 of 401, and thentests/step9_release.sx, which now passes on s32 with the PATH quoting from master;PASS);It failed only at the runner Playwright config's global setup:
sak serve exited with code 1,sak serve never answered on http://127.0.0.1:18988. That port is held by pid 6685,/private/tmp/sak-run-75-4b1062ec/work/dist/sak serve … --port 18988, which has been up 2 days 18 hours. Run 75 left it behind. It fails this step on s32 whatever the branch.The runner Playwright config, by hand (10:52–10:53), from a
git archiveofcbf1316in scratch. The arm used gate run 291'sdist/sak,dist/sak-runner,dist/sak-runner-liveandweb/dist, plus the gate cache'sweb/node_modulesand chromium. Its port was changed to 18978 in the arm only: 10 passed (35.5 s). The checkout was not touched.The flow's gate step at
c61164d, on s32: job 103 (09:54:29–09:55:53 EEST) failed innpm run build. Its vitest failed one of 401 tests:NavigatorRoute.test.ts"shows the counts of the loaded tracker" ran out its 1 swaitFor(1140 ms). Duration 16.97 s, environment 102.59 s. That is open task #3731. The branch'sweb/equals master's.Earlier heads:
c61164d, on s32: gate run 269 failed in vitest on the same #3731 test while job 92's sx gate ran beside it. Gate run 271 passed every step up to the release step,tests/*.sxincluded, then failed onlytests/step9_release.sx's three install.sh checks (env: Support/JetBrains/Toolbox/scripts: No such file or directory, #3745). By hand, with the PATH's one spaced entry dropped: step9STEP9 OK, the deploy contractPASS, the main Playwright config 740 passed and 1 skipped once rerun in a quiet window, and the runner Playwright config 10 of 10 on port 18978, because pid 6685 holds 18988 on s32.aaac369, on m5: gate run 260 passed every step offull. Job 88, on s32, failed only at step9 (#3745).ec78bde, on m5: gate run 259 passed every step offull. On s32, gate run 250 failed only at step9, and job 81 failed on #3731's timeouts in the release step's second vitest.234bb3e, on m5: gate runs 224 and 232 and job 75 failed, on two web timeouts and onstep_concurrency.sx's lease check, whichec78bdefixes.tests/step9_release.sx ran install.sh through `env ... PATH={}/stubs:$PATH sh -c ...` with the PATH unquoted. A run PATH that holds a directory with a space split that assignment, and env ran the rest as a command, so three install.sh checks failed. s32's run PATH holds "Application Support", so the full gate failed there for every branch. The assignment is quoted, and the launchctl stub's directory is named with a space, so the check hands install.sh a spaced PATH entry on every host. Tracker: https://sak.swipelab.com/tasks/3745