This repository was archived by the owner on May 26, 2026. It is now read-only.
feat(KR-7): swap chain-event-emit defer for kora__append_event MCP call — closes D-kr2-st4#14
Merged
Merged
Conversation
…ll — closes D-kr2-st4
Mechanical follow-on swap unlocked by K-9 (substrate `f8487059`) +
KR-7a (`b6415692`). events.py:emit_kora_event body replaces
`raise ChainEventEmitNotAvailableError()` with
`await mcp_client.invoke('kora__append_event', {...})` returning the
substrate-assigned event_id. ~30 LOC across events.py + provider.py +
tools/iso_node.py + tests + docs.
Production-test posture per IsoKron PM #27:
* K-9's kora__append_event handler is a notImplementedHandler stub on
substrate main; substrate-team's dispatch tier (queued) un-stubs +
resolves Layer-A wsk_* auth → Layer-B actor_kind='kora'.
* KR-7's code shape is sound; mock tests verify correctness.
* Production deploys wait on substrate dispatch tier landing —
identical posture to KR-7a (token-not-yet-issued; ships green).
* Verify-at-first-live-emit step per spec § 3: confirm
event_log.actor_id resolves to the 0076-seeded canonical Kora actor
(actor_kind='kora' AND workspace_id=<Flynn clerk_org_id>); if it
resolves to a token-UUIDv5 instead (cowork-claude-pm precedent),
small substrate patch needed.
events.py:
* emit_kora_event body: live call to mcp_client.invoke. K-9 contract
output: {'event_id': '<uuid>'} — projected to a plain str return.
* Defensive: None mcp_client raises ValueError ("caller must resolve
via IsoKronConnection.get_mcp_client() before calling"); unexpected
response shape raises RuntimeError.
* mcp_client param is now required (was Optional with deferred-error
default). Forward signature stays caller-stable from KR-2 ST4.
* ChainEventEmitNotAvailableError class kept exported tagged
[kora.isokron.deprecated] for one release so any downstream pinned
tests resolve. Will be removed when KR-N audits show no remaining
references.
provider.py:_attempt_chain_event_emit:
* Stop catching ChainEventEmitNotAvailableError (no longer raised).
* Fetch IsoKronMCPClient via self._connection.get_mcp_client() (wired
in KR-7a); failure to obtain the client is caught + logged at
ERROR ([kora.chain.emit.failed] ... MCP client unavailable: ...)
so lifecycle hooks (on_session_end / on_delegation / on_memory_write
/ sync_turn) stay alive on substrate downtime.
* Catch IsoKronMCPInvocationError + any defensive Exception at the
lifecycle boundary — log at ERROR (not WARNING) since chain-event-
emit failure is operator-visibility-worthy per PM-lean ("substrate-
side issue worth surfacing"). Sessions don't crash; emits drop with
a loud log line.
* Successful emits log INFO [kora.chain.emit] with the event_id and
invalidate the per-workspace events cache so the next
system_prompt_block §6 re-reads.
tools/iso_node.py:_handle_iso_node_supersede:
* The kora.node.superseded emit call site previously imported
emit_kora_event directly and passed mcp_client=None. Refactored to
route through provider._attempt_chain_event_emit so all chain-emit
paths share the same error-handling + cache-invalidation logic.
Tests:
* test_events.py — replaced test_emit_kora_event_raises_deferred_
write_error with four MCP-call-path tests:
- invokes 'kora__append_event' with expected arg shape; returns
the substrate event_id.
- propagates IsoKronMCPInvocationError on substrate-side error.
- rejects None mcp_client with ValueError (defensive).
- rejects unexpected response shape with RuntimeError (defensive).
Plus one deprecation-runway test asserting the
ChainEventEmitNotAvailableError class is still importable post-KR-7.
* test_provider_end_to_end.py — _FakeProviderConnection now exposes
get_mcp_client() returning a _FakeMcpClient that records invokes.
E2E asserts both emits (on_delegation + on_session_end) hit
kora__append_event with the spec-pinned tool name + arg shape; logs
[kora.chain.emit] INFO each time. Scratchpad-write deferrals stay
at 3 (D-kr2-st3 still open).
* test_isokron_provider_skeleton.py — unchanged (didn't reference
D-kr2-st4 specifics).
BUILD_DEVIATIONS:
* D-kr2-st4-no-chain-emit-mcp-tool moved from Open to Closed with
Rule-5 spec-quote + production-test posture + deprecation-runway
note. 3 deferrals remain open (D-kr2-st2 / D-kr2-st3 / D-kr3-st2).
README "Operator pitfalls":
* Deferred-surface table trimmed 4 → 3 rows. Recently-closed list
gains the KR-7 closure under D-kr2-st4.
* Individual "Chain event emission deferred" pitfall replaced with
the KR-7-shape description: routes via kora__append_event;
successful emits log [kora.chain.emit] INFO; substrate failures log
[kora.chain.emit.failed] ERROR; production posture flagged.
* D-kr2-st2 row updated — K-7 already merged 2026-05-20; PM drafts
the swap bucket.
Local gates:
* ty check — 7,337 diagnostics, zero-delta vs KR-7a baseline.
* pytest tests/plugins/memory/ — 351/351 passing.
* Full suite via xdist (-n auto): 24,561 / 203 failed / 52 errors /
129 skipped. Higher xdist isolation variance this run (kanban_tools
test_board_param errors aren't reproducible in isolation; none
touch isokron).
3 deferrals still open pending CC#1 substrate buckets:
* D-kr2-st2-capability-matrix-mirror ← KR-N swap (K-7 merged; PM drafts)
* D-kr2-st3-no-scratchpad-write-mcp-tool ← K-8
* D-kr3-st2-no-relationlink-write-mcp-tool ← K-10
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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-7 ships the mechanical follow-on swap unlocked by K-9 (substrate
f8487059) + KR-7a (b6415692). ClosesD-kr2-st4-no-chain-emit-mcp-tool.events.py:emit_kora_eventbody replacesraise ChainEventEmitNotAvailableError()withawait mcp_client.invoke('kora__append_event', {...}), returning the substrate-assignedevent_id. ~30 LOC acrossevents.py+provider.py+tools/iso_node.py+ tests + docs — the originally-spec'd ~20-40 LOC mechanical patch shape now that KR-7a wired the MCP client transport.Production-test posture per IsoKron PM #27
kora__append_eventhandler is anotImplementedHandlerstub on substrate main; substrate-team's dispatch tier (queued) un-stubs + resolves Layer-Awsk_*auth → Layer-Bactor_kind='kora'.KORA_SERVICE_TOKENmint, confirmevent_log.actor_idresolves to the 0076-seeded canonical Kora actor (actor_kind='kora' AND workspace_id=<Flynn clerk_org_id>). If it resolves to a token-UUIDv5 instead (thecowork-claude-pmprecedent), small substrate patch needed. Not a KR-7 blocker — operational-correctness step.What landed
plugins/memory/isokron/events.pyemit_kora_eventbody swap:mcp_client.invoke('kora__append_event', {workspace_id, event_type, payload})→ returnsevent_id(str). Defensive:mcp_client=NoneraisesValueError; unexpected response shape raisesRuntimeError.ChainEventEmitNotAvailableErrorclass kept exported tagged[kora.isokron.deprecated]for one-release runway.plugins/memory/isokron/provider.py_attempt_chain_event_emitfetchesIsoKronMCPClientviaself._connection.get_mcp_client()(KR-7a-wired); catchesIsoKronMCPInvocationError+ defensiveExceptionat the lifecycle boundary and logs at ERROR ([kora.chain.emit.failed]) so session hooks stay alive. Successful emits log INFO[kora.chain.emit]with the event_id and invalidate the per-workspace events cache.plugins/memory/isokron/tools/iso_node.py_handle_iso_node_supersede'skora.node.supersededemit refactored to route throughprovider._attempt_chain_event_emitinstead of importingemit_kora_eventdirectly — all chain emits now share one error-handling + cache-invalidation surface.BUILD_DEVIATIONS.mdD-kr2-st4moved Open → Closed with Rule-5 spec-quote, production-test posture, deprecation-runway note. 3 deferrals remain open.plugins/memory/isokron/README.mdtests/plugins/memory/test_events.pytests/plugins/memory/test_provider_end_to_end.py_FakeProviderConnectionnow exposesget_mcp_client()returning a_FakeMcpClientthat records invokes; E2E asserts both lifecycle emits (on_delegation+on_session_end) hitkora__append_eventwith the spec-pinned arg shape. Scratchpad-write deferrals stay at 3 (D-kr2-st3 still open).Error handling — PM-lean propagate at function level, catch at lifecycle boundary
Per spec § 100 ("PM-lean: propagate, since chain event failure is a substrate-side issue worth surfacing"):
emit_kora_eventpropagatesIsoKronMCPInvocationErrordirectly — no catching at the function level.provider._attempt_chain_event_emitcatches at the lifecycle boundary (on_session_end/on_delegation/ etc. can't crash on a single bad emit) and logs at ERROR rather than WARNING so the failure is operator-visible.[kora.chain.emit.failed]in logs to find dropped events;[kora.chain.emit]for successes.Test plan
5 new tests in
test_events.py(replacing 1 deferred-path test):test_emit_kora_event_invokes_kora__append_event_with_expected_args— happy path: invokes the tool with{workspace_id, event_type, payload}exactly; returns the substrateevent_id.test_emit_kora_event_propagates_mcp_invocation_error— substrateIsoKronMCPInvocationErrorpropagates unchanged.test_emit_kora_event_rejects_none_mcp_client— defensiveValueErrorfor caller misuse.test_emit_kora_event_rejects_unexpected_response_shape— defensiveRuntimeErrorif substrate response drifts.test_chain_event_emit_not_available_error_still_importable_post_kr7— deprecation runway intact; message now flags[kora.isokron.deprecated].E2E test (
test_provider_end_to_end.py) updated to post-KR-7 reality:_FakeProviderConnection.get_mcp_client()returns a_FakeMcpClientrecording invokes.[kora.chain.emit]INFO logs (on_delegation + on_session_end emits succeed).kora__append_eventinvokes with{workspace_id, event_type, payload}arg shape.Gates
ty check— 7,337 diagnostics, zero-delta vs KR-7a baseline. All targeted-file diagnostics resolved.pytest tests/plugins/memory/— 351/351 passing.-n auto): 24,561 passed / 203 failed / 52 errors / 129 skipped. Higher xdist isolation variance this run (kanban_toolstest_board_param_*errors — not reproducible in isolation; same family as documented KR-2/KR-3/KR-7a noise). None touchplugins/memory/isokron/.Rule-6 / BUILD_DEVIATIONS / Open asks
Closed:
D-kr2-st4-no-chain-emit-mcp-tool(KR-7).3 deferrals remain open:
D-kr2-st2-capability-matrix-mirrorD-kr2-st3-no-scratchpad-write-mcp-toolD-kr3-st2-no-relationlink-write-mcp-toolPM dispatch note flagged KR-7b (capability-row C2→MCP swap, closes D-kr2-st2) as the next parallel-dispatchable on this lane. Standing by for that dispatch (or the K-8 / K-10 follow-on swaps as their substrate buckets land).
🤖 Generated with Claude Code