Repository navigation
exec: v1.4 notify, revised: callbacks never open a session, live reconcile, job ledger, sweep - #395
Merged
Merged
Conversation
…r, sweep v1.4 now changes how a replica learns of a job's end and nothing else: a notified end is handed to the session exactly as a streamed one, wake turns included, in every mode. The rule that kept a session from starting a turn with no client attached is gone. - The edge attaches a callback grant carrying "purpose": "job_ended". A replica refuses anything but a job_ended POST under it, decided from the verified grant, and a replica that predates the claim fails it closed. One header grammar, shared by edge and replica; look-alikes are refused. - Two reconciles: the open reconcile (today's read-back) and a live reconcile that only lists and reads, acts only on released jobs and a current-generation proof, and never kills, replaces, adopts or re-keeps. Re-keeping happens only on adoption at open, so no keep/proof loop. - `exited` is observable only after a live group member was found past the shell's exit; the baseline writes `exit` only then. - A durable per-session job ledger, committed atomically with the transcript and outside it: records precede keep, ends are marked reported in the commit that carries them, reopen reports missing jobs lost, and a scope owner record wipes a dead conversation's jobs before a new one uses it. - job_ended is served mid-run, answered from its own reply path, never counts as a client, and never extends the idle clock. - Fleet faults (no key, failing callback grant, header disagreement, unknown kid) answer 503, never a stopping status. Upcoming and retired notify keys, kid collisions refused, a rotation order. - The proof is sent by the job's supervisor, the same sender as the end; keep never waits for it; a keep's generation rides in the url so a stale proof counts for nothing, and a new keep ends older notifications. - A 5-minute sweep while live bounds a lost end; idle connections close. Fixtures and the coverage test follow: a reference receiver and edge classifier recompute every callback case from the spec's own steps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk
A session that is closed, reaped or moved learns of its jobs' ends when it
next opens, as today, so a callback matters only to the replica holding the
session open now. The edge routes a callback to the hashed replica and
attaches, mints and verifies nothing; the url names the tenant, and the
replica verifies the token itself before it looks for the session. A replica
holding the session runs the live reconcile; any other answers 200 with
{"listener": false} and does nothing: no open, no storage write.
Gone with it: the callback grant and its purpose claim, the edge's tenant
lookup and grammar pre-check, its rollout gate, callback-opened sessions, the
ledger's records-before-keep, lost reports and scope owner. The ledger keeps
only what stops a crash reporting an end twice. The edge never walks the
ring for a callback. Takeover is stated: the new owner re-keeps at its open
reconcile, a callback at the old owner gets no listener, and the sweep
covers a session that stops being its hash target.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk
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.
Revises the exec v1.4 "Sandbox job notify" spec (
crates/agent/src/exec_endpoint.rsmodule doc), FLEET.md "Job-end callbacks", thetests/fixtures/exec/v1.4-*.jsonvectors andtests/exec_notify_vectors.rstogether. This is spec only: nothing implements v1.4 yet. Requirements are renumbered N1–N64 and E1–E6, and every one names a check inv1.4-checks.json, with the task that writes it.The two rules everything else follows
v1.4 changes how a replica learns of a job's end, and nothing else. A notified end is handed to the session exactly as a streamed one (N42): the same notice, through the same path, wake turns included, whether or not a client is attached, in every mode. The old rule against starting a turn with no client attached is removed from the spec, the fixtures and the checks. The invariant is now I2: a job end learned by notify behaves exactly as the same end learned by the held stream.
A callback never opens a session. Today, a session that is closed, reaped or moved learns of its jobs' ends only when it next opens and reads its sandbox's jobs back. So a callback only matters to the replica that has the session open right now.
200with{"listener": false}and does nothing: no session is opened and nothing is written to storage (N31). This answer is terminal for the attempt.job_notify::a_reaped_session_is_as_today.What changed, problem by problem
An unauthenticated request could reach full session access.
job_ended(N28–N30).Authorizationheader, a case-insensitive scheme, one or more spaces. A request that only looks like a callback is answered400(N28, E1).The reconcile was destructive in a live session.
bash_sandbox::read_backkills unkept jobs and replaces table entries.listandread. It acts only on released jobs, and on aprovingjob whose callback carries its current generation. It never kills, adopts, replaces, re-follows or re-keeps.False leftovers notices.
exitedis observable only once a live group member has been found after the shell exited. That is the same condition under which a stream sendsshell_exit(N13).exitonly in that case. For jobs started by an older supervisor,listchecks that the group is still alive (N14).Exactly-once reporting.
keep: that only existed to reportloston callback-opened or crashed sessions. Today's reopen path (open reconcile plus reopen note) already handles those, and a reaped session must behave as today.lostfor jobs missing from the listing: a session that isn't open must behave as today, and today's reopen note already reports such a job as gone.job_endedwas answered "busy" during a run.503counts only if it too arrives in the window (N48).Callback replies were matched by job id.
Fleet faults stopped the sandbox for good.
503, never a stopping status:407,408and425are retried.401, as it answers any request without a grant. That loses nothing: such a replica holds its own streams, and a v1.4 owner's sweep covers a callback that went astray. So no rollout order is needed (FLEET.md).Callback-opened sessions and the reaper.
The proof might not take the path the end takes.
setsidsupervisor, signalled bykeep.keeprefusesnotifyfor a job whose supervisor carries no notifier (N9).keepcould wait on its own callback.keepnever waits for an attempt (N10). Attempts are made outside the process group that v1.1 kills.An end could be lost while a client watched.
listevery 5 minutes is sixty times less traffic than a 5 s heartbeat ("What release costs").A replacing keep left old notifications running.
keepcarries a fresh generation in the URL (&keep=<16 hex>), and only a callback carrying the latest generation counts as the proof (N52).keepends earlier notifications and abandons any attempt still in flight (N6, N22). Tokens stay deterministic (N56).Takeover (stated explicitly in "Crash, takeover and rotation"):
Recreated sessions and
new_session.Smaller fixes:
404/401existence oracle is gone too: a forged token is refused before the session is looked up (E3 removed).410means the open session neither tracks the job nor has recorded its end.Verified
cargo test -p beyond-ai-agent --test exec_notify_vectors: 10/10 pass. The new test fails against the old fixtures.data.listener, and whether the sandbox is contacted. No case can open a session. A reference edge classifier checks the edge cases.cargo fmt --check, clippy (--all-targets --features code-mode -D warnings),dprint checkandcheck-module-tablesare clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk