Repository navigation
Conversation
* docs(fork): the crashed-actor session files name kagent-dev#3255 * docs(fork): before you carry a patch, place it and name its upstream exit (#762) Signed-off-by: QuentinBisson <quentin@giantswarm.io> --------- Signed-off-by: QuentinBisson <quentin@giantswarm.io>
…e-clone-origin * origin/giantswarm: docs(fork): carry codex in its session directory with the placeholder credentials fix(harness): codex works in its session directory with the placeholder credentials docs(fork): drop the never-filed theme slug (#764) docs(fork): the admission row names the e2e of the signed grant refusals chore(e2e): refuse an expired, another person's and another volume's grant feat(egress): actor-JWT hosts only during a turn docs(fork): carry the egress trust bundle's public roots fix(substrate): the egress trust bundle carries the public roots docs(fork): the crashed-actor session files name kagent-dev#3255 (#760) docs(fork): Session rows 209/210 upstream branches on main 2261632
jjamroga
left a comment
There was a problem hiding this comment.
I have some general feedback on the solution here. An actor could crash for any number of reasons, but graceful recovery is reasonable.
We should introduce a feature (flagged, off by default) which allows an Agent to automatically recover
- This is primarily for delivering a good experience for machine clients (other agents, microservices, etc)
- Not sure whether this should be system wide or on an agent by agent basis (or both)
- Clients will likely retry when experiencing unavailability, and that's wasted work if the Agent can't recover itself
- This is feature flagged because recovery can be complex, and we want some preliminary feedback before making this default behavior
When disabled (default), the user should be given an option on how they want to recover in chat
- There are a few ways to recover: Revert? Fork from last good state?
- If an agent is supposed to automatically recover, they will obviously not be given an option
After rolling back to last good state, users should be made aware of messages that failed to send
- This does not mean we need to revert our A2A task state, just like a text message we should be showing "failed to send"
… not a YAML comment (#765)
… not a YAML comment (#765)
4e27ee9 to
72aaed6
Compare
|
Addressed the recovery feedback in 1c17482 and 72aaed6, and rebased onto main with the snapshot/lifecycle conflicts resolved.
The full PostgreSQL store/migration race suites, Go lint, protobuf generation/compatibility, E2E compilation, and focused UI tests/typecheck pass locally. The final broader backend run and current-head CI are still pending. The PR description now reflects this scope and distinguishes the local controlled-runtime checks from live CI E2Es. |
|
Taking @jjamroga's comments one step further. Can we actually stage/stack this functionality? This is a very big PR. Can we start with manual revert and then layer auto-revert on top? |
^^ I'm switching this back to draft and getting this done today. |
72aaed6 to
de60476
Compare
|
Split update for the staging request in the review: the implementation now has six open, non-draft PRs. Each dependent PR targets its immediate predecessor.
Parts 2–6 are dependent review previews in the contributor fork. They need new upstream PRs as their predecessors merge; they are not upstream merge targets yet. The fork cannot supply base branches to this upstream repository. The split preserves the requested behavior:
Local validation passes for the affected PostgreSQL/migration and backend race suites, Go lint, protobuf compatibility, UI behavior tests, typecheck, and lint. The UI layers have 82 and 83 passing behavior tests, respectively. Practical validation now also uses an isolated KIND cluster with Substrate 0.6.0 and controller/Go ADK images built from the stack. Real API/browser checks passed for checkpoint forks, recovery after a lost final Revert response, retained HITL waits, opt-in automatic recovery, and exact failed-message indicators after reload. Adversarial review is complete for the current six heads. Three reproduced defects still need fixes:
All three have real KIND/API evidence, with browser screenshots for the UI case. The concurrency probe delays one genuine Substrate response through a local instance of the production SessionService; it uses the real database, Actor and Go runtime. No persisted state or API response payload is fabricated. The native Claude/Codex runtime matrix was not rerun. No fixes for these findings have been pushed, and the six application worktrees remain unchanged. Current upstream CI is not green: CI Build and Migration Immutability still report |
| if err != nil { | ||
| return notFoundOr(err) | ||
| } | ||
| matches, err := queryOne(ctx, tx, `SELECT actor_uid = $2 FROM session WHERE id = $1`, pgx.RowTo[bool], row.ID, actorUID) |
There was a problem hiding this comment.
Can we capture the current operation generation before reading Substrate and compare it under this lock? Revert A can observe CRASHED, pause while Revert B succeeds and a new turn starts, then pass both this Actor UID check and READY/NONE. It fails the new task before discovering that the Actor is RUNNING, leaving the Session FAILED/RESUME. This remains reachable after #3409 through ordinary ingress wake-up.
There was a problem hiding this comment.
Done in 7490323. Revert now reads the Session's lifecycle generation, its latest operation_id, before it calls Substrate. BeginCrashedSessionRecovery compares it under the Session lock. A revert that finished in between makes the stale admission return ABORTED, and the new turn keeps running. TestCrashedRecoveryAdmissionRespectsFences covers the store, and TestExplicitRevertRejectsCrashObservedBeforeAnotherRevert in #3395 replays your sequence. Without the check, that test fails the new task and then stops on the RUNNING Actor.
| busy, err := queryOne(ctx, tx, ` | ||
| SELECT EXISTS (SELECT 1 FROM session_checkpoint WHERE source_session_id = $1 AND state = 'CREATING') | ||
| OR EXISTS (SELECT 1 FROM session_task_event WHERE history_id = $2 | ||
| AND quiescence_pending AND quiescence_executor_id IS NOT NULL) |
There was a problem hiding this comment.
A crash during ordinary post-turn suspension leaves an issued quiescence claim here. quiesceIdleSession retains it on error, ClaimSessionQuiescence cannot reclaim it, and this guard keeps rejecting recovery even after a controller restart. Can we reconcile that issued outcome against the recorded Actor's confirmed crash, then retire the obsolete claim atomically with recovery admission? We still need the exclusion while suspension is genuinely unresolved.
There was a problem hiding this comment.
Done in 7490323. Once the recorded Actor's crash is confirmed, admission no longer waits on an issued idle suspension, because that suspension cannot finish against a crashed Actor. Settlement retires the claim in the admission transaction. A late FinishSessionQuiescence for it finds no pending work and records nothing. The generation check keeps the crash observation current. The claim still excludes task writes, checkpoints, expiration and lifecycle operations, and checkpoint creation still excludes recovery. TestCrashedRecoveryRetiresIssuedIdleSuspension covers the retirement, the late finish and the next turn.
de60476 to
0227fc4
Compare
Signed-off-by: Ada Lundhe <adalundhe@lundhe.audio>
0227fc4 to
7490323
Compare
Summary
Add the storage and Substrate operations needed by manual Session recovery.
Stack
Split requested in the review discussion.
Part 1 of 6. 6 files, +544/-45 against the immediate parent.
Parts 2–6 form a GitHub stack on
main, so CI runs on each part. A stack cannot include a fork branch, so part 2 targetsmainand also shows part 1's commit until #3255 merges. Parts 3–6 target their immediate parents for a focused diff. The stack is rebased ontomainafter #3409, which removed the public Session suspend and resume controls.Testing
On
mainafter #3409, the fulldatabaserace suite passes against PostgreSQL, and full Go lint reports no issues. Tests cover settlement, retained history, admission fences, stale crash observations, interrupted idle suspensions, replay idempotency and rollback. Reverting either review fix makes its new tests fail.Parts 2–6 form a GitHub stack on
main, so upstream CI runs on each of them.Upstream CI runs on this part, which targets
main.