This repository was archived by the owner on May 26, 2026. It is now read-only.
feat(KR-P2-I-integration ST5): flip /api/operational-state stub → real + add history ring#35
Merged
rafe-walker merged 1 commit intoMay 21, 2026
Conversation
7d571d8 to
244f373
Compare
…l + add history ring
OperationalStateHolder (ST1 module) now retains the last 10
transitions in an in-memory deque ring buffer:
- TransitionRecord dataclass carries (timestamp, from_state,
to_state, trigger).
- Appended under the holder lock at the end of transition_to
(after the held-state swap) so ordering matches the swap.
- holder.history(limit=N) returns a list of dicts ready for the
/api/operational-state JSON payload.
- Bounded at 10 — durable history lives in the chain-event log
(every transition writes kora.operational_state.transitioned
via ST2's emit listener); this ring is for the admin panel's
immediate-history view and dies on process restart.
kora_cli/web_server.py — endpoint flipped:
- Now reads agent.operational_state_holder.get_holder().
- When holder is initialized: returns the live state with
transition_history sourced from holder.history(limit=10),
valid_next_states from transitions_from(state.primary_state),
and the ``stub`` flag DROPPED — CC#2's admin panel auto-stops
rendering its stub banner.
- When holder is None (boot incomplete or IsoKron provider
failed): returns the stub-shape with stub:True PLUS an
``error`` field naming the cause, so the panel can distinguish
"no wire-in yet" from "real runtime is up".
Tests (tests/test_operational_state_endpoint.py): 8 cases covering
the ring mechanics (append, bound at 10, limit clamp, ISO-8601 UTC
timestamp, TransitionRecord shape), the uninitialized-holder
endpoint branch (stub + error), the live endpoint branch (no stub
key, payload keys match pre-flip shape, transition_history and
valid_next_states populated from the holder + table), and the
sorted-degradation_reasons cockpit-diff stability invariant.
Stacked on ST3 (#34); base is feat/kora-KR-P2-I-integration-st3-provider-wire.
Retarget to main once ST3 merges. ST4 (SeaTicketPoller wire-in)
is independently blocked on KR-P2-E ST1 having landed — STOP gate
filed in chat, not in this PR's scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6822ecd to
f35ae85
Compare
This was referenced May 21, 2026
Merged
Merged
rafe-walker
added a commit
that referenced
this pull request
May 21, 2026
… to PR #35 two-branch shape (#64) Full rewrite around PR #35's stub-true-when-uninit / live-when-initialized response shape. New autouse _reset_holder_for_tests fixture (the missing piece that caused the original 4 intermittent failures — holder singleton survived across tests). Tests-only, no production edits. Closes task #191 + the 4 pre-existing failures.
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-I-integration ST5 of 5. Flips
/api/operational-statefrom the hardcoded stub to a live read of theOperationalStateHolder, and adds the in-memory transition-history ring that the admin panel renders. Stacked on top of #34 (ST3) — base isfeat/kora-KR-P2-I-integration-st3-provider-wire. Retarget tomainonce ST3 merges.Two changes in this PR
1.
OperationalStateHolder— transition history ring bufferTransitionRecordfrozen dataclass:(timestamp, from_state, to_state, trigger).to_dict()matches the endpoint'stransition_historyrow shape._history: deque[TransitionRecord]withmaxlen=10. Appended under the holder lock at the end oftransition_to(after the held-state swap) so ordering matches the swap.holder.history(limit=10)returns a snapshot list of dicts.kora.operational_state.transitionedvia ST2's emit listener). The ring is for the admin panel's immediate-history view and dies on process restart, matching Hermes-fork session-state semantics.2.
/api/operational-state— endpoint flipget_holder()is initialized: response carries the live state.transition_historyfromholder.history(limit=10);valid_next_statesderived fromtransitions_from(state.primary_state); thestubflag is dropped. CC#2's admin panel auto-stops rendering its stub banner when the field is absent.get_holder()returnsNone(boot incomplete, or the IsoKron provider failed to construct): response uses the stub-shape withstub: TruePLUS anerrorfield set to"OperationalStateHolder not yet initialized". The panel can render this distinctly from the cold-stub banner so operators can distinguish "no wire-in yet" from "real runtime is up."Tests —
tests/test_operational_state_endpoint.py(266 LOC)8 cases:
Ring buffer mechanics
transition_toappends the (from, to, trigger) record under the lockhistory(limit=N)clamps the snapshot size; asking for more than we have returns allTransitionRecord.to_dict()shape matches the endpoint dict%Y-%m-%dT%H:%M:%SZ) — round-trippableEndpoint flip
stub: True+errorfield + empty history/next-statesstubkey, noerrorkey, live state surfaces correctlydegradation_reasonsrendered alphabetically (cockpit diff stability)Honest scope
flyctl proxy 9119:9119or equivalent local run)." I haven't touched the panel; if any banner stops rendering correctly after this lands, that's a CC#2-side fix.Sub-task chain (this bucket)
mainand no open PRTest plan
pytest tests/test_operational_state_endpoint.pyflyctl proxy 9119:9119against a live Kora and confirm the admin panel renders the operational-state card with no stub banner. Trigger a synthetic transition (e.g. via a future test command) and watch the history ring populateMEMORY_PROVIDERunset so the IsoKron provider never constructs; confirm the panel renders the newOperationalStateHolder not yet initializedbanner instead of the cold-stub banner🤖 Generated with Claude Code