Skip to content
This repository was archived by the owner on May 26, 2026. It is now read-only.

feat(KR-P2-I-skeleton): operational state machine — enums + transition table (no emit, no wire-in)#27

Merged
rafe-walker merged 1 commit into
mainfrom
feat/kora-KR-P2-I-skeleton-operational-state
May 21, 2026
Merged

feat(KR-P2-I-skeleton): operational state machine — enums + transition table (no emit, no wire-in)#27
rafe-walker merged 1 commit into
mainfrom
feat/kora-KR-P2-I-skeleton-operational-state

Conversation

@rafe-walker

Copy link
Copy Markdown
Owner

Summary

KR-P2-I-skeleton — ships R4.1 §9.1's runtime operational state machine as a pure dataclass + enum + transition-table layer. No chain-event emission, no agent-loop wire-in, no startup hooks. Both follow on after substrate-round Bucket C (event-type literals) + KR-P2-H (boot gates) + KR-P2-I-integration (which folds this into the agent loop).

CC#2 imports PrimaryState, DegradationReason, and ClaimPermission for the Operational State admin panel; shipping the dataclass layer decouples that frontend work from the substrate-round critical path.

R4.1 §9.1 spec: kora_docs/21_program_council/Kora_v1_Program_Spec_R4.1_FINAL.md#9-1

New module — agent/operational_state.py

  • PrimaryState — 5 members (booting / ready / active / paused / stopped). DEGRADED is not a primary state per R4.1; it's the derived flag is_degraded().
  • DegradationReason — 8 members (cost / auth / dispatch / substrate / migration / operator / token_expiring / retry_ceiling).
  • ClaimPermission — 3 members (none / critical_only / normal).
  • OperationalState — frozen dataclass with with_added_reason / with_removed_reason / with_primary_state / with_claim_permission factory methods.
  • StateTransition + TRANSITION_TABLE — 15-row tuple covering all R4.1 §9.1 transitions, with any → PAUSED / any → STOPPED shorthand expanded per concrete from-state so single-pass lookup works.
  • is_valid_transition / transitions_from / transitions_to — read-only query helpers.

Enum string values are treated as load-bearing wire format (CC#2 admin panel + future substrate event payloads consume them verbatim).

Bucket-spec inconsistencies (resolved, non-blocking)

Two minor typos in the bucket §5 test descriptions vs the §3 enum / table definitions; resolved by following the definitions:

Honest non-scope

Per bucket §4, this PR deliberately omits:

  • No append-event MCP calls (emit on transition lands in KR-P2-I-integration)
  • No agent/tool_executor.py modifications
  • No startup hooks anywhere (gateway/, kora_cli/)
  • No IsoKronMemoryProvider integration / actor-loop coupling
  • No OperationalStateManager singleton / service-locator
  • No persistence (in-memory only)

Test plan

🤖 Generated with Claude Code

…n table (no emit, no wire-in)

Skeleton-only: ships R4.1 §9.1's runtime operational state machine as a
pure dataclass + enum + transition-table layer. No chain-event emission,
no agent-loop wire-in, no startup hooks. Both follow on after
substrate-round Bucket C (event-type literals) + KR-P2-H (boot gates) +
KR-P2-I-integration (which folds this skeleton into the agent loop).

# Why ship the skeleton now

CC#2 is building the Operational State admin panel in parallel; they
import `PrimaryState`, `DegradationReason`, and `ClaimPermission` for
type safety. Shipping the dataclass layer now decouples that frontend
work from the substrate-round critical path.

# New module: agent/operational_state.py

- `PrimaryState` — 5-member enum (booting / ready / active / paused /
  stopped) per R4.1 §9.1. Per R4.1 footnote, DEGRADED is NOT a
  primary_state — it's modeled as the derived flag
  `OperationalState.is_degraded()`.
- `DegradationReason` — 8-member enum (cost / auth / dispatch /
  substrate / migration / operator / token_expiring / retry_ceiling).
- `ClaimPermission` — 3-member enum (none / critical_only / normal).
- `OperationalState` — frozen dataclass snapshot with `with_*` factory
  methods (immutable; produce derived instances).
- `StateTransition` — frozen dataclass row of the transition table.
- `TRANSITION_TABLE` — 15-row tuple covering all R4.1 §9.1
  transitions, with `any → PAUSED` and `any → STOPPED` shorthand
  expanded into per-from-state rows so single-pass lookup works.
- `is_valid_transition` / `transitions_from` / `transitions_to` —
  read-only query helpers.

Enum string values are treated as load-bearing wire format (CC#2 admin
panel + future substrate event payloads consume them verbatim).

# Tests: tests/test_operational_state.py — 54 (incl. parametrize)

- Enum value strings match R4.1 §9.1 verbatim + correct cardinality
- OperationalState immutability (frozen=True) + defaults
- `is_degraded()` truth table
- All four `with_*` factory methods: new-instance + original-untouched
- `is_valid_transition` parametrized: 12 known transitions True,
  10 unknown False (terminal STOPPED, self-loops, illegal jumps)
- `transitions_from(BOOTING)` returns 6 rows (1 R4.1 row per to-state,
  plus the duplicate-arrow rows from the `any → PAUSED` and
  `any → STOPPED` expansions)
- `transitions_to(STOPPED)` returns 5 rows (one per concrete from-state,
  plus the duplicate BOOTING→STOPPED arrow from "invariant fail" vs
  "STOP-KORA L4/L5" triggers)
- `transitions_from(STOPPED)` empty (terminal)
- Enum round-trip via `.value` / `EnumCls(value_str)` for all members

# Bucket-spec inconsistencies adjusted (non-blocking)

- Bucket §5 test #1 says "8 PrimaryState members"; R4.1 §9.1 + the
  bucket's own §3 enum block list 5. Test asserts 5 (matches reality).
- Bucket §5 tests #8 / #9 say "4 BOOTING-origin / 4 STOPPED-arrival";
  the bucket's TRANSITION_TABLE expansion of "any → PAUSED/STOPPED"
  yields 6 / 5 respectively (one row per concrete from-state, with
  same-arrow rows for different triggers). Tests assert 6 / 5 and
  document the expansion rationale in the docstring.

# §8 pre-push grep sanity check passes

- `grep -n "kora__append_event\|IsoKronMemoryProvider\|tool_executor"
  agent/operational_state.py` — zero matches.
- `grep -rn "operational_state\|OperationalState" gateway/ kora_cli/
  agent/tool_executor.py` — zero matches.

# Honest scope

Pure dataclass + enum + transition table. No imports of
`IsoKronMemoryProvider`, no append-event MCP calls, no startup hooks,
no agent-loop coupling, no persistence. Emit + wire-in lives in the
KR-P2-I-integration follow-on (post-Bucket-C).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rafe-walker rafe-walker merged commit 376a43d into main May 21, 2026
@rafe-walker rafe-walker deleted the feat/kora-KR-P2-I-skeleton-operational-state branch May 21, 2026 19:21
rafe-walker added a commit that referenced this pull request May 21, 2026
…nt (#26)

GET /api/operational-state stub (READY/normal/no-degradation, stub:true). OperationalStatePage.tsx with STUB DATA banner (conditional), primary-state badge with per-state tone, claim-permission pill, degradation chips with hover blurbs, transition-history table, valid-next-states list. Read-only, manual Reload only. api.ts typed enums anchored to R4.1 §9.1 verbatim (matches PR #27 Python enum strings). 8 new + 47/47 full suite tests; tsc + vite clean. Flip-over plan: swap stub function body to project from real OperationalState singleton when KR-P2-I-integration lands; frontend continues unchanged.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant