[release] v0.115.0 - #6571
Merged
Merged
Conversation
The records stream worker added every decoded Redis message id to its acknowledged list during deserialization, before it attempted the Postgres write. A failed `append_many` logged an error and continued, and the shared consumer loop then acknowledged and deleted those messages from the stream. Every Postgres failure was therefore permanent, silent record loss, and the worker reported success while doing it (#5496). `append_many` is one statement in one transaction, so one record Postgres rejected also took its whole batch with it, losing up to fifty unrelated records per rejection (#5594). Three changes: - `process_batch` returns a message id only once its rows are committed, or once the worker has decided to drop it on purpose (undecodable, or over quota). A failed entitlements check now defers instead of dropping, because an unreachable meter is transient. - A failed group is rewritten one record at a time, so a rejected record no longer discards the rest of its batch. - `StreamConsumer` gains an opt-in reclaim pass. `read_batch` only ever asks for `>`, so without it an unacknowledged entry is invisible to every later read and "leave it pending" would still lose the record. The pass claims the group's pending entries, and drops one after `max_deliveries` failures with an error log naming the lost record. The drop applies only while other records are committing. The delivery counter cannot tell a rejected record apart from a database that is down, so dropping on the count alone would delete every record in flight once an outage outlasts the budget. A live run against a real Redis found that hole; the guard closes it. The reclaim pass is off for the tracing and events workers, so their behaviour is unchanged. Verified against a real Redis 8: five records published during a twenty second write outage stayed pending, then all landed on recovery with no duplicates and an empty stream; a permanently rejected record let its batch mates through and was dropped loudly once traffic resumed. Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
`RecordsRetentionDAO.delete_records_before_cutoff` selected and deleted on `RecordDBE.id`. That attribute does not exist. The records key is `(project_id, record_id)`, so every call to the retention flush raised before it deleted anything and records have never been aged out. Scope added on purpose: this defect is clear, obvious and one line, it sits in the records durability area this branch already touches, and Spike D found it while auditing the same pipeline. It is kept in its own commit so it can be reverted or landed alone. Verified: `hasattr(RecordDBE, "id")` is False, the primary key constraint at `dbes.py:18` is `(project_id, record_id)`, and the corrected statement compiles against the Postgres dialect. Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
Port the durable-cancel spike's 13-cell driver (refresh_live.py) into the release-gate skill as resources/session_control.py, per qa-audit-2026-09-03.md section 4, so the standing regression check survives outside one evidence folder. Matches the gate's env contract (AGENTA_BASE, AGENTA_ADMIN_KEY, QA_OPENAI_API_KEY, no file fallback), moves the Docker/Postgres-only helpers behind an OperatorHooks interface so six cells run over HTTP against any deployment and the rest SKIP by name without --project, emits the gate's PASS/FAIL/SKIP result shape into a timestamped ~/agenta-qa-evidence/ run folder, and adds --resume so a lost agent costs one cell, not the run. Adds two new cells (repeat-stop, stop-during-completion) from qa-audit section 3, a path_triggers.py rule that makes the suite mandatory for session-code changes, a SKILL.md section naming the command and the model-key locations, and a pytest-and-standalone-runnable unit test for the pure parts (cell registry, hooks skip path, resume, verdict shape, env resolution). Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
The live smoke run against the integration stack showed /sessions/{id}/cancel
returns 202 Accepted (a pending command plus a stopping execution), the
correct async-acceptance status. The verdict checks in stop-warm,
stop-approval, repeat-stop, and stop-during-completion hardcoded 200 and
FAILed every real Stop. Accept 200 or 202 in each.
Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
…ontrol.py Add HARNESSES["claude"] (kind claude, model sonnet, provider anthropic, vault connection) so the session-control cells can drive the Claude Code harness, and stock an Anthropic provider key into the bootstrapped account's vault the same way the OpenAI key is stocked, gated on --harness claude so a pi_core/codex-only run does not need ANTHROPIC_API_KEY set. Also widen wait_for_turn/wait_for_tool by a configurable SANDBOX_STARTUP_SLACK_S (25s) when --sandbox daytona is selected, since a Daytona sandbox takes 10 to 20s to start on top of local timings. Record the session's distinct sandbox ids (via /sessions/turns/query, HTTP-only) in every HTTP-only cell's evidence as sandbox_ids / warm_same_sandbox, so a resume that silently rebuilt the sandbox is visible in the result instead of only in the recalled codeword. Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
The Daytona smoke run FAILed stop-approval with only "resume did not recall the codeword" and no reply text to check why, so a driver replay bug (the reconstructed output-denied tool part) could not be told apart from a genuine product miss. Add resume_text, resume_frames, and resume_errors to the cell's evidence. Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
Two invocations started in the same second (e.g. Claude Code and Daytona smoke runs fired in parallel tonight) shared a run folder, since the timestamp alone has 1-second resolution -- the second writer silently overwrote the first one's results.json mid-run and one run's evidence was lost until recovered from its redirected stdout log. Add the PID to the folder name so concurrent invocations never collide. Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
…imeout
assistant_message() indexed turn["segments"] unconditionally. When the
driver's own wait for a turn times out (handle["out"] stays None,
observed when the runner is unhealthy after a restart), the cell passed
an empty {} dict in and the KeyError masked the real signal, which is a
driver-side timeout rather than a cell result.
Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
Add cell_runner_gone, ported from cell_runner_gone in refresh_live.py, as cell "runner-gone" in the session-control driver's registry. It restarts the runner right after a Stop is claimed, then checks that the sweep settles the command as lost (not claimed) in session_commands, the session_streams row reads is_running: false, and a Send sent after that runs. Register it in CELLS and in the registry's stable-names unit test, and update SKILL.md's cell count and Docker-needing cell list. Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
…ncurrent-stops Add OperatorHooks.ensure_runner_healthy(), implemented in DockerComposeHooks, that unpauses, restarts, and health-checks the runner as needed. Extract the per-cell execution in main() into run_cell(), which calls it in a finally block after every needs_hooks cell, so a cell that raises before its own restore code runs (as cell_stale_tail did tonight, leaving the runner paused) cannot strand the runner for the next cell. Also wrap cell_stale_tail's pause/unpause and cell_records_outage's stop/start Postgres in their own try/finally, so each cell restores what it touched even on an exception in between. Add cell "concurrent-stops": five sessions started at once with a long turn, Stop sent to all five within about a second, each expected to return HTTP 202, settle exactly one terminal record, and recall its own codeword on a warm resume. HTTP-only, no hooks needed. Add unit tests for run_cell's finally path (NullHooks skips the recovery call without crashing; a stub hooks object confirms the recovery call fires when a cell raises, and is skipped for a cell that does not need hooks) and add "ensure_runner_healthy" to the NullHooks-raises coverage. Update SKILL.md's cell count and lists. Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
… both races Split cell_runner_gone into two: the new cell_runner_gone pauses the runner BEFORE sending the Stop, so the command can never be claimed or reported and must settle lost off a deterministic sweep, with an explicit check for the watchdog's execution_lost ending. cell_runner_gone_late keeps the old restart-after-stop timing, which mostly loses that hard race because the runner often reports the Stop's outcome before it actually dies. Both races satisfy the same invariant: exactly one effective terminal outcome, no command left pending or claimed, is_running false, and the next Send succeeds. Factor that shared PASS rule into _judge_runner_gone(), used by cell_runner_gone_late (cell_runner_gone keeps its own stricter assertion since pausing first is meant to force the lost/execution_lost shape every time). Both record which race landed on evidence["race"]. Register runner-gone-late in CELLS and the registry's stable-names test. Update SKILL.md's cell count and Docker-needing cell list. Claude-Session: https://claude.ai/code/session_01GAqSs7fw6QRi2n1ZJ2tmAV
Document owner routing limits, transport requirements, fail-closed admission, cleanup continuity, and sanitized test evidence. Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Treat post-cancel process reaping as best effort without changing the harness-confirmed cancellation outcome. Log cleanup misses for QA and cover failed and unknown reap paths through parking and continuity. Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Keep the recovery slot when a newer composer draft blocks restoration. Consume it only after refused text and staged attachments are placed safely, with regressions for the occupied-composer case. Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Document that Codex cleanup misses are QA evidence rather than a teardown signal. Keep settled Stop parking and continuity under the 600-second stopped-session window. Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Apply the repository Prettier style to the inherited test file so the TypeScript format check passes. Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Move a refused send into a conversation-local holding slot before a newer submission can replace the queue recovery value. Restore its text and staged attachments once the composer becomes empty, without submitting it automatically. Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
Capture the refused send into the conversation-local holding slot before scheduling editor placement. Cover the interleaving where a newer submission replaces the queue recovery value before the frame runs. Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
fix: address CodeRabbit review for session control
Expose the fixture-generated database name to the concurrency test. Connect the raw sweep and observer clients to that isolated database. Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
[fix] Point the watchdog collapse test at its own database
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
[feat] Session control milestone 1: warm Stop, durable Stop, recovery
Contributor
Author
📘 Docs preview
This comment updates in place on every push. |
Contributor
Author
Railway Preview Environment
Updated at 2026-09-05T17:11:29.194Z |
…6577) * fix(api): preserve flag-off session defaults Keep the pre-milestone watchdog threshold and visible late output when durable Stop is disabled. Retain watchdog terminal settlement and the stricter durable Stop behavior. Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk * fix(frontend): settle acknowledged legacy stops Abort the local stream as soon as the flag-off legacy API confirms Redis displacement. Keep durable Stops waiting for terminal evidence and prevent the legacy path from entering the 30-second retry state. Claude-Session: https://claude.ai/code/session_0164kzT6ttwpBtzvcDC6YzYk
[fix] Enable durable Stop by default
mmabrouk
approved these changes
Sep 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
New version v0.115.0 in