This repository was archived by the owner on May 26, 2026. It is now read-only.
feat(KR-8): swap scratchpad-write defer for kora__write_agent_scratchpad MCP call — closes D-kr2-st3#16
Merged
Conversation
b37df46 to
0c57dbf
Compare
…pad MCP call — closes D-kr2-st3
Mechanical follow-on swap unlocked by K-8 (substrate `bd165eb2`) +
KR-7a's MCP client transport. Same pattern as KR-7 chain-emit:
write_scratchpad_entry body replaces `raise
ScratchpadWriteNotAvailableError()` with
`await mcp_client.invoke('kora__write_agent_scratchpad', {...})`,
returning the substrate-assigned scratchpad_entry_id. ~40 LOC across
scratchpad.py + provider.py + tools/iso_node.py + tests + docs.
Production-test posture per IsoKron PM #27:
* K-8's kora__write_agent_scratchpad handler is a
notImplementedHandler stub on substrate main; dispatch tier (task
NousResearch#395) un-stubs + bridges Layer-A wsk_* auth → Layer-B actor_kind.
* KR-8 code shape is sound; mock tests verify correctness.
* Production deploys wait on dispatch tier landing — identical
posture to KR-7 / KR-7b / KR-7a.
K-DG note: K-8's internal flow emits the substrate-canonical chain
literal `kronicle.agent_scratchpad.created` (NOT
`kora.scratchpad.entry.created` as some prior specs may suggest).
The runtime doesn't pass an event_type — the substrate emits
internally as part of the SECDEF flow. Verify-at-first-live-emit:
confirm event_log.actor_id resolves to the 0076-seeded canonical
Kora actor.
scratchpad.py:
* write_scratchpad_entry body: live call to mcp_client.invoke. K-8
contract output: {'scratchpad_entry_id': '<uuid>',
'approved_event_id': '<uuid>'} — projected to a plain str
scratchpad_entry_id return.
* BLAKE3 content_hash computed via existing
compute_scratchpad_content_hash() helper; passed to MCP tool.
* Defensive: None mcp_client raises ValueError; unexpected response
shape raises RuntimeError (same pattern as KR-7's emit_kora_event).
* mcp_client param now required (was Optional with deferred-error
default). Forward signature stays caller-stable from KR-2 ST3.
* ScratchpadWriteNotAvailableError class kept exported tagged
[kora.isokron.deprecated] for one release. Original deferred-tag
preserved inside the message for grep stability.
provider.py:_attempt_scratchpad_write:
* Stop catching ScratchpadWriteNotAvailableError (no longer raised).
* Fetch IsoKronMCPClient via self._connection.get_mcp_client();
failure logs ERROR [kora.scratchpad.write.failed] + returns.
* Catch IsoKronMCPInvocationError + defensive Exception at the
lifecycle boundary — log at ERROR so failures are operator-visible
(same posture as KR-7's chain emit). Sessions don't crash; writes
drop with a loud log line.
* Successful writes log INFO [kora.scratchpad.write] with the
substrate-assigned entry_id and invalidate the per-workspace
own-scratchpad cache so the next read re-fetches.
tools/iso_node.py:
* _handle_iso_node_create + _handle_iso_node_supersede: dropped
the deferred-envelope return path. On success return
{"ok": True, "entry_id": <substrate-uuid>}. On
IsoKronMCPInvocationError return
{"ok": False, "substrate_error": True, "tool_name": ...,
"message": ...} — structured signal for the model that maps to
substrate-level failures distinct from validation errors.
* iso_node_supersede also passes the new entry_id into the
kora.node.superseded chain event payload so audit consumers can
trace the supersession chain.
* Removed unused ScratchpadWriteNotAvailableError import.
Tests (5 new + 9 updated):
* test_scratchpad.py:
- Replaced test_write_scratchpad_entry_raises_deferred_write_error
with 4 MCP-call-path tests (happy/error/None-client/bad-shape)
+ 1 deprecation-runway test.
- _FakeProviderConnection now exposes get_mcp_client() returning a
_FakeMcpClient; sync_turn/on_memory_write tests flipped from
deferred-WARNING assertions to success-INFO + entry_id checks.
- New test_sync_turn_substrate_error_logs_at_error_session_continues
verifies the IsoKronMCPInvocationError → ERROR-log path.
* test_iso_node_tools.py:
- _FakeProviderConnection gains get_mcp_client(); new _FakeMcpClient
routes by tool_name (kora__write_agent_scratchpad → spe-mock-N;
kora__append_event → evt-mock-N; kora__read_kora_capability_row
→ minimal matrix).
- test_iso_node_create_returns_deferred_payload →
test_iso_node_create_returns_ok_envelope_with_substrate_entry_id;
new test_iso_node_create_substrate_error_surfaces_structured_envelope.
- test_iso_node_supersede_inherits_node_kind_from_original flipped
to assert success + verify the inherited node_kind in the new
content_inline header + verify the kora.node.superseded emit
payload carries the new entry_id.
* test_tool_finalize.py: round-trip tests flipped from deferred to
success-envelope assertions. _FakeProviderConnection gains
get_mcp_client().
* test_provider_end_to_end.py: _FakeMcpClient extended to handle
kora__write_agent_scratchpad (returns spe-mock-N). E2E assertions
flipped from 3 deferred-WARNINGs to 3 [kora.scratchpad.write] INFO
logs + 3 scratchpad_write_calls on the fake + 2 append_event_calls.
Assertion narrowed to NOT count the cap-matrix fetch (KR-7b adds
that; this PR stays narrow per parallel-mergeability).
BUILD_DEVIATIONS:
* D-kr2-st3-no-scratchpad-write-mcp-tool moved Open → Closed with
Rule-5 spec-quote, KR-8's call-site refactor inventory, production-
test posture, deprecation-runway note, verify-at-first-live-emit
step.
* 1 deferral remains open (D-kr3-st2 — KR-9 dispatchable).
README "Operator pitfalls":
* Deferred-surface table trimmed 2 → 1 row (D-kr3-st2 only).
* D-kr2-st3 moves to Recently-closed list.
* Individual "Scratchpad writes are currently deferred" pitfall
rewritten as "Scratchpad writes route via kora__write_agent_
scratchpad (KR-8)" with success/failure log paths + operator
grep pointer.
Local gates:
* ty check — 7,337 diagnostics, zero-delta vs KR-7 baseline.
* pytest tests/plugins/memory/ — 357/357 passing.
* Full suite via xdist (-n auto): 24,654 / 168 failed / 129 skipped.
Δ vs KR-7b (24,652/175/129): +2 passed, -7 failed. Same
tests/tools/* + tests/tui_gateway/* xdist isolation noise; none
touch isokron.
1 deferral still open pending CC#1 substrate:
* D-kr3-st2-no-relationlink-write-mcp-tool ← KR-9 (K-10 merged; PM dispatched)
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-8 swaps the deferred scratchpad-write surface for a real
kora__write_agent_scratchpadMCP call via the KR-7a-wiredIsoKronMCPClient. ClosesD-kr2-st3-no-scratchpad-write-mcp-tool.Mechanical follow-on unlocked by K-8 (
bd165eb2). Same pattern as KR-7's chain-emit swap —write_scratchpad_entrybody replacesraise ScratchpadWriteNotAvailableError()withawait mcp_client.invoke(...)returning the substrate-assignedscratchpad_entry_id. ~40 LOC across implementation + tests + docs.Production-test posture (same as KR-7 / KR-7b)
K-8's
kora__write_agent_scratchpadhandler is anotImplementedHandlerstub on substrate main; substrate-team's dispatch tier (task NousResearch#395) un-stubs + bridges Layer-Awsk_*auth → Layer-Bactor_kind='kora'. KR-8 code shape is sound; mock tests verify correctness; production deploys wait on dispatch tier landing.K-DG note: substrate-canonical chain literal
K-8's internal flow emits
kronicle.agent_scratchpad.created(NOTkora.scratchpad.entry.created). The runtime doesn't pass an event_type — the substrate emits internally as part of the SECDEF flow.What landed
plugins/memory/isokron/scratchpad.pywrite_scratchpad_entrybody swap: invokeskora__write_agent_scratchpadwith{workspace_id, scratchpad_kind, visibility_scope, content_inline, content_hash}. BLAKE3 hash computed via existingcompute_scratchpad_content_hash. Defensive: None client →ValueError; unexpected response shape →RuntimeError.mcp_clientparam now required.ScratchpadWriteNotAvailableErrorkept tagged[kora.isokron.deprecated]for one-release runway.plugins/memory/isokron/provider.py_attempt_scratchpad_writestops catching the defer-error; fetchesIsoKronMCPClientviaget_mcp_client(); catchesIsoKronMCPInvocationError+ defensiveExceptionat lifecycle boundary, logs at ERROR ([kora.scratchpad.write.failed]). Successful writes log INFO[kora.scratchpad.write]with the substrate entry_id + invalidate the own-scratchpad cache.plugins/memory/isokron/tools/iso_node.pyiso_node_create+iso_node_supersedehandlers: dropped the deferred-envelope path. Success →{"ok": True, "entry_id": <substrate-uuid>}. Substrate failure →{"ok": False, "substrate_error": True, "tool_name", "message"}.iso_node_supersedepasses the new entry_id into thekora.node.supersededchain event payload so audit consumers can trace the supersession chain.BUILD_DEVIATIONS.mdplugins/memory/isokron/README.mdTest plan
Existing tests updated to post-KR-8 reality (all passing):
test_scratchpad.py— replacedtest_write_scratchpad_entry_raises_deferred_write_errorwith:kora__write_agent_scratchpadwith spec-pinned arg shape; returns substrate entry_id; BLAKE3 hex hash present.IsoKronMCPInvocationErrorfrom substrate.mcp_client(defensiveValueError).RuntimeError).ScratchpadWriteNotAvailableErrorstill importable; carries[kora.isokron.deprecated]tag.test_scratchpad.py(provider integration):sync_turnKora-action triggers real MCP write; success-INFO log fires.sync_turninvalidates own-scratchpad cache on successful write.sync_turnsubstrate error → ERROR log; session lifecycle continues.on_memory_writeembeds[memory.{action} → {target}]prefix incontent_inline; real MCP invoke fires + success-INFO log.test_iso_node_tools.py:test_iso_node_create_returns_ok_envelope_with_substrate_entry_id(replaces_returns_deferred_payload).test_iso_node_create_substrate_error_surfaces_structured_envelope(new).test_iso_node_supersede_inherits_node_kind_from_originalflipped to verify success + inheritednode_kind: Patternin new content_inline +kora.node.supersededemit fires with new entry_id in payload.test_tool_finalize.py:test_provider_end_to_end.py:_FakeMcpClientextended to handlekora__write_agent_scratchpad(returnsspe-mock-N).[kora.scratchpad.write]INFO logs (sync_turn + on_memory_write + on_delegation) + 3scratchpad_write_calls+ 2append_event_calls. Assertion narrowed to NOT count cap-matrix fetch — KR-7b adds that in its own PR, kept parallel-mergeable.Gates
ty check— 7,337 diagnostics, zero-delta vs KR-7 baseline.pytest tests/plugins/memory/— 357/357 passing.-n auto): 24,654 passed / 168 failed / 129 skipped. Δ vs KR-7b (24,652/175/129): +2 passed, -7 failed. Sametests/tools/*+tests/tui_gateway/*xdist isolation noise; none touchplugins/memory/isokron/.Rule-6 / BUILD_DEVIATIONS / Open asks
Closed:
D-kr2-st3-no-scratchpad-write-mcp-tool(KR-8).1 deferral remains open:
D-kr3-st2-no-relationlink-write-mcp-toolShipping KR-9 next. After it merges, all 4 BUILD_DEVIATIONS are closed code-side; standing-by state becomes dispatch-tier-gated (substrate task NousResearch#395 + service-token mint for production).
🤖 Generated with Claude Code