This repository was archived by the owner on May 26, 2026. It is now read-only.
feat(KR-2 ST4): chain events + Constitution + session context — closes KR-2#8
Merged
Merged
Conversation
…s KR-2
ST4 wraps KR-2. Recent chain events read, active Constitution revision
read, KoraSessionContext shape + assembler, system prompt block §6,
and removal of every remaining NotImplementedError stub. Chain event
emit is deferred behind ChainEventEmitNotAvailableError until the Sea
MCP tool ships (same pattern as the ST3 scratchpad write defer).
Read paths (new):
* events.py:read_recent_kora_events — direct asyncpg against
hivex_foundation.event_log, the substrate's one genuine
tenant_id-UUID-keyed table. JOINs hivex_foundation.tenant on
clerk_org_id to resolve the workspace_id (Clerk TEXT) → tenant_id.
Filters event_type LIKE 'kora.%'. jsonb_pretty(payload) truncated to
300 chars + ellipsis to match the TS-side KoraRecentChainEvent shape.
Default LIMIT 50 per spec.
* constitution.py:read_active_constitution_revision — asyncpg against
kronicle.workspace_constitution_revisions. RLS-GUC-in-transaction
(same pattern as policy_registry / scratchpad). No superseded_at
column on the table — active = ORDER BY revision_number DESC
LIMIT 1. rules_hash BYTEA hex-encoded to match the TS-side
constitutionVersionHashFromRulesHash helper so K-6 (Constitution
pre-screen middleware) sees the same canonical hex form. Returns
None for fresh-workspace bootstrap state.
Session context (new):
* session_context.py:assemble_session_context — fans out the six
load-bearing reads via asyncio.gather. Mirrors TS-side
KoraSessionContext shape at
packages/sea-mcp-server/src/kora/context-assembler/types.ts:130.
* KoraSessionContext dataclass: workspace_id, assembled_at,
role_charter, capability_matrix_row, own_scratchpad,
cross_agent_scratchpad, recent_chain_events,
active_constitution_revision_id, active_constitution_rules_hash.
Tuple-typed read collections — immutable end-to-end like TS side.
Deferred chain event emit:
* events.py:emit_kora_event raises ChainEventEmitNotAvailableError
with [kora.isokron.todo] tag + D-kr2-st4-no-chain-emit-mcp-tool.
Signature matches the future MCP-backed impl so caller code stays
stable when the substrate tool lands. Direct INSERT into event_log
forbidden — would skip _emit_chain_event SECDEF and break
prev_event_hash / this_event_hash witness chain.
Provider finalize:
* on_turn_start now gathers 7 reads in one shot (the original 3 from
ST2 + the 2 scratchpad reads from ST3 + the 2 ST4 reads). Single
asyncio.gather per prefetch for one round-trip latency.
* IsoKronMemoryProvider.session_context() returns the typed
KoraSessionContext from warm caches (or None when caches are cold).
* system_prompt_block extended with §6 "Recent kora.* activity"
surfacing the last 5 events as readable bullets; cap is bounded
to keep the prompt size sane.
* All remaining ABC stubs replaced with real implementations:
- on_session_end emits kora.session.ended (deferred + caught)
- on_session_switch updates session_id; reset=True flushes all 7
caches so the next turn re-reads against fresh substrate
- on_pre_compress returns "" (substrate is the system of record;
nothing isokron-side to inject into compression summaries)
- on_delegation mirrors as scratchpad reasoning_trail entry +
emits kora.handoff.to_claude_pm (both deferred + caught)
- save_config no-op (env-var-based config; no native YAML file)
- handle_tool_call inherits ABC default (provider has no tools
until KR-3 ships iso_node_* / iso_link_*)
* _attempt_chain_event_emit helper mirrors
_attempt_scratchpad_write's pattern: submit-and-wait, catch the
defer-error, log a one-line WARNING tagged with the deviation ID.
Successful emits invalidate the events cache.
Capability mirror parity fix:
* The C2 mirror was 1 cap stale — Sea MCP capability-matrix.ts has
added cap_unbless_convention (operator-only, kora: false) since
KR-2 ST2 shipped. The parity test fired in this PR's run and
caught the drift in both directions. Mirror + counts updated to
match: SEA 24 + KORA_BROADER 25 = 49 total; Kora granted=22,
denied=27. Parity test now passes against current substrate main
(28ff4f7). This is the exact win the C2 parity guard was built
for; closing D-kr2-st2 still gated on K-7 substrate dispatch.
Tests (23 new):
* test_events.py (9): typed shape, default limit 50, custom limit
binding, SQL JOINs tenant on clerk_org_id + LIKE filter, payload
truncation at 300 chars + ellipsis, short payload pass-through,
empty payload, datetime occurred_at ISO encoding, deferred emit
carries the tag.
* test_constitution.py (8): typed shape with hex-encoded rules_hash,
None for fresh workspace, RLS-GUC ordering, SQL uses
revision_number DESC (NOT superseded_at), hex encoder accepts
bytes / bytearray / memoryview / str / rejects unknown types.
* test_session_context.py (3): fans out all 6 reads, None pair on
no Constitution row, default limits.
* test_provider_end_to_end.py (2): full lifecycle walk
(initialize → on_turn_start → system_prompt_block → sync_turn
→ on_memory_write → on_delegation → on_pre_compress →
on_session_switch → on_session_end → reset → shutdown). Asserts
no NotImplementedError surfaces anywhere; deferred-emit / deferred-
scratchpad warnings logged but caught. Cold-cache session_context
returns None.
ST1 skeleton test updated: parametrize replaced with
test_no_stub_methods_remain_after_st4 verifying each ABC method
returns normally; test_handle_tool_call_unsupported_tool_raises_
with_provider_name verifies the ABC's clear-error path is preserved.
Local gates:
* ty check — 7,337 diagnostics, same as ST3 baseline (zero-delta).
* pytest tests/plugins/memory/ — 248/248 passing (23 new ST4 + 64
pre-ST4 isokron + 161 other memory providers).
* Full suite via xdist (-n auto): 24,570 passed / 143 failed / 129
skipped. Δ vs ST3 merge baseline (24,551/143/129): +19 passed,
±0 failed. Failures still concentrated in tests/tools/* +
tests/tui_gateway/* xdist isolation noise; none touch
plugins/memory/isokron/.
Rule-6:
* BUILD_DEVIATIONS.md gains D-kr2-st4-no-chain-emit-mcp-tool under
Open with grep snapshot of available kora__* tools (still 3) and
exact closure condition.
* README "Operator pitfalls" gains 3 entries: chain emit deferred
+ tag to grep, event_log tenant_id-keying exception, Constitution
revisions table has no superseded_at column.
* All [kora.isokron.todo] tags retained on deferred surfaces.
KR-2 milestone closed. Standing by for KR-3 dispatch + K-7 + K-8
+ K-9 (or equivalent) substrate dispatches to swap the three C2 /
deferred surfaces.
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
Closes KR-2. Ships the final ST4 pieces:
hivex_foundation.event_log(the substrate's one genuinetenant_id UUID-keyed table). Resolves Kora'sworkspace_id TEXTtotenant_idviaJOIN hivex_foundation.tenant ON t.clerk_org_id = $1. Filtersevent_type LIKE 'kora.%'. Truncatesjsonb_pretty(payload)to 300 chars + ellipsis to match TS-sideKoraRecentChainEvent. Default LIMIT 50.kronicle.workspace_constitution_revisions. Schema gotcha verified: nosuperseded_atcolumn; "active" =ORDER BY revision_number DESC LIMIT 1(riding theidx_constitution_revisions_workspace_currentindex).rules_hash BYTEAhex-encoded to match the TS-sideconstitutionVersionHashFromRulesHashhelper so K-6 (Constitution pre-screen middleware) sees the same canonical hex form. ReturnsNonefor fresh-workspace bootstrap state.KoraSessionContextshape + assembler mirroring TS-sidepackages/sea-mcp-server/src/kora/context-assembler/types.ts:130. Six load-bearing reads + two identity fields (workspace_id,assembled_at).assemble_session_contextfans out the six reads viaasyncio.gather.read_recent_kora_eventsdirectly).on_session_end,on_session_switch(cache flush onreset=True),on_pre_compress(returns""),on_delegation(scratchpad mirror + emit),save_config(no-op per env-var config),handle_tool_call(inherits ABC default for the no-tools case). NoNotImplementedErrorstubs remain in the provider.Rule-3 STOP-gate findings during recon
WHERE workspace_id = $1againsthivex_foundation.event_log. The actual schema (foundation/0003:73) keys offtenant_id UUID NOT NULL REFERENCES tenant(tenant_id)— noworkspace_idcolumn. PM's ST4 dispatch caught this correctly: this IS the substrate's one tenant_id exception. Implementation uses the TS reader's JOIN pattern verbatim (verified againstpackages/sea-mcp-server/src/kora/context-assembler/index.ts:287).kora__append_eventMCP tool exists. Same probe as the ST3 scratchpad-write recon: onlykora__propose_convention,kora__read_escalation_queue,kora__propose_policy_changeregistered in Sea MCPsrc/tools/. Per spec § ST4 § 1: "If kora__append_event is missing: defer behind ChainEventEmitNotAvailableError, file BUILD_DEVIATIONS D-kr2-st4-no-chain-emit-mcp-tool". Done — third deferred-write surface, same pattern as ST2 capability-row mirror + ST3 scratchpad write.cap_unbless_convention(operator-only, added 2026-05-20) missing from the Python mirror. Updated the mirror + counts (24+25=49; Kora granted=22, denied=27). This is exactly the drift the C2 parity guard was built to catch — closing the C2 deviation still gated on K-7 substrate dispatch.What landed
plugins/memory/isokron/events.pyRecentChainEventdataclass,ChainEventEmitNotAvailableError,read_recent_kora_events, deferredemit_kora_event, canonical SQL + payload truncation.plugins/memory/isokron/constitution.pyActiveConstitutionRevisiondataclass,read_active_constitution_revisionwith RLS-GUC-in-transaction, BYTEA hex-encoder accepting bytes/bytearray/memoryview/str.plugins/memory/isokron/session_context.pyKoraSessionContextdataclass mirroring TS types.ts:130;assemble_session_contextasync fan-out viaasyncio.gather.plugins/memory/isokron/provider.py_prefetch_allnow gathers 7 reads (4 ST2 + 2 ST3 + ST4 events + ST4 constitution);system_prompt_blockadds §6; new_attempt_chain_event_emithelper; all remaining ABC stubs replaced (noNotImplementedErrorleft); newsession_context()public API returning the typed shape from warm caches.plugins/memory/isokron/capability_matrix_mirror.pycap_unbless_convention: Falseper parity-test drift catch.BUILD_DEVIATIONS.mdD-kr2-st4-no-chain-emit-mcp-toolunder Open — closes when Sea MCP append-event tool ships.plugins/memory/isokron/README.mdevent_logtenant_id-keying exception, Constitution table has nosuperseded_at. Sub-task table marked KR-2 closed.test_events.py(9 tests),test_constitution.py(8),test_session_context.py(3),test_provider_end_to_end.py(2 — full E2E).test_isokron_provider_skeleton.pyparametrize replaced with stub-free verification;test_reads.py+test_capability_matrix_parity.pyupdated for the 49-cap matrix size.Key SQL (verbatim, mirrors TS readers)
_prefetch_allnow gathers 7 reads in oneasyncio.gatherSingle round-trip latency for the full session-warm. Caches populated:
_charter_cache(RoleCharter)_policy_cache(List[PolicyRegistryEntry])_capability_cache(KoraCapabilityRow)_own_scratchpad_cache(List[ScratchpadEntry])_cross_agent_scratchpad_cache(List[ScratchpadEntry])_events_cache(List[RecentChainEvent])_constitution_cache(Optional[(revision_id, rules_hash_hex)])Test plan
23 new tests, all passing:
test_events.py(9):LIKE 'kora.%'occurred_atISO-encodedChainEventEmitNotAvailableErrorwith tag + deviation IDtest_constitution.py(8):rules_hashNonefor fresh-workspace bootstraptxn.enter→execute(set_config)→fetchrow→txn.exit)revision_number DESC(NOTsuperseded_at)bytes,bytearray,memoryview, hexstrpass-throughtest_session_context.py(3):NoneConstitution revision pair on fresh workspaceDEFAULT_SCRATCHPAD_LIMIT=100,DEFAULT_RECENT_EVENT_LIMIT=50)test_provider_end_to_end.py(2):initialize→on_turn_start(warms 7 caches) →system_prompt_block(uses warm cache; all 6 sections present + Rule-6 verbatim + recent event_types in §6) →session_context()returns typed shape with constitution hex →sync_turn(cap_* token → attempt write → catch defer → log) →on_memory_write(mirrors to scratchpad) →on_delegation(scratchpad + emit, both deferred + logged) →on_pre_compress(returns "") →on_session_switch(no-reset keeps caches) →on_session_end(emit kora.session.ended deferred) →on_session_switch(reset=True)(flushes 7 caches) →shutdown. ZeroNotImplementedErrors surface. 5+ deferred-write/emit WARNINGs logged.session_context()returnsNonewhen caches are cold (caller must callon_turn_startfirst)Plus existing 64 isokron tests + parametrize-cross-product still passing.
Gates
ty check— 7,337 diagnostics, same as ST3 baseline (Δ 0). Zero new diagnostics introduced.pytest tests/plugins/memory/— 248/248 passing (23 new ST4 + 64 pre-ST4 isokron + 161 other memory providers).-n auto): 24,570 passed / 143 failed / 129 skipped. Δ vs ST3 merge baseline (24,551/143/129): +19 passed, ±0 failed. Sametests/tools/*+tests/tui_gateway/*xdist isolation noise as ST2/ST3; none touchplugins/memory/isokron/.Rule-6 / BUILD_DEVIATIONS / Open asks
Three deferred-MCP-tool surfaces still open (all gated on substrate-team dispatches; same pattern, same closure shape):
D-kr2-st2-capability-matrix-mirrorACTOR_CAPABILITY_MATRIXKora columnkora__read_kora_capability_rowD-kr2-st3-no-scratchpad-write-mcp-toolkora__write_agent_scratchpadD-kr2-st4-no-chain-emit-mcp-toolkora__append_eventAll three follow the same shape: signature is forward-stable, body swaps from
raise <DeferredError>tomcp_client.invoke('kora__*', ...). The follow-on KR-N buckets on this lane are size O(60 LOC each) once the substrate tools land. PM coordinates the K-9 substrate dispatch.KR-2 milestone closes here. The IsoKronMemoryProvider has:
KoraSessionContextshape ready for K-6 (Constitution pre-screen middleware) to consume~/.kora/config.yamlmemory.provider: isokronis enable-able in production with the known caveats documented in README "Operator pitfalls"Standing by for KR-3 (beads-pattern consumer wiring) dispatch.
🤖 Generated with Claude Code