This repository was archived by the owner on May 26, 2026. It is now read-only.
feat(KR-P2-CLEANUP ST1): wire READY↔ACTIVE transitions into SeaTicketPoller#55
Merged
Merged
Conversation
…Poller KR-P2-I-integration ST4 (deferred from #34) — now unblocked by KR-P2-E shipping the poller. The full operational-state machine moves automatically as the consumer loop turns: - On successful claim + ledger.allocate (work fully committed) → holder.transition_to(ACTIVE, trigger="claim acquired") - On successful kora__release_claim → holder.transition_to(READY, trigger="claim released") - On lease-lost-mid-work early exit (heartbeat.lease_lost) → holder.transition_to(READY, trigger="claim released") — same target arrow, since the claim is gone substrate-side; trigger string is free-form for the chain-event payload Fail-soft per spec §2 ST1. A new _signal_operational_state helper wraps each transition: - get_holder() returns None (no agent session has run wire_operational_state yet) → DEBUG-log + skip; consumer loop still claims + releases cleanly - holder.transition_to raises → WARN-log with target + trigger; cycle continues Operational-state observability is best-effort during the consumer loop; substrate-side records (sea_ticket.claimed_for_edit / sea_ticket.claim_released + the existing kora.sea_ticket.resolved emit from ST4) remain the durable trail. Tests (tests/test_sea_ticket_poller_operational_state.py): 4 cases covering the full cycle (ACTIVE then READY fired in order with the right trigger strings), the lease-lost branch (still transitions back to READY even though release wasn't called), and both fail-soft paths (holder-uninitialized: DEBUG log + cycle still runs; transition_to raises: WARN log + cycle still completes). After this PR lands, kora.operational_state.transitioned chain events fire on every claim/release cycle when the holder is initialized (by the first agent session). Gateway-only deployments without agent sessions stay observationally quiet — that's the acceptable trade per spec. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 21, 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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Summary
KR-P2-CLEANUP ST1 of 5. KR-P2-I-integration ST4 (deferred from #34) — now unblocked by KR-P2-E shipping the
SeaTicketPoller. The operational-state machine moves automatically as the consumer loop turns.Transitions
ledger.allocate(work committed)"claim acquired"kora__release_claim"claim released""claim released"Both the normal-release path and the lease-lost path land back at READY since the claim is gone from the runtime's perspective. The
TRANSITION_TABLEonly has oneACTIVE → READYarrow (trigger column ="claim released"); the trigger string is free-form for the chain-event payload, so both paths share it.Fail-soft per spec §2 ST1
A new
_signal_operational_state(target, trigger)helper wraps each transition:get_holder()returnsNone(no agent session has yet runwire_operational_state, so the holder is uninitialized) → DEBUG-log + skip. Consumer loop still claims + releases cleanly.holder.transition_toraises → WARN-log with target + trigger; cycle continues.Operational-state observability is best-effort during the consumer loop. Substrate-side records (
sea_ticket.claimed_for_edit/sea_ticket.claim_released+ the existingkora.sea_ticket.resolvedemit from KR-P2-E ST4) remain the durable trail.Tests —
tests/test_sea_ticket_poller_operational_state.py(340 LOC, 4 cases)[("ready","active","claim acquired"), ("active","ready","claim released")]fired in order; holder lands back at READY.kora__release_claimnever fires substrate-side. Only one MCP call (the claim)."holder not initialized"lines acknowledge the skip.transition_toraises: substrate cycle still completes both calls; two WARN-log lines (one per attempted transition).Tests are isolated in their own file (
test_sea_ticket_poller_operational_state.py) — they useinit_holder+ per-test_reset_holder_for_testsrather than mocking, so the singleton's first-wins semantics don't bleed across tests. The existingtests/test_sea_ticket_poller.pystays unchanged.Honest scope
wire_operational_state(provider)is called fromagent/agent_init.pyper-session, not fromsea_ticket_poller_lifecycle.py. Gateway-only deployments (poller running, no agent sessions yet) stay observationally quiet — that's the spec's accepted trade. A future "init holder at gateway startup" change is its own bucket (and would need to dedup the per-session listener to avoid N-times emit).agent/operational_state*modules.kora.sea_ticket.resolved/kora.operational_state.transitionedevent payload shapes.Sub-task chain (this bucket — all independent)
…st1-poller-wire…st2-sea-panel-flip…st3-control-panel-flip…st4-boot-panel-flip…st5-ops-tests-fixEach ST branches from main independently — no cascade-merge dance.
Test plan
pytest tests/test_sea_ticket_poller.py tests/test_sea_ticket_poller_operational_state.pyevent_logshowskora.operational_state.transitionedevents withpayload.trigger ∈ {"claim acquired", "claim released"}interleaved withsea_ticket.claimed_for_edit/sea_ticket.claim_released🤖 Generated with Claude Code