feat(registry): add a starting state with a single activation path - #93
Open
maxlamagna wants to merge 1 commit into
Open
feat(registry): add a starting state with a single activation path#93maxlamagna wants to merge 1 commit into
maxlamagna wants to merge 1 commit into
Conversation
Groundwork for an opt-in readiness gate. An instance may now be registered as
`starting`: it receives the identity details a wrapper needs in order to
launch, but it is not yet active.
- `register(ready_gate=True)` enters `starting`, and `mark_ready` is the only
path to active, so an instance cannot drift into active by another route.
- `mark_starting` re-gates an existing starting or active instance, for
restarts.
- `claim` refuses a starting instance, so a second process cannot take an
identity that is still coming up.
- `cancel_starting` removes the instance with no reservation and no reclaimable
entry, via a shared `_remove_instance` primitive, so the family rename-back
bookkeeping runs on both removal paths. It returns that removal record rather
than a bare bool, so a caller can migrate chat identity and history on a
rename-back exactly as it already does for deregister.
- `expire_crashed` is the crash sweep's removal. The two cases need opposite
treatment — a starting instance is cancelled so a relaunch reacquires the bare
name and its token dies, anything else stays reclaimable so a sleeping machine
recovers — and that choice is state-dependent, so it is made under the same
lock that performs the removal. Choosing outside it is a race: a wrapper
re-gating from active to starting in the window would still be deregistered
reclaimably, reserving its name and leaving a revivable token.
Token recovery preserves `starting`. On startup every persisted instance is
reloaded as reclaimable and a live wrapper transparently recovers its identity
through resolve_token; that path previously forced every recovered instance to
active, so restarting the server would have opened the gate for an agent whose
CLI was never proven ready. Recovery of an active instance is unchanged.
Inert without `ready_gate=True`: existing registration behaviour is unchanged,
which is why this can land ahead of the server, UI and wrapper pieces that
use it.
The existing removals are untouched: `deregister` and `cancel_starting` still
return exactly `{'ok': True}` plus an optional `_renamed_back`. The `cancelled`
key belongs to the state-dependent expiry alone, and the by-state mode chooses
both branches itself rather than inheriting them from the caller.
21 unit tests, written red-first, each guard verified by a mutation that
removes it. Two cover the expiry race specifically: a barrier proving a
concurrent re-gate cannot complete while the removal holds the lock, and one
that fails any implementation which decides from a state read before the
removal rather than during it. A further set pins the exact return shape of
every removal, so the gate cannot quietly widen an existing contract.
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.
First of three for #90. Relates to #90.
Groundwork for the opt-in readiness gate described in #90. Inert without
ready_gate=True, which is why it can land ahead of the server, UI and wrapper pieces that use it.register(ready_gate=True)entersstarting, andmark_readyis the only path to active, so an instance cannot drift into active by another route.mark_startingre-gates an existing starting or active instance, for restarts.claimrefuses a starting instance, so a second process cannot take an identity that is still coming up.cancel_startingremoves the instance with no reservation and no reclaimable entry, through the same removal primitive asderegister, so family rename-back bookkeeping runs on both paths. It returns that removal record so a caller can migrate chat identity on a rename-back.expire_crashedis the crash sweep's removal. A starting instance is cancelled; anything else stays reclaimable so a sleeping machine recovers its identity. Because that choice is state-dependent it is made under the same lock as the removal — deciding outside it lets a wrapper re-gate in the window and take the wrong path, which strands its name and leaves a revivable token.starting, so restarting the server cannot activate an agent whose CLI was never proven ready.The existing removals are untouched:
deregisterandcancel_startingstill return exactly{'ok': True}plus an optional_renamed_back. Thecancelledkey belongs to the state-dependent expiry alone.Tests: 21 unit tests, written red-first, each guard verified by a mutation that removes it — including a barrier proving a concurrent re-gate cannot complete inside the removal, one that fails any implementation deciding from a state read before the removal, and a set pinning the exact return shape of every removal.
Ordering. Two further PRs complete #90 and will be opened as each parent merges, so that each shows only its own work rather than repeating this one:
startingstate#91 also fixes an unrelated intermittent 502 in
ProxyHeaderForwardingTeststhat can make a full-suite run fail at random; merging it first makes this stack read cleanly.