Conversation
9209172 to
325a81a
Compare
#63 (d349f25) merged mid-review, so its final review wave never reached `pinvou3-clean`. This PR carries that wave plus the fixes from the subsequent review rounds onto the current base, as one commit; per-area detail lives in the PR description. - exec: headless runs resolve `request_user_input` via `cancel_user_input` instead of parking forever once the engine wait became unbounded; the cancellation contract is pinned. - snapshot: every pre-git window on a wedged mount is bounded (workspace canonicalize, the safety classifier's re-canonicalization, the first-init size walk, honoring `max_workspace_gb = 0`); a killed `git init` heals instead of wedging (HEAD-less, half-written, leftover config.lock/HEAD.lock); wedge failures surface as errors with stderr evidence and stage-keyed remedy hints; drain captures cap at 16 MiB per stream. - runtime: every approval exit publishes `approval.decided` and is race-free by construction (delivery races, monitor-death settlement, channel-closed exit); compat streams forward the approval posture. - tasks: liveness heartbeats short-circuit before the manager-wide state lock. The human-wait clock pause lives in #78 instead: as first landed it changed task-lifecycle behavior and regressed the TUI, so it was taken back out. - vision: the envelope timeout classifies as `ToolError::Timeout`, and the inference permit is acquired inside the envelope. - client: envelope-exceeded requests mark connection health and run the recovery probe on every dialect arm, including the Anthropic transport arm; the non-streaming budget got an injectable test seam. - app-server: a dropped stream resumes from the last consumed seq (bounded reconnects) before falling back to a best-effort interrupt, on every turn surface; the `/health` boot probe bounds each attempt. - tools: drain aborts are one helper covering every exit including cancel/drop; timeouts SIGKILL the child's process group so forked commands no longer outlive the call. - rlm: the sub-query budget is floored at 1s and the session default references `CHILD_TIMEOUT_SECS`. - mcp: the send-timeout error states the budget instead of tokio's `Elapsed` display. - docs: the 1800s timeout family anchors one complete roster; MCP/SUBAGENTS disclose the per-leg budget and wall-clock interactions; the RUNTIME_API.md restore contract is corrected. - tests: every fix above has a regression pin (mutation-verified red-green), timing margins are widened, and the Anthropic endpoint tests hold the env lock. Deliberately not fixed here (pre-existing on base unless noted, follow-up scale): Windows job-object group-kill parity; the contested inference-permit shape in web_search/voice/ prompt_suggestion; the pandoc.rs old-shape timeout; the two bounded runners' parallel drain implementations; the engine-side MCP stdio proxy's unbounded send leg; debounce starvation while heartbeats stream; the `CODEWHALE_MAX_OUTPUT_TOKENS` test-isolation race. Verified: `cargo fmt --check`; clippy `-D warnings` on codewhale-tui, codewhale-app-server, codewhale-core and codewhale-mcp; `cargo test -p codewhale-tui --lib` 11969/0 and `-p codewhale-app-server --lib` 103/0 on the final tree; the load-bearing pins were each mutation-checked to fail red. No-Issue: review-fix replay for the merged #63; no separate tracking issue exists. The full findings ledger is the round-6 review comment on #63. Signed-off-by: asto <asto18089@126.com>
This reverts commit ce5a01f. Signed-off-by: asto <asto18089@126.com>
A durable task whose turn hit an approval or user-input prompt was still bounded by the 30-minute task wall_time, which interrupted the turn at expiry and denied the pending decision on the human's behalf. The window is now excluded from the wall clock (like the engine-side turn budget), the idle clock pauses and restarts fresh when the window closes, and the exclusion is bounded by a fail-safe cap. Three design holes close the gaps the first landing shipped with: - Answerability: the pause applies only where a host can actually deliver the decision. The runtime API's task threads are served by HTTP decide_approval/submit_user_input and pause; the TUI's private task runtime has no such channel, so a prompt there never arms the window and the wall clock releases the worker at its own deadline (before, it parked the worker for the 24h cap). - Aggregate bound: the cap now bounds the windows summed across the whole task, not only each open window — a task chaining short prompts can no longer sum its way past the cap once per prompt. - Park-poll backoff: while a window is parked the journal poll backs off from the 200ms catch-up cadence, doubling up to 2s; an arriving decision broadcasts on the subscription and wakes the loop early, so the backoff only removes the empty polls. Pinned by red-green tests: an unanswerable surface keeps the wall clock running (mutation check fails without the gate), chained short waits sum to the cap and terminalize between windows (fails without the hoisted aggregate check), and every wait-writer surface that arms the window still completes on a late answer. Signed-off-by: asto <asto18089@126.com>
325a81a to
ba9c0a5
Compare
…pt-in explicit The park-poll backoff shipped inert: evaluate() clamped the loop wait to the 200ms catch-up cadence unconditionally, so the doubling and PARKED_POLL_CAP never took effect and the PR's own comments described a behavior that did not exist. The clamp now applies only on the non-parked path, and the parked cap wait excludes the already-closed windows so the aggregate budget, not the open window alone, bounds the sleep. The aggregate HumanWaitTimeout check moved into the pending chain after shutdown and cancel (it previously jumped the queue and mislabeled a canceled or shutting-down task as human_wait_timeout), and the window- closing event arm is now gated like the opening arm, so unanswerable surfaces no longer emit unpaired HumanWaitEnded signals. The answerability flag moved from a positional bool parameter to the named TaskManagerConfig::human_waits_answerable field, defaulting to false (the TUI's safe default); the runtime API opts in through server_task_config, which carries the contract in its name and doc. New deterministic pins cover the backoff cadence and its 2s ceiling, the aggregate-budget wake, the between-windows terminalization, cancel and shutdown outranking a spent cap, the idle restart on window close, the from_runtime default, and the server opt-in. Signed-off-by: asto <asto18089@126.com>
A fresh from-scratch re-review confirmed the guard behavior but caught the chained-waits integration test claiming the wrong mechanism: with back-to-back windows the open-window check fires mid-window (window one's spend is already in the aggregate total), so deleting the between-windows aggregate arm leaves the test green. The test is renamed and recommented to say what it actually pins; the between-windows arm itself stays pinned by execution_guard_spent_aggregate_cap_terminalizes_between_windows, which does go red under that mutation. Also from the review: - the backoff pins assert literal durations (800ms, 2s) instead of restating the constants, so drifting EVENT_CATCHUP_POLL or PARKED_POLL_CAP off their documented 200ms / 2s values fails them; - the supervised wall-exclusion executor delay grows from 900ms to 2s, clearing the thin 500ms scheduling-starvation margin on loaded CI; - the shared evaluate() comment now names both wake paths (the turn loop's event subscription and the supervisor's forwarded task event); - the human-wait event and config docs state that a custom executor emitting the HumanWaitStarted/Ended pair self-declares its waits answerable, independent of human_waits_answerable. Signed-off-by: asto <asto18089@126.com>
JensenChen28
left a comment
There was a problem hiding this comment.
已复核当前 head。实现把可应答宿主的审批/用户输入等待同时接入 turn guard 与 worker supervisor,暂停 wall/idle 计时,并用单次及累计 24h fail-safe cap 释放永久无人应答的任务;TUI 私有运行面保持默认不启用,避免不可应答提示占用 worker。事件配对、取消/关机优先级、累计上限与 parked poll backoff 的边界处理一致,文档也披露了等待期间继续占用 worker slot。git diff --check 通过;本地相关测试共实际执行 16 个(human-wait、backoff、aggregate cap、unanswerable surface 等),全部通过;当前 DCO、Gitleaks、check、gate、CodeQL 等门禁均通过。
|
Fresh from-scratch review (nine domain reviewers + mainline verification, no reliance on the earlier waves). Head audited: Verdict: 0 BLOCKER, 1 MAJOR (a test gap, not a code bug), 8 MINOR. The design is sound and the code is correct as far as nine adversarial passes could break it — the one MAJOR is that the PR's own mutation-verified-pin standard is not met for its headline wiring. What was verified, briefly:
MAJORM1 — The server opt-in wiring is unpinned end to end; a call-site revert keeps the whole suite green. I verified by mutation: reverting MINOR
Notes (no action demanded)
Verification on the rebased head
No-Issue consideration is already handled by the body. Fix M1 (and whatever MINORs are worth their churn) as one more commit on this branch; happy to re-verify the diff afterwards. |
The fresh review's MAJOR: the server opt-in had no test below the config builder, so reverting run_http_server's call site or dropping the flag's forward into EngineTaskExecutor kept the whole suite green — the one mutation the PR's own red-green standard did not cover. run_http_server's task-runtime wiring moves into open_server_task_runtime, the single wiring point the new pin also drives. The pin reads the flag where it lands (the executor field the manager runs tasks on) through a cfg(test) as_engine_executor seam, and goes red if any of the three hops (builder, forward, call site) regresses to the unanswerable default. Verified by mutation: inlining the builder in open_server_task_runtime and hardcoding the forward to false each turn only this pin red (the builder pin stays green — it never saw those hops); dropping the builder's flag assignment turns both red. Signed-off-by: asto <asto18089@126.com>
|
MAJOR fixed and pushed:
Mutation evidence, each applied to the committed fix:
Full suite on the pushed head: Scope note: the review's 8 MINORs are untouched, pending a call on which are worth their churn. The rebase onto |
#63 (d349f25) merged mid-review, so its final review wave never reached `pinvou3-clean`. This PR carries that wave plus the fixes from the subsequent review rounds onto the current base, as one commit; per-area detail lives in the PR description. - exec: headless runs resolve `request_user_input` via `cancel_user_input` instead of parking forever once the engine wait became unbounded; the cancellation contract is pinned. - snapshot: every pre-git window on a wedged mount is bounded (workspace canonicalize, the safety classifier's re-canonicalization, the first-init size walk, honoring `max_workspace_gb = 0`); a killed `git init` heals instead of wedging (HEAD-less, half-written, leftover config.lock/HEAD.lock); wedge failures surface as errors with stderr evidence and stage-keyed remedy hints; drain captures cap at 16 MiB per stream. - runtime: every approval exit publishes `approval.decided` and is race-free by construction (delivery races, monitor-death settlement, channel-closed exit); compat streams forward the approval posture. - tasks: liveness heartbeats short-circuit before the manager-wide state lock. The human-wait clock pause lives in #78 instead: as first landed it changed task-lifecycle behavior and regressed the TUI, so it was taken back out. - vision: the envelope timeout classifies as `ToolError::Timeout`, and the inference permit is acquired inside the envelope. - client: envelope-exceeded requests mark connection health and run the recovery probe on every dialect arm, including the Anthropic transport arm; the non-streaming budget got an injectable test seam. - app-server: a dropped stream resumes from the last consumed seq (bounded reconnects) before falling back to a best-effort interrupt, on every turn surface; the `/health` boot probe bounds each attempt. - tools: drain aborts are one helper covering every exit including cancel/drop; timeouts SIGKILL the child's process group so forked commands no longer outlive the call. - rlm: the sub-query budget is floored at 1s and the session default references `CHILD_TIMEOUT_SECS`. - mcp: the send-timeout error states the budget instead of tokio's `Elapsed` display. - docs: the 1800s timeout family anchors one complete roster; MCP/SUBAGENTS disclose the per-leg budget and wall-clock interactions; the RUNTIME_API.md restore contract is corrected. - tests: every fix above has a regression pin (mutation-verified red-green), timing margins are widened, and the Anthropic endpoint tests hold the env lock. Deliberately not fixed here (pre-existing on base unless noted, follow-up scale): Windows job-object group-kill parity; the contested inference-permit shape in web_search/voice/ prompt_suggestion; the pandoc.rs old-shape timeout; the two bounded runners' parallel drain implementations; the engine-side MCP stdio proxy's unbounded send leg; debounce starvation while heartbeats stream; the `CODEWHALE_MAX_OUTPUT_TOKENS` test-isolation race. Verified: `cargo fmt --check`; clippy `-D warnings` on codewhale-tui, codewhale-app-server, codewhale-core and codewhale-mcp; `cargo test -p codewhale-tui --lib` 11969/0 and `-p codewhale-app-server --lib` 103/0 on the final tree; the load-bearing pins were each mutation-checked to fail red. No-Issue: review-fix replay for the merged #63; no separate tracking issue exists. The full findings ledger is the round-6 review comment on #63. Signed-off-by: asto <asto18089@126.com>
797fbd6 to
ea07845
Compare
Summary
Re-lands the human-wait clock pause that #75 originally carried before its history was squashed, rebuilt on the fixes the fresh 12-agent audit demanded. Split out of #75 deliberately: it is task-lifecycle behavior change rather than a review fix, and as first landed it regressed the TUI.
A durable task whose turn hits an approval or user-input prompt was still bounded by the 30-minute task
wall_time, which interrupted the turn at expiry and denied the pending decision on the human's behalf. The window is now excluded from the wall clock (mirroring the engine-side turn budget), the idle clock pauses and restarts fresh when the window closes, and the exclusion is bounded by a 24h fail-safe cap with a dedicatedHumanWaitTimeoutterminal reason.Three design holes from the first landing are closed:
decide_approval/submit_user_inputand pause; the TUI's private task runtime has no such channel, so a prompt there never arms the window and the wall clock releases the worker at its own deadline. Before, it parked the worker for the 24h cap — the TUI regression that pulled this feature out of fix: land the timeout-audit review fixes for the merged #63 #75.Docs: SUBAGENTS.md / zh describe the pause and the answerability scope.
Testing
Red-green pins for every claim, each verified by mutation (ungating the answerability check, removing the aggregate check, re-applying the dead 200ms clamp, re-hoisting the aggregate check above shutdown/cancel, or dropping the closed-window subtraction from the parked wake — each fails its pin):
unanswerable_surface_keeps_the_wall_clock_running_through_a_prompt— wallWallTimeoutfires through a pending prompt, and noHumanWaitStartedis published.chained_short_waits_still_terminate_at_the_fail_safe_cap— two 120ms windows against a 200ms cap cannot chain past the fail-safe: the guard terminalizes asHumanWaitTimeoutonce the windows sum past it. With back-to-back windows the open-window check observes the crossing mid-window; the between-windows arm itself is pinned byexecution_guard_spent_aggregate_cap_terminalizes_between_windows.Full suite on this head:
cargo test -p codewhale-tui --lib11980/0 withRUST_MIN_STACK=33554432; fmt clean. Head320307876, stacked on #75's squashed head1f69acdf8; this PR's base is #75's head branchround6-followup, so Files changed carries only this PR's four commits. Merge order is enforced by the stack: after #75 merges, retarget this PR topinvou3-clean(GitHub does it automatically ifround6-followupis deleted at merge time; this repo usually keeps branches, so retarget by hand otherwise) — the full check/gate CI, whosepull_requesttrigger is filtered topinvou3-clean, runs there.Review-fix wave (
ac2ecca64)A fresh from-scratch review pass of the re-land found and fixed three things:
evaluate()clamped the loop wait to the 200ms catch-up cadence unconditionally, so the doubling andPARKED_POLL_CAPnever took effect. The clamp now applies only on the non-parked path, and the parked cap wait excludes the already-closed windows, so the aggregate budget — not the open window alone — bounds the sleep. Deterministic pins now cover the cadence (EVENT_CATCHUP_POLL * 4at 2s parked), the 2s ceiling, and the aggregate-budget wake; each fails under its mutation.human_wait_timeout. It now sits in the pending chain after them (pins assert both outrank a spent cap). The window-closing event arm is also gated like the opening arm, so unanswerable surfaces no longer emit unpairedHumanWaitEnded.TaskManagerConfig::human_waits_answerable(defaultfalse, the TUI's safe default) replaces the positional bool; the runtime API opts in viaserver_task_config, which pins both the default (from_runtime_defaults_to_unanswerable_human_waits) and the opt-in (server_task_config_opts_tasks_into_answerable_human_waits).Docs: SUBAGENTS.md / zh also disclose the worker-hold cost — a parked task keeps its worker slot, so several simultaneously parked tasks can stall the durable-task queue until their answers or the fail-safe cap.
Review-hardening wave (
320307876)A second from-scratch review pass (eight domain reviewers plus independent mutation re-verification) confirmed the behavior and tightened the claims:
execution_guard_spent_aggregate_cap_terminalizes_between_windows, which does go red under that mutation (re-verified).EVENT_CATCHUP_POLLorPARKED_POLL_CAPoff their documented 200ms / 2s values fails them instead of silently tracking the constants (both drifts re-verified red).evaluate()comment now names both wake paths (the turn loop's event subscription and the supervisor's forwarded task event), and the human-wait event/config docs state that a custom executor emitting theHumanWaitStarted/Endedpair self-declares its waits answerable, independent ofhuman_waits_answerable.No-Issue: re-land of the human-wait half of the #75 fix wave; no separate tracking issue exists.
Signed-off-by trail: every commit DCO-signed.
Wiring-pin wave (
b11a92cdb)The fresh review's one MAJOR: the server opt-in had no pin below the config builder, so reverting
run_http_server's call site or dropping the flag's forward intoEngineTaskExecutorkept the whole suite green — the one mutation the red-green standard did not cover. Fixed by movingrun_http_server's task-runtime wiring intoopen_server_task_runtime, the single wiring point, and pinning it withserver_task_runtime_wires_answerable_human_waits_into_the_executor, which reads the flag where it lands — the executor field the manager runs tasks on — through the same entry the server itself uses. Verified by mutation: inlining the builder there and hardcoding the forward tofalseeach turn only this pin red (the builder pin stays green, it never saw those hops); dropping the builder's assignment turns both red. Head is nowb11a92cdb, stacked as before.