This repository was archived by the owner on May 26, 2026. It is now read-only.
feat(kora): KR-MCP-STOP-CONTROL ST2 — stop tool + kora_control writer + actor_id#147
Merged
rafe-walker merged 1 commit intoMay 23, 2026
Conversation
… + actor_id
Closes the KR-MCP-STOP-CONTROL arc. Three deliverables in one PR:
D1. actor_id field on Caller dataclass + mcp_callers.yaml loader
- Optional uuid string. Backwards-compatible — existing
callers (no actor_id key) load with actor_id=None.
- Loader rejects malformed UUIDs (fail-CLOSED skip + WARN).
- Accepts unhyphenated form; normalizes to canonical form.
D2. kora_cli/clients/kora_control_writer.py — Python wrapper
around public.issue_kora_control SECDEF (substrate
packages/db/migrations/0090_kora_control_secdefs.sql,
shipped substrate-side at commit 2abf095, May 21 2026).
- Raw asyncpg through the IsoKron pool, mirroring
KoraControlReader's pattern. Bucket-spec drift: spec
said "IsoKronMCPClient.invoke('substrate__issue_kora_
control', ...)" but no MCP wrapper exists in
sea-mcp-server/src/tools/; the canonical access path
for this SECDEF is direct asyncpg.
- dry_run mode short-circuits before substrate; predicted
shape uses zero-UUID placeholders + sequence=-1.
- 10s client-side asyncio.wait_for ceiling
(ISSUE_KORA_CONTROL_TIMEOUT_SECONDS) per §4 Q3.
- Fail-CLOSED on: missing actor_id, invalid (level, kind),
missing pool, asyncpg PostgresError (→ SubstrateRejected
with sqlstate preserved), client-timeout (→ Writer
Timeout).
- Module-level current_kora_control_writer() accessor
bound to the active IsoKronMemoryProvider singleton.
D3. kora__request_stop MCP tool — L1/L2 only.
- JSON schema constrains level to [1, 2]; executor ALSO
refuses L3-L5 with explicit operator-on-machine message.
- confirm_token must equal daemon's daemon_session_id
(newly added on DaemonCoordinator at construction +
surfaced via get_status()). Mismatch → -32602.
- Caller cap-gate on "kora__request_stop". DISTINCT from
kora__request_pause (verified by test) — operator can
grant pause/resume without granting stop.
- actor_id required: -32001 actor_id_required_for_stop
(new error class _ST2_ActorIdRequired in mcp_tools.py
with dedicated handler in mcp.py).
- Audit emit: {level, kind, dry_run, actor_id_present} —
reason text NEVER in audit details per §4 Q4 ruling.
- Returns immediately after substrate write succeeds;
enforcement is async via Kora's poll loop.
Additional surface:
- DaemonCoordinator._daemon_session_id (hex uuid4, per-process
stable) — exposed via .daemon_session_id property + the
get_status() JSON key. Read by kora__daemon_status for the
confirm_token mint flow.
- _jsonrpc_error extended to accept optional `data` field for
structured error envelopes (used by the actor_id_required
branch to surface caller_actor_kind + remediation text).
§4 PM-opens resolved:
Q1 — SECDEF verified at packages/db/migrations/0090_kora_
control_secdefs.sql; full signature pasted into
kora_control_writer.py module docstring §1.
Q2 — operator-pastes actor_id from substrate actor_registry
(must be substrate-resolvable per SECDEF body checks).
Q3 — 10s wait_for ceiling, SECDEF's 60s statement_timeout is
the substrate-side backstop.
Q4 — {level, kind, dry_run, actor_id_present, result_tag}
audit keys. Reason text omitted.
Tests:
- tests/kora_cli/clients/test_kora_control_writer.py: 14
writer-level unit tests (dry_run, missing actor_id,
invalid level/kind, no pool, SubstrateRejected wrap,
timeout wrap, current_writer accessor states).
- tests/kora_cli/test_listeners/test_mcp_tools_stop.py: 16
request_stop tool tests (L1/L2 success, L3-L5 refused,
L0 refused, confirm_token mismatch, empty token,
no capability, no actor_id, dry_run, substrate-error,
no coordinator, no provider, audit-omits-reason, SECURITY
bearer-never-in-envelope).
- tests/kora_cli/test_listeners/test_mcp_caller_auth.py: 9
new tests for actor_id parsing (default-None, valid UUID,
malformed UUID, non-string, unhyphenated, mixed entries).
Full regression: 9051 passed (matches base+57 new). 47 failed
identical to ST1 baseline = zero new regressions. All failures
are pre-existing in tests/agent/test_anthropic_adapter.py +
tests/kora_cli/test_web_server*.py — unrelated to this PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
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 the KR-MCP-STOP-CONTROL arc. 3 deliverables in one PR — the two ST1-flagged blockers + the substrate-backed stop tool.
D1 — actor_id field on Caller dataclass + YAML loader
kora_cli/listeners/mcp_caller_auth.py. Optional uuid string on every caller entry. Additive + backwards-compatible: existing callers (no `actor_id` key) load with `actor_id=None`. Loader rejects malformed UUIDs fail-CLOSED (skip + WARN); accepts unhyphenated form and normalizes.D2 — `kora_cli/clients/kora_control_writer.py`
Python wrapper around `public.issue_kora_control` SECDEF.
Bucket-spec drift caught + corrected: spec said `await isokron_client.invoke("substrate__issue_kora_control", {...})` via IsoKronMCPClient. But `packages/sea-mcp-server/src/tools/` registers no `kora_control` MCP tool, and `KoraControlReader` docstring (lines 16-26) explicitly says "no MCP wrapper exists" — the canonical access path is raw asyncpg through the IsoKron pool (same as the existing reader). Writer mirrors that pattern.
D3 — `kora__request_stop` MCP tool
L1 (intake-stop, kind='pause') / L2 (drain, kind='drain') only.
§4 PM-opens — answers in flight + locked
Bonus drift logged
`Caller` is `@dataclass(frozen=True, slots=True)`, not Pydantic — extended the dataclass additively rather than refactor to Pydantic. Same intent, zero footprint.
Tests
Test plan
After this PR merges, Kora has full pause/resume/stop accessible via `/mcp` with caller-attributed substrate writes. Operator retains `kora_control` SQL + flyctl/Doppler paths; the agent surface complements rather than replaces.
🤖 Generated with Claude Code