You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/09/20 09:08:32 UTC

(maka) branch feat/runtime-host-rust updated: refactor(plugins): unify public capabilities and remove legacy paths

This is an automated email from the ASF dual-hosted git repository.

M4n5ter pushed a commit to branch feat/runtime-host-rust
in repository https://gitbox.apache.org/repos/asf/maka.git


The following commit(s) were added to refs/heads/feat/runtime-host-rust by this push:
     new 00e52fc2de refactor(plugins): unify public capabilities and remove legacy paths
00e52fc2de is described below

commit 00e52fc2de736fd16ca22660f87ddac8b4439e0e
Author: Wang <m4...@gmail.com>
AuthorDate: Sun Sep 20 17:08:12 2026 +0800

    refactor(plugins): unify public capabilities and remove legacy paths
    
    Expose scoped host services and Rust/JS interoperability through the public plugin API. Add durable background authorization, effect settlement, and client call provenance; migrate skills entry points and remove obsolete compatibility paths.
    
    Generated-by: OpenAI Codex
---
 Cargo.lock                                         |    2 +-
 .../src/main/__tests__/browser-message-box.test.ts |   14 +
 .../src/main/__tests__/browser-tools.test.ts       |    3 +-
 .../main/__tests__/client-plugin-remote.test.ts    |   69 +-
 .../__tests__/main-process-diagnostics.test.ts     |    2 +-
 .../__tests__/managed-artifact-preview.test.ts     |    2 +-
 .../src/main/__tests__/mcp-runtime-e2e.test.ts     |    9 +-
 .../permission-response-ipc-boundary.test.ts       |   10 +-
 .../runtime-host-bot-session-adapter.test.ts       |   11 +-
 .../main/__tests__/runtime-host-client-uds.test.ts |   12 +-
 .../runtime-host-desktop-candidate.test.ts         |    9 +-
 .../__tests__/runtime-host-desktop-manager.test.ts |    5 +-
 .../runtime-host-local-remote-access.test.ts       |   89 +-
 .../runtime-host-managed-services.test.ts          |   27 +-
 .../main/__tests__/runtime-host-management.test.ts |   19 +-
 .../runtime-host-native-capabilities.test.ts       |   48 +-
 .../main/__tests__/runtime-host-onboarding.test.ts |  220 +---
 .../__tests__/runtime-host-profile-service.test.ts |  122 +-
 ...runtime-host-session-execution-ipc-main.test.ts |   86 +-
 .../__tests__/runtime-host-ssh-terminal.test.ts    |  354 ++----
 .../__tests__/runtime-host-wsl-controller.test.ts  |  121 --
 .../__tests__/runtime-host-wsl-handoff.test.ts     |   27 +-
 .../src/main/__tests__/session-local.test.ts       |    4 +-
 .../__tests__/workbar-services-adapter.test.ts     |    2 +-
 apps/desktop/src/main/browser-message-box.ts       |   10 +
 apps/desktop/src/main/browser/browser-tools.ts     |   18 +-
 apps/desktop/src/main/client-plugin-remote-ipc.ts  |   49 +-
 apps/desktop/src/main/main-process-diagnostics.ts  |    2 +-
 .../src/main/managed-artifact-preview-tools.ts     |    6 +-
 apps/desktop/src/main/permission-response-guard.ts |   29 +-
 .../src/main/plugin-authorization-dialog.ts        |   51 +
 apps/desktop/src/main/runtime-host-boot.ts         |   21 +-
 .../src/main/runtime-host-bot-session-adapter.ts   |    8 +-
 apps/desktop/src/main/runtime-host-client.ts       |   40 +-
 .../src/main/runtime-host-local-remote-access.ts   |  171 +--
 .../src/main/runtime-host-managed-services.ts      |  137 +--
 apps/desktop/src/main/runtime-host-management.ts   |   52 +-
 .../src/main/runtime-host-native-capabilities.ts   |   70 +-
 apps/desktop/src/main/runtime-host-onboarding.ts   |  220 +---
 .../src/main/runtime-host-pairing-journal.ts       |    5 +-
 .../src/main/runtime-host-profile-service.ts       |   90 +-
 .../runtime-host-session-execution-ipc-main.ts     |   29 +-
 apps/desktop/src/main/runtime-host-ssh-terminal.ts |  158 +--
 .../src/main/runtime-host-wsl-controller.ts        |  110 --
 apps/desktop/src/main/runtime-host-wsl-handoff.ts  |    4 +-
 apps/desktop/src/main/session-local-service.ts     |    4 +-
 apps/desktop/src/preload/bridge-contract.d.ts      |   23 +-
 apps/desktop/src/preload/preload.ts                |   11 +
 .../desktop/src/renderer/app-shell-chat-actions.ts |   10 +-
 .../features/client-plugins/host-runtime.ts        |    2 +-
 .../src/renderer/features/client-plugins/ports.ts  |    3 +-
 .../platform/desktop/client-plugin-remote.ts       |  109 +-
 .../desktop/create-client-plugin-services.ts       |   31 +
 .../src/renderer/skill-invocation-feedback.ts      |   14 +-
 crates/agent/src/executor.rs                       |    9 +-
 crates/agent/src/history.rs                        |    3 +-
 crates/agent/src/history/compatible.rs             |   13 +-
 crates/agent/src/history/replay.rs                 |    5 +-
 crates/agent/src/lib.rs                            |   10 +-
 crates/agent/src/model_attempt.rs                  |    2 +-
 crates/agent/src/runner.rs                         |    2 -
 .../tests/integration/attachment_projection.rs     |    2 -
 crates/agent/tests/integration/auto_context.rs     |    2 +-
 .../agent/tests/integration/code_mode_lifetime.rs  |    1 -
 crates/agent/tests/integration/context_overflow.rs |    8 +-
 crates/agent/tests/integration/dynamic_tools.rs    |    2 +-
 .../agent/tests/integration/frozen_projection.rs   |    4 +-
 crates/agent/tests/integration/handoff.rs          |    2 +-
 crates/agent/tests/integration/image_projection.rs |    4 +-
 crates/agent/tests/integration/lifetime.rs         |    1 -
 .../agent/tests/integration/model_interruption.rs  |    5 +-
 .../agent/tests/integration/plugin_composition.rs  |    2 +-
 crates/agent/tests/integration/pruning.rs          |    5 +-
 crates/agent/tests/integration/stale_pruning.rs    |    6 +-
 crates/agent/tests/integration/startup_recovery.rs |    1 -
 .../agent/tests/integration/support/agent_loop.rs  |    2 -
 .../agent/tests/integration/support/code_mode.rs   |    1 -
 crates/agent/tests/integration/support/context.rs  |    1 -
 .../tests/integration/support/image_projection.rs  |    1 -
 crates/agent/tests/integration/support/recovery.rs |    3 +-
 .../cli/migrations/deployment/0001_deployment.sql  |   12 +
 crates/cli/migrations/deployment/0002_update.sql   |   24 -
 .../migrations/deployment/0003_update_policy.sql   |   24 -
 crates/cli/src/deployment/policy/storage.rs        |    7 +-
 crates/cli/src/deployment/query.rs                 |    6 -
 crates/cli/src/deployment/store.rs                 |   44 +-
 crates/cli/src/deployment/updates.rs               |   56 +-
 crates/cli/src/host_client.rs                      |    1 -
 crates/cli/tests/integration/candidate.rs          |    1 -
 crates/client-capability/src/broker.rs             |    7 +-
 crates/client-capability/src/broker/dispatch.rs    |    8 +-
 crates/client-capability/tests/client_service.rs   |   13 +-
 .../client-capability/tests/journal_admission.rs   |    7 +-
 crates/config/migrations/0001_configuration.sql    |   54 +-
 .../config/migrations/0002_access_credentials.sql  |   26 -
 crates/config/migrations/0003_runtime_policy.sql   |   28 -
 crates/config/migrations/0004_model_facts.sql      |   24 -
 crates/config/migrations/0005_oauth_login.sql      |   27 -
 crates/config/migrations/0006_external_agents.sql  |   34 -
 crates/config/migrations/0007_model_overrides.sql  |   36 -
 .../migrations/0008_connection_model_authority.sql |   27 -
 .../config/migrations/0009_skill_preferences.sql   |   32 -
 .../config/migrations/0010_plugin_credentials.sql  |   28 -
 crates/config/src/database.rs                      |   26 +-
 crates/config/src/lib.rs                           |    2 +-
 crates/config/src/plugin_authorization.rs          |  187 ++++
 crates/config/src/policy.rs                        |   10 +-
 crates/config/src/skills.rs                        |  134 ---
 crates/config/tests/migrations.rs                  |  224 +---
 crates/config/tests/plugin_authorization.rs        |  191 ++++
 crates/config/tests/runtime_policy.rs              |   60 +-
 crates/config/tests/skills.rs                      |  117 --
 crates/event-log/migrations/0001_runtime.sql       |  411 ++++++-
 .../migrations/0002_session_read_state.sql         |   36 -
 crates/event-log/migrations/0003_interactions.sql  |   36 -
 crates/event-log/migrations/0004_artifacts.sql     |   41 -
 .../migrations/0005_tool_result_payloads.sql       |   25 -
 crates/event-log/migrations/0006_shell_runs.sql    |   38 -
 crates/event-log/migrations/0007_steering.sql      |   29 -
 .../event-log/migrations/0008_message_sources.sql  |   25 -
 .../migrations/0009_message_admissions.sql         |   28 -
 crates/event-log/migrations/0010_message_queue.sql |   30 -
 .../migrations/0011_queue_command_receipts.sql     |   30 -
 .../migrations/0012_message_submit_receipts.sql    |   28 -
 .../migrations/0013_message_interrupts.sql         |   34 -
 .../migrations/0014_continuation_claims.sql        |   31 -
 crates/event-log/migrations/0015_projects.sql      |   46 -
 .../migrations/0016_interrupted_transcript.sql     |   25 -
 crates/event-log/migrations/0018_workhub_stops.sql |   30 -
 .../event-log/migrations/0019_workhub_resume.sql   |   34 -
 .../event-log/migrations/0020_session_events.sql   |  118 --
 .../migrations/0021_workhub_assignment_target.sql  |   25 -
 .../migrations/0022_workhub_correction.sql         |   67 --
 .../event-log/migrations/0023_handoff_claims.sql   |   43 -
 crates/event-log/migrations/0024_plugins.sql       |   58 -
 crates/event-log/migrations/0025_graph.sql         |   52 -
 .../migrations/0026_request_composition.sql        |   29 -
 .../event-log/migrations/0027_session_managers.sql |   26 -
 crates/event-log/src/archive/effective.rs          |   32 +-
 crates/event-log/src/context/boundary.rs           |   11 +-
 crates/event-log/src/context/latest_main.rs        |   10 +-
 crates/event-log/src/context/proof.rs              |   17 +-
 crates/event-log/src/context/request.rs            |   30 +-
 crates/event-log/src/effects.rs                    |  179 +++
 crates/event-log/src/lib.rs                        |    1 +
 crates/event-log/src/message_admissions.rs         |    6 +-
 .../event-log/src/message_admissions/receipts.rs   |    6 +-
 crates/event-log/src/message_queue.rs              |    1 -
 crates/event-log/src/message_queue/edit.rs         |    2 -
 crates/event-log/src/observation/delivery.rs       |    2 +-
 crates/event-log/src/observation/streams.rs        |    2 +-
 crates/event-log/src/plugins/execution.rs          |    3 +-
 crates/event-log/src/root.rs                       |   21 +-
 crates/event-log/src/root_lock.rs                  |    1 -
 crates/event-log/src/schema.rs                     |   26 +-
 crates/event-log/src/sessions/activity.rs          |   42 +-
 crates/event-log/src/sessions/partial.rs           |    2 +-
 crates/event-log/src/steering_delivery.rs          |    1 -
 crates/event-log/src/transcript.rs                 |    2 +-
 crates/event-log/src/transcript/evidence.rs        |    2 +-
 crates/event-log/src/transcript/tools.rs           |    2 +-
 crates/event-log/src/workhub/correction.rs         |   16 +-
 crates/event-log/src/workhub/correction/admit.rs   |    3 +-
 crates/event-log/tests/append_batch.rs             |    1 -
 crates/event-log/tests/archive/fixtures.rs         |    5 +-
 .../event-log/tests/archive/lineage_checkpoint.rs  |    2 +-
 crates/event-log/tests/async_commit.rs             |    1 -
 crates/event-log/tests/catalog_changes.rs          |    1 -
 crates/event-log/tests/catalog_execution.rs        |   12 +-
 crates/event-log/tests/catalog_partial.rs          |    3 +-
 crates/event-log/tests/compact_presentation.rs     |   18 +-
 .../tests/compact_presentation/automatic.rs        |    8 +-
 .../tests/compact_presentation/automatic/steps.rs  |    2 +-
 crates/event-log/tests/context_compaction.rs       |    3 -
 .../event-log/tests/context_compaction/active.rs   |    4 +-
 .../event-log/tests/context_compaction/fixtures.rs |    5 +-
 .../event-log/tests/context_compaction/frozen.rs   |    3 -
 .../event-log/tests/context_compaction/legacy.rs   |   68 --
 .../event-log/tests/context_compaction/lineage.rs  |    4 +-
 crates/event-log/tests/continuation.rs             |    1 -
 crates/event-log/tests/continuation/fixtures.rs    |    1 -
 crates/event-log/tests/continuation/handoff.rs     |    4 +-
 .../event-log/tests/continuation/handoff_budget.rs |    2 +-
 .../tests/continuation/handoff_consumers.rs        |   24 +-
 crates/event-log/tests/effects.rs                  |   86 ++
 crates/event-log/tests/executor.rs                 |    1 -
 crates/event-log/tests/graph.rs                    |    1 -
 crates/event-log/tests/message_queue.rs            |   24 +-
 crates/event-log/tests/message_submit_receipts.rs  |   14 +-
 crates/event-log/tests/migrations.rs               |  161 +--
 crates/event-log/tests/navigation.rs               |    1 -
 crates/event-log/tests/observation.rs              |    4 +-
 crates/event-log/tests/plugins.rs                  |    2 -
 crates/event-log/tests/projects.rs                 |    1 -
 crates/event-log/tests/provider_call_namespace.rs  |    3 +-
 crates/event-log/tests/read_state.rs               |   90 --
 crates/event-log/tests/root_messages.rs            |   20 +-
 crates/event-log/tests/run_prefix.rs               |    1 -
 crates/event-log/tests/session_metadata.rs         |    1 -
 crates/event-log/tests/session_prefix.rs           |    1 -
 crates/event-log/tests/sessions.rs                 |    2 -
 crates/event-log/tests/shell_runs.rs               |   17 +-
 crates/event-log/tests/steering.rs                 |    3 +-
 crates/event-log/tests/stream_delivery.rs          |    6 +-
 crates/event-log/tests/support/message_queue.rs    |    2 -
 crates/event-log/tests/support/steering.rs         |    3 +-
 crates/event-log/tests/tool_ancestry.rs            |   28 +-
 crates/event-log/tests/tool_rejection.rs           |    1 -
 .../event-log/tests/transcript_index/fixtures.rs   |    3 +-
 crates/event-log/tests/transcript_tools.rs         |    3 +-
 .../event-log/tests/transcript_tools/payloads.rs   |    1 -
 crates/event-log/tests/turn_boundary.rs            |    4 +-
 crates/event-log/tests/visible_tail.rs             |   29 +-
 crates/event-log/tests/workhub.rs                  |    3 -
 crates/event-log/tests/workhub_attachments.rs      |    1 -
 crates/event-log/tests/workhub_candidates.rs       |    1 -
 crates/event-log/tests/workhub_correction.rs       |   53 +-
 crates/event-log/tests/workhub_creation.rs         |    1 -
 crates/event-log/tests/workhub_resume.rs           |    1 -
 crates/event-log/tests/workhub_revision.rs         |    1 -
 crates/event-log/tests/workhub_stop.rs             |   16 +-
 crates/js-runtime/src/plugin/sdk.js                |  237 ++--
 crates/plugins/src/authorization.rs                |  218 ++++
 crates/plugins/src/call.rs                         |  199 ++++
 .../invocation => plugins/src/call}/resources.rs   |    4 +-
 crates/plugins/src/client_capability.rs            |   19 +-
 crates/plugins/src/contributions.rs                |   76 +-
 crates/plugins/src/execution.rs                    |   59 +-
 crates/plugins/src/execution/root.rs               |   56 +
 crates/plugins/src/executor.rs                     |   70 +-
 crates/plugins/src/fiber.rs                        |    4 +-
 crates/plugins/src/filesystem.rs                   |   29 +-
 crates/plugins/src/host.rs                         |   45 +
 crates/plugins/src/http.rs                         |   85 ++
 crates/plugins/src/input.rs                        |   12 +-
 crates/plugins/src/kernel.rs                       |   12 +-
 crates/plugins/src/kernel/reconcile.rs             |   10 +-
 crates/plugins/src/lib.rs                          |    7 +
 crates/plugins/src/llm.rs                          |    8 +
 .../src/preferences.rs}                            |   23 +-
 crates/plugins/src/process.rs                      |  134 +++
 crates/plugins/src/remote.rs                       |   32 +-
 crates/plugins/src/services.rs                     |   49 +
 crates/plugins/src/services/method.rs              |  306 ++++++
 crates/plugins/src/session.rs                      |   27 +-
 crates/plugins/src/storage.rs                      |    1 +
 crates/plugins/src/storage/files.rs                |  339 ++++++
 crates/plugins/src/terminal.rs                     |   83 ++
 crates/plugins/tests/executor.rs                   |   10 +-
 crates/plugins/tests/kernel.rs                     |  152 ++-
 crates/plugins/tests/services.rs                   |  121 ++
 crates/presentation/src/compact.rs                 |    7 +-
 crates/presentation/tests/projection.rs            |    3 +-
 crates/presentation/tests/reference_projection.rs  |    1 -
 crates/presentation/tests/tools/fixtures.rs        |    3 +-
 crates/protocol/Cargo.toml                         |    1 -
 crates/protocol/src/authority.rs                   |    7 +-
 crates/protocol/src/capability/host_frame.rs       |   30 +-
 crates/protocol/src/codec.rs                       |   23 +-
 crates/protocol/src/handshake.rs                   |   45 +-
 crates/protocol/src/host/status.rs                 |    8 +-
 crates/protocol/src/lib.rs                         |    3 +-
 crates/protocol/src/message/input.rs               |   16 +-
 crates/protocol/src/message/output.rs              |   39 +-
 crates/protocol/src/operation.rs                   |    7 +-
 crates/protocol/src/plugin.rs                      |    5 +
 crates/protocol/src/plugin/authorization.rs        |   84 ++
 crates/protocol/src/plugin/input.rs                |    6 +
 crates/protocol/src/plugin/output.rs               |    4 +
 crates/protocol/src/plugin/remote.rs               |   15 +-
 crates/protocol/src/session.rs                     |   17 +-
 crates/protocol/src/session_mutation.rs            |   10 +-
 crates/protocol/src/session_types.rs               |   27 -
 crates/protocol/src/skills.rs                      |  140 ---
 crates/protocol/src/skills/catalog.rs              |  144 ---
 crates/protocol/src/skills/governance.rs           |   56 -
 crates/protocol/src/skills/import.rs               |   42 -
 crates/protocol/src/skills/mutation.rs             |   75 --
 crates/protocol/src/skills/path.rs                 |   38 -
 crates/protocol/src/skills/preview.rs              |   82 --
 crates/protocol/src/turn.rs                        |   75 +-
 crates/protocol/src/turn_skills.rs                 |   20 -
 crates/protocol/src/workhub.rs                     |    4 +-
 crates/protocol/tests/capability_frames.rs         |   30 +-
 crates/protocol/tests/message_contract.rs          |   12 +-
 crates/protocol/tests/session_contract.rs          |   28 +-
 crates/protocol/tests/session_mutation.rs          |   23 +-
 crates/protocol/tests/turn_contract.rs             |   25 +-
 crates/protocol/tests/wire_contract.rs             |   28 +-
 crates/runtime-host/Cargo.toml                     |    1 +
 crates/runtime-host/src/execution.rs               |    9 +-
 crates/runtime-host/src/execution/admission.rs     |   34 +-
 crates/runtime-host/src/execution/archive.rs       |   34 +-
 crates/runtime-host/src/execution/client_view.rs   |  132 ---
 crates/runtime-host/src/execution/input.rs         |   78 +-
 .../runtime-host/src/execution/input/prepared.rs   |   19 +-
 crates/runtime-host/src/execution/message.rs       |   46 +-
 .../runtime-host/src/execution/message/queued.rs   |   15 +-
 crates/runtime-host/src/execution/plugins.rs       |   63 +-
 .../src/execution/plugins/authority.rs             |  163 ++-
 .../runtime-host/src/execution/plugins/children.rs |    4 +-
 .../runtime-host/src/execution/plugins/effects.rs  |  339 ++++++
 .../src/execution/plugins/effects/client.rs        |  214 ++++
 crates/runtime-host/src/execution/plugins/llm.rs   |   79 +-
 crates/runtime-host/src/execution/plugins/root.rs  |  194 +++-
 .../runtime-host/src/execution/plugins/scopes.rs   |  295 +++++
 crates/runtime-host/src/execution/prepare.rs       |   26 +-
 .../src/execution/prepare/environment.rs           |   87 +-
 crates/runtime-host/src/execution/read.rs          |    5 +-
 .../src/execution/scheduler/delivery.rs            |    1 +
 crates/runtime-host/src/execution/skills.rs        |   85 --
 crates/runtime-host/src/execution/snapshot.rs      |   10 +-
 crates/runtime-host/src/execution/successor.rs     |    9 +-
 .../runtime-host/src/execution/workhub/answer.rs   |    1 -
 .../src/execution/workhub/correction.rs            |    2 +-
 .../src/execution/workhub/correction/settlement.rs |    5 +-
 crates/runtime-host/src/plugins.rs                 |    5 +
 crates/runtime-host/src/plugins/assistant.rs       |   31 +-
 .../runtime-host/src/plugins/assistant/prompt.rs   |   23 +-
 crates/runtime-host/src/plugins/effects.rs         |  194 ++++
 .../runtime-host/src/plugins/graph/definitions.rs  |    6 +-
 crates/runtime-host/src/plugins/host.rs            |  227 ++++
 crates/runtime-host/src/plugins/http.rs            |  245 +++++
 .../src/plugins/{javascript => }/http/worker.rs    |    2 -
 crates/runtime-host/src/plugins/javascript.rs      |   40 +-
 .../runtime-host/src/plugins/javascript/bridge.rs  |  431 +++++---
 .../src/plugins/javascript/bridge/service.rs       |  141 ++-
 .../src/plugins/javascript/bridge/wire.rs          |  205 +++-
 .../src/plugins/javascript/callbacks.rs            |   40 +-
 .../runtime-host/src/plugins/javascript/effects.rs |  142 ---
 .../src/plugins/javascript/executor.rs             |   14 +-
 crates/runtime-host/src/plugins/javascript/http.rs |  275 -----
 .../runtime-host/src/plugins/javascript/input.rs   |   28 +-
 .../src/plugins/javascript/invocation.rs           |  106 +-
 .../runtime-host/src/plugins/javascript/process.rs |  327 ------
 .../src/plugins/javascript/registration.rs         |   66 +-
 .../runtime-host/src/plugins/javascript/remote.rs  |   54 +-
 crates/runtime-host/src/plugins/process.rs         |  366 ++++++
 .../src/plugins/{javascript => }/process/worker.rs |    3 +-
 crates/runtime-host/src/plugins/remote.rs          |   32 +-
 crates/runtime-host/src/plugins/skills.rs          |   63 +-
 .../src/plugins/{javascript => }/terminal.rs       |  243 ++--
 .../plugins/{javascript => }/terminal/resource.rs  |   55 +-
 crates/runtime-host/src/plugins/wire.rs            |    3 +-
 crates/runtime-host/src/plugins/workhub.rs         |    4 -
 .../src/plugins/workhub/coordinator.rs             |    6 +-
 crates/runtime-host/src/plugins/workhub/remote.rs  |    2 +-
 .../src/plugins/workhub/tools/input.rs             |    2 +-
 crates/runtime-host/src/server.rs                  |   13 +-
 crates/runtime-host/src/server/authority.rs        |   48 +-
 crates/runtime-host/src/server/diagnostics.rs      |    2 +-
 crates/runtime-host/src/server/dispatch.rs         |   28 +-
 crates/runtime-host/src/server/handshake.rs        |   16 +-
 .../runtime-host/src/server/messages/capacity.rs   |    5 -
 .../runtime-host/src/server/messages/mutations.rs  |   15 +-
 crates/runtime-host/src/server/operations.rs       |   61 -
 .../src/server/plugin_authorization.rs             |  441 ++++++++
 crates/runtime-host/src/server/plugin_remote.rs    |   29 +-
 .../runtime-host/src/server/plugin_remote/views.rs |  232 ++++
 .../src/server/plugin_remote/worker.rs             |   12 +-
 crates/runtime-host/src/server/sessions.rs         |   26 +-
 crates/runtime-host/src/server/sessions/create.rs  |    2 +-
 .../runtime-host/src/server/sessions/mutation.rs   |    2 +-
 crates/runtime-host/src/server/skills.rs           |  127 ---
 crates/runtime-host/src/server/skills/sources.rs   |  159 ---
 .../src/server/subscriptions/transcript.rs         |    1 -
 crates/runtime-host/src/session/projection.rs      |    2 +-
 .../tests/fixtures/background-plugin.mjs           |  303 +++++
 .../runtime-host/tests/fixtures/files-plugin.mjs   |    2 +-
 crates/runtime-host/tests/fixtures/host-plugin.mjs |   74 +-
 crates/runtime-host/tests/fixtures/http-plugin.mjs |    2 +-
 .../runtime-host/tests/fixtures/remote-plugin.mjs  |   52 +
 .../tests/integration/access_expiry.rs             |    1 -
 .../tests/integration/artifact_boundary.rs         |    2 +-
 .../runtime-host/tests/integration/auto_context.rs |    2 +-
 .../tests/integration/client_capability.rs         |    5 +-
 .../runtime-host/tests/integration/client_forms.rs |    3 +-
 .../tests/integration/client_interactions.rs       |    3 +-
 .../tests/integration/connection_multiplex.rs      |    3 +-
 .../tests/integration/execution_drain.rs           |    3 +-
 crates/runtime-host/tests/integration/handoff.rs   |    2 -
 .../runtime-host/tests/integration/host_drain.rs   |    3 +-
 .../runtime-host/tests/integration/input_plugin.rs |    4 +-
 .../tests/integration/javascript_plugins.rs        |   25 +-
 .../integration/javascript_plugins/services.rs     |  231 ++++
 crates/runtime-host/tests/integration/main.rs      |    1 +
 .../tests/integration/message_recovery.rs          |   12 -
 .../tests/integration/message_submit.rs            |   40 +-
 .../tests/integration/model_overrides.rs           |    2 +-
 .../tests/integration/plugin_authorization.rs      |  422 +++++++
 .../tests/integration/plugin_clients.rs            |    4 +-
 .../tests/integration/plugin_commands.rs           |    2 +-
 .../tests/integration/plugin_commands/boundary.rs  |   12 +
 .../runtime-host/tests/integration/plugin_files.rs |    2 +-
 .../tests/integration/plugin_remote.rs             |   19 +
 .../tests/integration/plugin_remote/fixture.rs     |   27 +-
 .../tests/integration/plugin_remote/javascript.rs  |   75 ++
 crates/runtime-host/tests/integration/pruning.rs   |    2 +-
 .../tests/integration/question_boundary.rs         |    2 +-
 .../tests/integration/remote_capacity.rs           |    1 -
 .../runtime-host/tests/integration/retirement.rs   |    8 +-
 .../tests/integration/skills_management.rs         |  140 +--
 .../tests/integration/skills_plugin.rs             |  107 +-
 .../tests/integration/skills_plugin/client.rs      |   58 +
 .../tests/integration/support/message_recovery.rs  |    2 +-
 .../runtime-host/tests/integration/support/peer.rs |    3 +-
 .../tests/integration/transcript_limits.rs         |    1 -
 .../tests/integration/transcript_pager.rs          |    1 -
 crates/runtime-host/tests/integration/workhub.rs   |   53 +-
 crates/runtime/src/archive/reference.rs            |   75 +-
 crates/runtime/src/capability.rs                   |    2 +-
 crates/runtime/src/capability/frame.rs             |   27 +-
 crates/runtime/src/configuration/policy.rs         |    2 +-
 crates/runtime/src/configuration/policy/codec.rs   |    8 +-
 crates/runtime/src/context.rs                      |   37 +-
 crates/runtime/src/context/checkpoint_mode.rs      |    6 -
 crates/runtime/src/event.rs                        |    8 +-
 crates/runtime/src/event_write.rs                  |   12 +-
 crates/runtime/src/handoff/execution.rs            |   60 +-
 crates/runtime/src/input.rs                        |   39 +-
 crates/runtime/src/input/selections.rs             |   46 +
 crates/runtime/src/message.rs                      |   38 +-
 crates/runtime/src/skills.rs                       |   14 -
 crates/runtime/src/tools/journal.rs                |   20 +-
 crates/runtime/src/tools/preparation.rs            |   19 +
 crates/runtime/src/workhub.rs                      |    1 -
 crates/runtime/src/workhub/correction.rs           |    5 +-
 crates/runtime/tests/archive.rs                    |   56 +-
 crates/skills/src/catalog.rs                       |   15 +-
 crates/skills/src/discovery/sources.rs             |   10 +-
 crates/skills/src/plugin.rs                        |   39 +-
 crates/skills/src/plugin/mutation.rs               |   19 +-
 crates/skills/src/plugin/preferences.rs            |  161 +++
 crates/skills/src/plugin/remote.rs                 |   28 +-
 crates/skills/src/publication.rs                   |   17 -
 crates/skills/tests/management.rs                  |  323 ------
 crates/skills/tests/plugin.rs                      |  294 -----
 crates/skills/tests/publication.rs                 |   18 +-
 crates/tool-catalog/src/plugins.rs                 |   24 +-
 crates/tool-catalog/src/plugins/binding.rs         |    1 +
 crates/tools/src/client.rs                         |    6 +-
 .../tools/tests/integration/client_preparation.rs  |    7 +-
 crates/tools/tests/integration/commit_faults.rs    |    3 +-
 crates/tools/tests/integration/plugins.rs          |   54 +-
 .../tests/integration/support/model_fixture.rs     |    3 +-
 .../tools/tests/integration/support/preflight.rs   |    3 +-
 package-lock.json                                  |    1 +
 .../cli/src/__tests__/acp-session-registry.test.ts |   48 +-
 packages/cli/src/__tests__/pi-tui-runner.test.ts   |   88 +-
 ...untime-host-capability-provider-command.test.ts |    3 +-
 .../__tests__/runtime-host-profile-command.test.ts |    9 +-
 .../__tests__/runtime-host-service-manager.test.ts |   30 +-
 .../__tests__/runtime-host-session-driver.test.ts  |   47 +-
 .../__tests__/runtime-host-session-update.test.ts  |   13 -
 packages/cli/src/acp/session-registry.ts           |   10 +-
 packages/cli/src/pi-tui-runner.ts                  |   13 +-
 packages/cli/src/pi-tui-turn.ts                    |    4 +-
 packages/cli/src/runtime-host-cli.ts               |   17 +-
 packages/cli/src/runtime-host-run-command.ts       |    9 +-
 packages/cli/src/runtime-host-service-manager.ts   |   24 +-
 packages/cli/src/runtime-host-session-driver.ts    |   61 +-
 packages/cli/src/runtime-host-session-update.ts    |   26 +-
 packages/cli/src/runtime-host-skills.ts            |   94 ++
 packages/cli/src/runtime-host-tui-context.ts       |   11 +-
 packages/cli/src/runtime-host-update-command.ts    |   42 +-
 packages/cli/src/session-driver.ts                 |   20 +-
 packages/core/src/skill-invocation.ts              |   25 +
 packages/plugin-sdk/README.md                      |    3 +
 packages/plugin-sdk/README.zh-CN.md                |    4 +
 packages/plugin-sdk/src/authorization.ts           |   60 +
 packages/plugin-sdk/src/client.ts                  |    9 +
 packages/plugin-sdk/src/execution.ts               |   49 +-
 packages/plugin-sdk/src/filesystem.ts              |    9 +-
 packages/plugin-sdk/src/host.ts                    |  143 ++-
 packages/plugin-sdk/tsconfig.acceptance.json       |    1 +
 packages/runtime-host/package.json                 |    1 +
 .../src/__tests__/authenticated-websocket.test.ts  |   18 -
 .../src/__tests__/catalog-reader.test.ts           |   83 +-
 .../__tests__/client-capability-channel.test.ts    |   12 +-
 .../client-capability-coordinator.test.ts          |    7 +-
 .../__tests__/client-capability-protocol.test.ts   |   58 +-
 .../src/__tests__/connection-session.test.ts       |    4 +-
 .../src/__tests__/execution-composition.test.ts    |  495 ---------
 .../src/__tests__/execution-host-recovery.test.ts  |   21 +-
 .../src/__tests__/host-handoff.test.ts             |   42 +-
 .../runtime-host/src/__tests__/host-kernel.test.ts |   25 +-
 .../src/__tests__/host-profile.test.ts             |   55 +-
 .../src/__tests__/message-coordinator.test.ts      |    2 +-
 .../runtime-host/src/__tests__/protocol.test.ts    |  346 ++----
 .../src/__tests__/root-turn-coordinator.test.ts    |   10 +-
 .../src/__tests__/session-catalog-protocol.test.ts |   40 +-
 .../session-catalog-two-client-uds.test.ts         |   84 +-
 .../session-collaboration-authority.test.ts        |    6 +-
 .../session-revision-two-client-uds.test.ts        |    8 +-
 .../__tests__/skill-catalog-coordinator.test.ts    |  301 -----
 .../src/__tests__/skill-catalog-protocol.test.ts   |  725 ------------
 .../src/__tests__/skill-catalog-repository.test.ts |  180 +--
 .../__tests__/skill-catalog-two-client-uds.test.ts |  254 -----
 .../src/__tests__/ssh-operator-activation.test.ts  |   11 +-
 packages/runtime-host/src/client/catalog-reader.ts |  100 +-
 packages/runtime-host/src/client/connection.ts     |   15 +-
 .../runtime-host/src/client/host-handoff-copy.ts   |   11 +-
 packages/runtime-host/src/client/host-profile.ts   |  133 +--
 packages/runtime-host/src/client/index.ts          |    4 +-
 packages/runtime-host/src/client/plugin-remote.ts  |  151 +++
 .../src/client/ssh-operator-activation.ts          |    2 +-
 packages/runtime-host/src/operator/index.ts        |    2 -
 .../runtime-host/src/operator/operator-command.ts  |   35 +-
 packages/runtime-host/src/operator/setup-frame.ts  |    6 +-
 .../runtime-host/src/protocol/client-capability.ts |   45 +-
 .../runtime-host/src/protocol/external-session.ts  |   14 +-
 packages/runtime-host/src/protocol/host-status.ts  |   25 +-
 packages/runtime-host/src/protocol/index.ts        |  395 +------
 packages/runtime-host/src/protocol/message.ts      |   61 +-
 packages/runtime-host/src/protocol/operations.ts   |   15 +-
 .../src/protocol/plugin-authorization.ts           |  223 ++++
 .../runtime-host/src/protocol/plugin-remote.ts     |   25 +-
 .../runtime-host/src/protocol/session-catalog.ts   |   51 +-
 .../src/protocol/session-retirement.ts             |    9 +-
 .../runtime-host/src/protocol/session-revision.ts  |    9 +-
 .../runtime-host/src/protocol/session-status.ts    |   10 +-
 .../runtime-host/src/protocol/skill-catalog.ts     | 1048 +-----------------
 packages/runtime-host/src/protocol/turn.ts         |  138 ++-
 .../src/protocol/workhub-coordination.ts           |    8 +-
 .../server/client-capability-invocation-broker.ts  |    3 +-
 .../src/server/execution-composition.ts            |   68 +-
 packages/runtime-host/src/server/host-kernel.ts    |   27 +-
 .../src/server/interactive-turn-coordinator.ts     |   20 +-
 .../runtime-host/src/server/message-coordinator.ts |   28 +-
 .../src/server/operation-dispatcher.ts             |    3 -
 .../src/server/session-catalog-coordinator.ts      |   24 +-
 .../src/server/session-retirement-coordinator.ts   |    6 +-
 .../src/server/skill-catalog-coordinator.ts        |  249 +----
 packages/runtime/src/tool-runtime.ts               |   17 +-
 packages/storage/package.json                      |    1 -
 .../storage/src/__tests__/credential-store.test.ts |   53 +-
 .../storage/src/__tests__/file-update-lock.test.ts |    4 -
 .../__tests__/fixtures/file-update-lock-holder.ts  |   14 +-
 packages/storage/src/credential-store.ts           |   37 +-
 packages/storage/src/file-update-lock.ts           |   52 -
 .../src/process-lifetime-file-update-lock.ts       |  125 +--
 packages/ui/src/client-plugins/index.ts            |    2 +-
 packages/ui/src/client-plugins/instance.ts         |   24 +-
 packages/ui/src/client-plugins/runtime.ts          |    5 +-
 tests/fixtures/client-artifact-entry.mjs           |    2 +-
 tests/fixtures/client-candidate-entry.mjs          |    3 +-
 tests/fixtures/client-capability-host.mjs          |    4 +-
 tests/fixtures/client-capability-service.mjs       |    8 +-
 tests/fixtures/client-entry.mjs                    |    2 +-
 tests/fixtures/client-message-recovery.mjs         |    2 +-
 tests/fixtures/client-message-submit.mjs           | 1162 ++++++++++----------
 tests/fixtures/client-model-overrides-entry.mjs    |    2 +-
 tests/fixtures/client-plugin-remote.mjs            |   41 +
 tests/fixtures/client-runtime-policy-entry.mjs     |    2 +-
 tests/fixtures/client-skills-entry.mjs             |   32 +-
 tests/fixtures/client-system-prompt-entry.mjs      |    2 +-
 tests/fixtures/client-turn.mjs                     |    2 +-
 tests/fixtures/client-workhub-answer.mjs           |    4 +-
 tests/fixtures/client-workhub-delegation.mjs       |    4 +-
 tests/fixtures/client-workhub-entry.mjs            |   20 +-
 tests/fixtures/client-workhub-plugin.mjs           |   42 +-
 tests/fixtures/client-workhub-queue.mjs            |    2 +-
 562 files changed, 13151 insertions(+), 15406 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 650fdd679a..a1491c8723 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4416,7 +4416,6 @@ dependencies = [
  "maka-plugins",
  "maka-presentation",
  "maka-runtime",
- "maka-skills",
  "regress",
  "serde",
  "serde_json",
@@ -4454,6 +4453,7 @@ dependencies = [
  "hyper",
  "hyper-util",
  "jiff",
+ "jsonschema",
  "libc",
  "maka-agent",
  "maka-client-capability",
diff --git a/apps/desktop/src/main/__tests__/browser-message-box.test.ts b/apps/desktop/src/main/__tests__/browser-message-box.test.ts
index cbb8e717f8..13beed42ca 100644
--- a/apps/desktop/src/main/__tests__/browser-message-box.test.ts
+++ b/apps/desktop/src/main/__tests__/browser-message-box.test.ts
@@ -141,6 +141,20 @@ test('drives the BrowserWindow lifecycle through a safe response URL', async ()
 });
 
 test('maps close to cancel and falls back after each BrowserWindow presentation failure', async () => {
+  const cancelled = fakeBrowserWindow({pendingLoad:true});
+  const cancellation = new AbortController();
+  const cancelledResult = showBrowserMessageBoxWithRuntime(
+    {message:'Allow background work?',buttons:['Cancel','Allow'],cancelId:0,signal:cancellation.signal},
+    undefined, ACTIVE_APPEARANCE, runtimeForWindow(cancelled),
+  );
+  cancellation.abort();
+  assert.deepEqual(await cancelledResult,{response:0,checkboxChecked:false});
+  assert.equal(cancelled.destroyed(),true);
+  const failed = fakeBrowserWindow({loadError:new Error('renderer failed')});
+  await assert.rejects(showBrowserMessageBoxWithRuntime(
+    {message:'Allow background work?',signal:new AbortController().signal},
+    undefined, ACTIVE_APPEARANCE, runtimeForWindow(failed),
+  ), /renderer failed/); // Never enter a synchronous, uninterruptible native fallback.
   const closed = fakeBrowserWindow();
   const closeResult = showBrowserMessageBoxWithRuntime(
     { message: 'Recover Maka', buttons: ['Recover', 'Cancel'], cancelId: 1 },
diff --git a/apps/desktop/src/main/__tests__/browser-tools.test.ts b/apps/desktop/src/main/__tests__/browser-tools.test.ts
index 6ecce3c0e2..390238dee7 100644
--- a/apps/desktop/src/main/__tests__/browser-tools.test.ts
+++ b/apps/desktop/src/main/__tests__/browser-tools.test.ts
@@ -280,8 +280,7 @@ describe('browser tool execution', () => {
         serverId: 'desktop_browser',
         toolName: 'browser_snapshot',
         arguments: {},
-        sessionId: 's1',
-        turnId: 't1',
+        source: { kind: 'agent', sessionId: 's1', turnId: 't1' },
         toolCallId: 'c1',
         cwd: '/tmp',
       },
diff --git a/apps/desktop/src/main/__tests__/client-plugin-remote.test.ts b/apps/desktop/src/main/__tests__/client-plugin-remote.test.ts
index 374d8b4855..5ff2d56758 100644
--- a/apps/desktop/src/main/__tests__/client-plugin-remote.test.ts
+++ b/apps/desktop/src/main/__tests__/client-plugin-remote.test.ts
@@ -41,6 +41,73 @@ function deferred<T>() {
   return { resolve, promise };
 }
 
+test('plugin consent is explicit and cannot outlive its document or publication', async () => {
+  const owner = renderer();
+  const handlers = new Map<string, IpcHandler>();
+  let nonce = randomUUID();
+  let published = true;
+  let confirmation = deferred<boolean>();
+  let showing = deferred<AbortSignal>();
+  let writes = 0;
+  const identity = {entryId:'view',extensionId:'example',activation:randomUUID(),contentDigest:'sha256-'+'a'.repeat(64),clientDigest:'sha256-'+'b'.repeat(64)};
+  const proposal = {client:identity,scope:'profile',command:{kind:'approve',request:{operationId:randomUUID(),title:'Send reminders',target:{kind:'profile'},capabilities:['notifications']}}};
+  const dispose = registerClientPluginRemoteIpc({
+    ipcMain: {handle: (name, handler) => {handlers.set(name,handler);}},
+    ownsRenderer: (contents) => contents === owner.emitter as unknown as WebContents,
+    report: assert.ifError,
+    client: {hostEpoch:'host',async request() {throw new Error('Unexpected Remote command');}},
+    authorization: {
+      async validate() {if (!published) throw new Error('Client publication retired');},
+      confirm(_input, signal) {showing.resolve(signal); return confirmation.promise;},
+      async request() {writes++; return {kind:'grant',grant:null};},
+    },
+  });
+  try {
+    const {epoch} = await handlers.get('plugins:connection')!(owner.event, nonce);
+    const invoke = (requestId = randomUUID()) => handlers.get('plugins:authorization')!(owner.event, nonce, epoch, proposal, requestId);
+    let pending = invoke();
+    await showing.promise;
+    confirmation.resolve(false);
+    assert.deepEqual(await pending,{kind:'grant',grant:null});
+    assert.equal(writes,0);
+    confirmation = deferred(); showing = deferred();
+    pending = invoke();
+    await showing.promise;
+    confirmation.resolve(true);
+    await pending;
+    assert.equal(writes,1);
+    confirmation = deferred(); showing = deferred();
+    const requestId = randomUUID();
+    pending = invoke(requestId);
+    const retiredSignal = await showing.promise;
+    await handlers.get('plugins:authorization-cancel')!(owner.event, randomUUID(), epoch, requestId);
+    assert.equal(retiredSignal.aborted,false,'another document cannot cancel this approval');
+    await handlers.get('plugins:authorization-cancel')!(owner.event, nonce, epoch, requestId);
+    assert.equal(retiredSignal.aborted,true);
+    confirmation.resolve(true);
+    assert.deepEqual(await pending,{kind:'grant',grant:null});
+    assert.equal(writes,1);
+    confirmation = deferred(); showing = deferred();
+    pending = invoke();
+    const signal = await showing.promise;
+    owner.emitter.emit('did-start-navigation',{},'new-page',false,true);
+    assert.equal(signal.aborted,true);
+    confirmation.resolve(true); // A late click must never reach Host.
+    assert.deepEqual(await pending,{kind:'grant',grant:null});
+    assert.equal(writes,1);
+    nonce = randomUUID();
+    await handlers.get('plugins:connection')!(owner.event, nonce);
+    confirmation = deferred(); showing = deferred();
+    const rejected = assert.rejects(invoke(),/publication retired/);
+    await showing.promise;
+    published = false;
+    confirmation.resolve(true);
+    await rejected;
+    assert.equal(writes,1);
+  } finally {await dispose();}
+  assert.deepEqual(owner.emitter.eventNames(),[]);
+});
+
 test('reconnecting to the same Host revokes old Remote leases without fencing the replacement Client', async () => {
   const owner = renderer();
   const nonce = randomUUID();
@@ -101,7 +168,7 @@ test('Remote documents belong to one Renderer and drain navigation, late opens a
   let late: ReturnType<typeof deferred<string>> | undefined;
   let attempts = 0;
   const dispose = registerClientPluginRemoteIpc({
-    ipcMain: { handle: (channel, listener) => { if (channel === 'plugins:remote') handler = listener; else if (channel === 'plugins:files') files = listener; else connection = listener; } },
+    ipcMain: { handle: (channel, listener) => { if (channel === 'plugins:remote') handler = listener; else if (channel === 'plugins:files') files = listener; else if (channel === 'plugins:connection') connection = listener; } },
     ownsRenderer: (contents: WebContents) => allowed.has(contents as unknown as typeof one.emitter),
     report: (error) => errors.push(error),
     client: { hostEpoch: 'host-process', async request(_operation, input) {
diff --git a/apps/desktop/src/main/__tests__/main-process-diagnostics.test.ts b/apps/desktop/src/main/__tests__/main-process-diagnostics.test.ts
index dc75925683..a6f9ebd10c 100644
--- a/apps/desktop/src/main/__tests__/main-process-diagnostics.test.ts
+++ b/apps/desktop/src/main/__tests__/main-process-diagnostics.test.ts
@@ -62,12 +62,12 @@ const runtimeHostDiagnostics = {
   activeOperations: 1,
   activeResidencies: 0,
   upgradeBlockingActivity: true,
+  drainResidencies: 0,
   residencies: [],
   protocolVersion: 0,
   compatibilityEpoch: 16,
   pid: 42,
   processUptimeSeconds: 37,
-  nodeVersion: '22.0.0',
   platform: 'linux' as const,
   arch: 'x64',
   osRelease: '6.6.0',
diff --git a/apps/desktop/src/main/__tests__/managed-artifact-preview.test.ts b/apps/desktop/src/main/__tests__/managed-artifact-preview.test.ts
index 61c99344a7..e3265d7123 100644
--- a/apps/desktop/src/main/__tests__/managed-artifact-preview.test.ts
+++ b/apps/desktop/src/main/__tests__/managed-artifact-preview.test.ts
@@ -158,7 +158,7 @@ test('tool binds to the admitted session and returns endpoint evidence only', as
       return service.prepare('h', client(), sessionId, artifactId, signal);
     });
     assert.equal((tool!.parameters as z.ZodType).safeParse({ artifactId: 'a1', sessionId: 'other' }).success, false);
-    const result = await tool!.impl({ artifactId: 'a1' }, { sessionId: 's1', turnId: 't1', cwd: '/tmp', toolCallId: 'c1', abortSignal: new AbortController().signal, emitOutput: () => {} });
+    const result = await tool!.impl({ artifactId: 'a1' }, { sessionId: 's1', cwd: '/tmp', toolCallId: 'c1', abortSignal: new AbortController().signal, emitOutput: () => {} });
     assert.equal((result as { loaded: boolean }).loaded, false);
   } finally { await service.close(); }
 });
diff --git a/apps/desktop/src/main/__tests__/mcp-runtime-e2e.test.ts b/apps/desktop/src/main/__tests__/mcp-runtime-e2e.test.ts
index 7d14c551ed..4d76c4eb8b 100644
--- a/apps/desktop/src/main/__tests__/mcp-runtime-e2e.test.ts
+++ b/apps/desktop/src/main/__tests__/mcp-runtime-e2e.test.ts
@@ -118,8 +118,7 @@ test('MCP tools stay bound to the connection generation that advertised them', a
           serverId: 'fixture',
           toolName: 'missing',
           arguments: {},
-          sessionId: 'session',
-          turnId: 'turn',
+          source: { kind: 'agent', sessionId: 'session', turnId: 'turn' },
           toolCallId: 'incompatible-capability-call',
           cwd: process.cwd(),
         },
@@ -142,8 +141,7 @@ test('MCP tools stay bound to the connection generation that advertised them', a
           serverId: 'fixture',
           toolName: 'annotated',
           arguments: { fallback: 'desktop-capability' },
-          sessionId: 'session',
-          turnId: 'turn',
+          source: { kind: 'agent', sessionId: 'session', turnId: 'turn' },
           toolCallId: 'annotated-capability-call',
           cwd: process.cwd(),
         },
@@ -171,8 +169,7 @@ test('MCP tools stay bound to the connection generation that advertised them', a
           serverId: 'fixture',
           toolName: 'echo',
           arguments: { value: 'desktop-capability' },
-          sessionId: 'session',
-          turnId: 'turn',
+          source: { kind: 'agent', sessionId: 'session', turnId: 'turn' },
           toolCallId: 'capability-call',
           cwd: process.cwd(),
         },
diff --git a/apps/desktop/src/main/__tests__/permission-response-ipc-boundary.test.ts b/apps/desktop/src/main/__tests__/permission-response-ipc-boundary.test.ts
index d7dcff031c..5eb378e9f8 100644
--- a/apps/desktop/src/main/__tests__/permission-response-ipc-boundary.test.ts
+++ b/apps/desktop/src/main/__tests__/permission-response-ipc-boundary.test.ts
@@ -173,7 +173,7 @@ describe('permission response IPC boundary', () => {
         turnId: 'turn-1',
         text: 'review @packages/ui/src/chat turn.tsx',
         displayText: 'review @packages/ui/src/chat turn.tsx',
-        skillIds: ['weekly-report', 'project:maka:writer'],
+        inputSelections: { "maka.skills": ['weekly-report', 'project:maka:writer'] },
         attachmentItems: [{ approvalId: 'a', name: 'n' }],
         retainedAttachments: [
           {
@@ -207,7 +207,7 @@ describe('permission response IPC boundary', () => {
         turnId: 'turn-1',
         text: 'review @packages/ui/src/chat turn.tsx',
         displayText: 'review @packages/ui/src/chat turn.tsx',
-        skillIds: ['weekly-report', 'project:maka:writer'],
+        inputSelections: { "maka.skills": ['weekly-report', 'project:maka:writer'] },
         attachmentItems: [{ approvalId: 'a', name: 'n' }],
         retainedAttachments: [
           {
@@ -233,10 +233,10 @@ describe('permission response IPC boundary', () => {
       },
     );
     assert.equal(normalizeSessionSendCommand({ type: 'stop' }), undefined);
-    assert.deepEqual(normalizeSessionSendCommand({ type: 'send', text: '', skillIds: ['writer'] }), {
+    assert.deepEqual(normalizeSessionSendCommand({ type: 'send', text: '', inputSelections: { "maka.skills": ['writer'] } }), {
       type: 'send',
       text: '',
-      skillIds: ['writer'],
+      inputSelections: { "maka.skills": ['writer'] },
     });
   });
 
@@ -256,7 +256,7 @@ describe('permission response IPC boundary', () => {
       // base64 bytes before IPC, and main resolves only the encoded shapes.
       { type: 'send', text: 'hello', attachmentItems: [{ file: {} }] },
       { type: 'send', text: 'hello', turnId: 1 },
-      { type: 'send', text: 'hello', skillIds: ['/bad'] },
+      { type: 'send', text: 'hello', inputSelections: { "maka.skills": ['bad\nselector'] } },
       { type: 'send', text: 'hello', turnOrchestration: { mode: 'swarm', source: 'prompt' } },
       { type: 'send', text: 'hello', quotes: {} },
       { type: 'send', text: 'hello', quotes: Array(17).fill({ text: 'x' }) },
diff --git a/apps/desktop/src/main/__tests__/runtime-host-bot-session-adapter.test.ts b/apps/desktop/src/main/__tests__/runtime-host-bot-session-adapter.test.ts
index 780eaae806..9fe7a16b59 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-bot-session-adapter.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-bot-session-adapter.test.ts
@@ -325,11 +325,12 @@ test('returns blocked Skill feedback without waiting for a Turn that was not cre
       }),
       startTurn: async () => ({
         kind: 'blocked',
-        skillInvocation: {
+        message: 'writer: not_found',
+        preparation: fixturePreparation({
           loaded: [],
           failed: [{ request: 'writer', reason: 'not_found' }],
           receipts: [],
-        },
+        }),
       }),
     }),
     resolveCreateTarget: hostPathCreateTarget,
@@ -449,7 +450,7 @@ function startedTurn(turn: TurnSnapshot) {
   return {
     kind: 'started' as const,
     turn,
-    skillInvocation: { loaded: [], failed: [], receipts: [] },
+    preparation: [],
   };
 }
 
@@ -538,3 +539,7 @@ class AsyncFrameQueue implements AsyncIterable<SubscriptionFrame> {
     };
   }
 }
+
+function fixturePreparation(receipt: unknown): import('@maka/runtime-host/protocol').InputReceipt[] {
+  return [{ source: { kind: 'input', name: 'maka.skills', packageId: 'maka.skills', entryId: 'skills', activation: "fixture", revision: "1" }, receipt: JSON.parse(JSON.stringify(receipt)) }];
+}
diff --git a/apps/desktop/src/main/__tests__/runtime-host-client-uds.test.ts b/apps/desktop/src/main/__tests__/runtime-host-client-uds.test.ts
index d0b67b69b2..7e8c75301e 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-client-uds.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-client-uds.test.ts
@@ -25,7 +25,7 @@ import test from 'node:test';
 import type { IpcMain } from 'electron';
 import type { BotRegistry } from '@maka/runtime/bots';
 import type { ComputerUseToolSet } from '@maka/runtime/computer-use-tools';
-import type { MakaTool } from '@maka/runtime/tool-runtime';
+import type { SessionTool } from '@maka/runtime/tool-runtime';
 import { connectRuntimeHost } from '@maka/runtime-host/client';
 import { acquireOperationalStateDatabase } from '@maka/storage/operational-state-store';
 import {
@@ -82,7 +82,7 @@ test('drives Desktop Session operations through a real Runtime Host connection',
               result: {
                 disposition: 'steering',
                 queueRevision: 1,
-                skillInvocation: { loaded: [], failed: [], receipts: [] },
+                preparation: [],
               },
             };
           },
@@ -118,7 +118,7 @@ test('drives Desktop Session operations through a real Runtime Host connection',
       {
         disposition: 'steering',
         queueRevision: 1,
-        skillInvocation: { loaded: [], failed: [], receipts: [] },
+        preparation: [],
       },
     );
 
@@ -390,7 +390,7 @@ test('drives the renderer Session execution facade through real UDS framing', as
               result: {
                 disposition: 'turn_started',
                 turnId: 'turn-host-1',
-                skillInvocation: { loaded: [], failed: [], receipts: [] },
+                preparation: [],
               },
             };
           },
@@ -444,7 +444,7 @@ test('drives the renderer Session execution facade through real UDS framing', as
         turnId: 'turn-host-1',
         attachments: [],
         inlineReferences: [],
-        skillInvocation: { loaded: [], failed: [], receipts: [] },
+        preparation: [],
       },
     );
 
@@ -643,7 +643,7 @@ function session(
   };
 }
 
-function nativeTool(): MakaTool {
+function nativeTool(): SessionTool {
   return {
     name: 'browser_snapshot',
     description: 'Capture the current page.',
diff --git a/apps/desktop/src/main/__tests__/runtime-host-desktop-candidate.test.ts b/apps/desktop/src/main/__tests__/runtime-host-desktop-candidate.test.ts
index 7509f10fd9..5d2fa0d2e5 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-desktop-candidate.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-desktop-candidate.test.ts
@@ -30,6 +30,7 @@ import type { BotIncomingMessage, BotRegistry } from '@maka/runtime/bots';
 import type { ComputerUseToolSet } from '@maka/runtime/computer-use-tools';
 import type { ShellRunUpdate } from '@maka/core/events';
 import type { MakaTool } from '@maka/runtime/tool-runtime';
+import type { SessionTool } from '@maka/runtime/tool-runtime';
 import type {
   ClientCapabilityProvider,
   ConnectOrSpawnRuntimeHostInput,
@@ -150,6 +151,7 @@ test('updates a protocol-compatible managed Host before exposing a candidate ove
               activeOperations: 0,
               processUptimeSeconds: 1,
               residencies: [],
+              drainResidencies: 0,
             },
             replacement: {
               kind: 'repair',
@@ -733,7 +735,7 @@ test('closes the claimed Host connection when native capability construction fai
   const invalidTool = {
     ...nativeTool(),
     parameters: z.string(),
-  } as unknown as MakaTool;
+  } as unknown as SessionTool;
 
   await assert.rejects(
     () =>
@@ -1280,7 +1282,7 @@ function emptyComputerUseTools(): ComputerUseToolSet {
   return Object.assign([], { clearSession() {} }) as unknown as ComputerUseToolSet;
 }
 
-function nativeTool(): MakaTool {
+function nativeTool(): SessionTool {
   return {
     name: 'browser_snapshot',
     description: 'Capture the current page.',
@@ -1696,8 +1698,7 @@ function capabilityFrame(sessionId: string): ClientCapabilityCallFrame {
     offerId: 'desktop_browser',
     serverId: 'desktop_browser',
     toolName: 'browser_snapshot',
-    sessionId,
-    turnId: `turn-${sessionId}`,
+    source: { kind: 'agent', sessionId, turnId: `turn-${sessionId}` },
     cwd: '/workspace',
     toolCallId: `tool-${sessionId}`,
     arguments: {},
diff --git a/apps/desktop/src/main/__tests__/runtime-host-desktop-manager.test.ts b/apps/desktop/src/main/__tests__/runtime-host-desktop-manager.test.ts
index 47bfb39e9c..9fe810c91d 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-desktop-manager.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-desktop-manager.test.ts
@@ -1867,7 +1867,7 @@ test('an exact legacy ephemeral stop always requires consent and preserves the O
     registration: { ...observed.registration, lifecycleMode: 'ephemeral' as const },
     processIdentity: { startIdentity: 'darwin:1700000000:123456' },
     handshake: { ...observed.handshake, activity: {
-      connections: 0, activeOperations: 0, processUptimeSeconds: 60, residencies: [],
+      connections: 0, activeOperations: 0, processUptimeSeconds: 60, residencies: [], drainResidencies: 0,
     } },
   };
   const replacement = candidateHarness();
@@ -1924,7 +1924,7 @@ test('keeps a known repair actionable when its first authority inspection fails'
       return {
         identity: 'verified-repair', target: { name: 'Local', location: 'local' },
         reason: 'repair', mayExitNaturally: false,
-        activity: { connections: 0, activeOperations: 0, processUptimeSeconds: 1, residencies: [] },
+        activity: { connections: 0, activeOperations: 0, processUptimeSeconds: 1, residencies: [], drainResidencies: 0 },
         replacement: { kind: 'repair', canReplaceIdle: true, canInterrupt: false,
           execute: async () => { didRepair = true; return { kind: 'completed' }; } },
       };
@@ -2008,6 +2008,7 @@ function upgradeRequired(
               activeOperations,
               processUptimeSeconds: 60,
               residencies,
+              drainResidencies: residencies.reduce((count, item) => count + item.count, 0),
             },
           }
         : {}),
diff --git a/apps/desktop/src/main/__tests__/runtime-host-local-remote-access.test.ts b/apps/desktop/src/main/__tests__/runtime-host-local-remote-access.test.ts
index 3c788c7f50..8ab42ea048 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-local-remote-access.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-local-remote-access.test.ts
@@ -518,76 +518,7 @@ test('does not persist recoverable setup authority before Desktop ownership comm
   assert.equal(setupCalls, 0);
 });
 
-test('adopts a released handoff through its existing legacy operator', async (t) => {
-  const base = await mkdtemp(join(tmpdir(), 'maka-local-remote-access-prestart-'));
-  t.after(() => rm(base, { recursive: true, force: true }));
-  const clientDataRoot = join(base, 'client');
-  const rootPath = join(clientDataRoot, 'workspaces', 'default');
-  const rootId = 'a'.repeat(64);
-  const deploymentId = '22222222-2222-4222-8222-222222222222';
-  const deploymentRoot = join(base, 'installed');
-  await mkdir(rootPath, { recursive: true });
-  await writeFile(
-    join(clientDataRoot, 'runtime-host-local-service.json'),
-    `${JSON.stringify({
-      schemaVersion: 1,
-      state: 'handoff',
-      rootPath,
-      rootId,
-      coordinationRelays: [],
-      allowInterruptActiveTasks: true,
-    })}\n`,
-  );
-  const service = createDesktopLocalRuntimeHostRemoteAccess({
-    ipcMain: { handle() {}, removeHandler() {} },
-    clientDataRoot,
-    rootPath,
-    rootId,
-    directPeerAvailable: false,
-    manager: () => assert.fail('pre-start reconciliation must not require the Local manager'),
-    resolveManagedDeploymentAuthority: async () => ({
-      kind: 'active',
-      lifecycleMode: 'supervised',
-      deploymentRoot,
-      target: {
-        schemaVersion: 2,
-        serviceId: rootId,
-        operator: testOperator(join(deploymentRoot, 'operator.mjs')),
-        rootPath,
-        rootId,
-        deploymentId,
-      },
-    }),
-    resolveSetupPackage: async () =>
-      assert.fail('committed authority must not resolve a package'),
-    operator: {
-      async runSetup() {
-        assert.fail('committed authority must not replay setup');
-      },
-      async close() {},
-    } as unknown as ReturnType<typeof createDesktopRuntimeHostLocalOperator>,
-  });
-  t.after(() => service.close());
-
-  assert.equal(await service.recoverBeforeLocalHostStart(), true);
-  assert.deepEqual(
-    JSON.parse(await readFile(join(clientDataRoot, 'runtime-host-local-service.json'), 'utf8')),
-    {
-      schemaVersion: 2,
-      state: 'managed',
-      serviceId: rootId,
-      operator: {
-        kind: 'legacy_posix_executable',
-        executablePath: join(deploymentRoot, 'operator'),
-      },
-      rootPath,
-      rootId,
-      deploymentId,
-    },
-  );
-});
-
-test('migrates a released managed receipt before exposing it to lifecycle operations', async (t) => {
+test('rejects an obsolete managed receipt without changing it', async (t) => {
   const base = await mkdtemp(join(tmpdir(), 'maka-local-managed-migration-'));
   t.after(() => rm(base, { recursive: true, force: true }));
   const clientDataRoot = join(base, 'client');
@@ -622,21 +553,9 @@ test('migrates a released managed receipt before exposing it to lifecycle operat
   });
   t.after(() => service.close());
 
-  const target = await service.inspectManaged(async (managed) => managed);
-  const expected = {
-    schemaVersion: 2,
-    state: 'managed',
-    serviceId: rootId,
-    operator: {
-      kind: 'legacy_posix_executable',
-      executablePath: operatorPath,
-    },
-    rootPath,
-    rootId,
-    deploymentId: RECOVERY_DEPLOYMENT_ID,
-  };
-  assert.deepEqual(target, expected);
-  assert.deepEqual(JSON.parse(await readFile(lifecyclePath, 'utf8')), expected);
+  const before = await readFile(lifecyclePath, 'utf8');
+  await assert.rejects(service.inspectManaged(async (target) => target), /lifecycle is invalid/u);
+  assert.equal(await readFile(lifecyclePath, 'utf8'), before);
 });
 
 test('interrupted Local Host setup converges to its exact managed service', async (t) => {
diff --git a/apps/desktop/src/main/__tests__/runtime-host-managed-services.test.ts b/apps/desktop/src/main/__tests__/runtime-host-managed-services.test.ts
index 4a7be3d104..e815ee952b 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-managed-services.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-managed-services.test.ts
@@ -67,7 +67,7 @@ afterEach(async () => {
   );
 });
 
-test("migrates released WSL deployment bindings to the legacy operator route", async () => {
+test("rejects obsolete deployment bindings without rewriting them", async () => {
   const root = await mkdtemp(join(tmpdir(), "maka-managed-wsl-migration-"));
   roots.push(root);
   const path = join(root, "runtime-host-deployments.json");
@@ -94,28 +94,9 @@ test("migrates released WSL deployment bindings to the legacy operator route", a
     })}\n`,
   );
 
-  const document = await createDesktopRuntimeHostManagedServiceStore(root).read();
-  const binding = document.bindings[0];
-  assert.equal(binding?.profile.kind, "environment");
-  assert.deepEqual(binding?.profile.kind === "environment" ? binding.profile.operator : null, {
-    kind: "legacy_posix_executable",
-    executablePath: "/home/operator/.local/share/maka/operator",
-  });
-  const stored = await readFile(path, "utf8");
-  assert.match(stored, /"schemaVersion": 2/u);
-  assert.doesNotMatch(stored, /operatorPath/u);
-
-  const currentProfile = {
-    id: "ubuntu",
-    name: "Ubuntu",
-    kind: "environment" as const,
-    provider: { kind: "wsl" as const, distribution: "Ubuntu-24.04" },
-    rootId: "a".repeat(64),
-    operator,
-  };
-  const resolved = findDesktopRuntimeHostManagedServiceBinding(document, currentProfile);
-  assert.equal(resolved?.profile.kind, "environment");
-  assert.equal(resolved?.profile.kind === "environment" ? resolved.profile.operator : null, operator);
+  const before = await readFile(path, 'utf8');
+  await assert.rejects(createDesktopRuntimeHostManagedServiceStore(root).read(), /schema/u);
+  assert.equal(await readFile(path, 'utf8'), before);
 });
 
 test("keeps Desktop service bindings outside the shared profile catalog", async () => {
diff --git a/apps/desktop/src/main/__tests__/runtime-host-management.test.ts b/apps/desktop/src/main/__tests__/runtime-host-management.test.ts
index bc8bf1eac7..d4f1b1c1a3 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-management.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-management.test.ts
@@ -24,7 +24,6 @@ import {
   RuntimeHostOperationError,
 } from '@maka/runtime-host/client';
 import {
-  RUNTIME_HOST_OPERATOR_PEER_RELAY_DISCOVERY_CAPABILITY,
   RUNTIME_HOST_OPERATOR_PEER_WEBRTC_STUN_CAPABILITY,
   runtimeHostAccessCredentialFingerprint,
   type RuntimeHostServiceManagementFrame,
@@ -1082,13 +1081,7 @@ test('retries acknowledged deployment cleanup without repeating uninstall', asyn
       ...unusedDirectPeerProfileDependencies(),
       resolveManagedService: async () => {
         const binding = managedBinding(profile, service, 'cleanup_pending');
-        return {
-          ...binding,
-          deployment: {
-            id: binding.deployment.id,
-            rootPath: binding.deployment.rootPath,
-          },
-        };
+        return binding;
       },
       resolveManagedAccess: async () => undefined,
       markManagedServiceUninstalling: async () =>
@@ -1155,13 +1148,7 @@ test('rechecks uninstall intent before retrying the remote service', async () =>
           },
           'uninstalling',
         );
-        return {
-          ...binding,
-          deployment: {
-            id: binding.deployment.id,
-            rootPath: binding.deployment.rootPath,
-          },
-        };
+        return binding;
       },
       resolveManagedAccess: async () => undefined,
       markManagedServiceUninstalling: async (binding) => {
@@ -1322,7 +1309,7 @@ test('disables a newly enabled listener when its Desktop profile cannot be commi
       directPeerClientAvailable: true,
       runServiceManagement: async () => ({
         ...serviceResult('status'),
-        operatorCapabilities: [RUNTIME_HOST_OPERATOR_PEER_RELAY_DISCOVERY_CAPABILITY],
+        operatorCapabilities: [RUNTIME_HOST_OPERATOR_PEER_WEBRTC_STUN_CAPABILITY],
       }),
       runAccessManagement: async () => assert.fail('access management is not expected'),
       runPeerManagement: async (input) => {
diff --git a/apps/desktop/src/main/__tests__/runtime-host-native-capabilities.test.ts b/apps/desktop/src/main/__tests__/runtime-host-native-capabilities.test.ts
index 6bca312df3..1ba9898654 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-native-capabilities.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-native-capabilities.test.ts
@@ -33,6 +33,7 @@ import { z } from 'zod';
 import { buildClientSettingsTools } from '../client-settings-tools.js';
 import { buildManagedArtifactPreviewTools } from '../managed-artifact-preview-tools.js';
 import { browserOriginAdmission } from '../browser/browser-origin-admission.js';
+import type { SessionToolContext } from '@maka/runtime/tool-runtime';
 import { buildRiveWorkflowTool } from '../rive-workflow-tool.js';
 import { createDesktopNativeCapabilityProvider } from '../runtime-host-native-capabilities.js';
 
@@ -58,12 +59,12 @@ test('Artifact preview is discoverable and admitted without a pre-existing brows
         signal.throwIfAborted();
         invoked = true;
         return { url: 'http://127.0.0.1:12345/token/index.html', expiresAt: 123456, reachable: true, loaded: false };
-      }),
+      }).map(tool => ({ context: 'session' as const, tool })),
     }],
   }, { nativeSessionId: (sessionId) => `native:${sessionId}` });
   assert.doesNotThrow(() => decodeClientCapabilityReplaceInput({ registrationId: 'registration-1', offers: provider.offers() }));
   assert.ok(provider.offers().some((offer) => offer.offerId === 'desktop_artifact_preview' && offer.tools.some((tool) => tool.name === 'ArtifactPreview')));
-  const result = await call(provider, capabilityFrame({ offerId: 'desktop_artifact_preview', serverId: 'desktop_artifact_preview', toolName: 'ArtifactPreview', arguments: { artifactId: 'artifact-1' } }));
+  const result = await call(provider, capabilityFrame({ offerId: 'desktop_artifact_preview', serverId: 'desktop_artifact_preview', toolName: 'ArtifactPreview', arguments: { artifactId: 'artifact-1' }, source: { kind: 'background', grantId: 'preview-grant', sessionId: 'session-1' } }));
   assert.equal(invoked, true);
   assert.deepEqual(result.structuredContent, { url: 'http://127.0.0.1:12345/token/index.html', expiresAt: 123456, reachable: true, loaded: false });
 });
@@ -615,7 +616,7 @@ test('validates before admission and invokes the exact offered tool with Host co
   let received:
     | {
         args: unknown;
-        context: Pick<MakaToolContext, 'sessionId' | 'turnId' | 'cwd' | 'toolCallId'>;
+        context: Pick<SessionToolContext, 'sessionId' | 'cwd' | 'toolCallId'>;
       }
     | undefined;
   const provider = createDesktopNativeCapabilityProvider(
@@ -628,11 +629,11 @@ test('validates before admission and invokes the exact offered tool with Host co
             url: 'https://example.com/path',
           });
           invoked = true;
+          assert.equal('turnId' in context, false);
           received = {
             args,
             context: {
               sessionId: context.sessionId,
-              turnId: context.turnId,
               cwd: context.cwd,
               toolCallId: context.toolCallId,
             },
@@ -675,7 +676,6 @@ test('validates before admission and invokes the exact offered tool with Host co
     args: { url: 'https://example.com/path' },
     context: {
       sessionId: 'host-a:session-1',
-      turnId: 'turn-1',
       cwd: '/workspace',
       toolCallId: 'tool-call-1',
     },
@@ -688,6 +688,23 @@ test('validates before admission and invokes the exact offered tool with Host co
     'https://example.com/path',
     'https://example.com/path',
   ]);
+  for (const source of [
+    { kind: 'remote' as const, requestId: 'request-1', sessionId: 'session-1' },
+    { kind: 'background' as const, grantId: 'grant-1', sessionId: 'session-1' },
+  ]) {
+    admitted = false;
+    invoked = false;
+    await call(provider, capabilityFrame({ source, arguments: { url: 'https://example.com/path' } }), () => {
+      admitted = true;
+    });
+    assert.equal(invoked, true);
+    admitted = false;
+    await assert.rejects(call(provider, capabilityFrame({ source: { ...source, sessionId: null } }), () => {
+      admitted = true;
+    }), /requires a Session/u);
+    assert.equal(admitted, false);
+  }
+  await provider.close();
 });
 
 test('does not execute Browser work when its Origin changes while admission is pending', async () => {
@@ -750,7 +767,7 @@ test('watches Computer Use turns without widening Browser lifecycle', async () =
 
   await call(
     provider,
-    computerFrame({ sessionId: 'session-2', turnId: 'turn-2' }),
+    computerFrame({ source: { kind: 'agent', sessionId: 'session-2', turnId: 'turn-2' } }),
   );
   assert.deepEqual(usedSessions, ['session-1', 'session-2']);
   assert.deepEqual(computerUseTurns, [['session-2', 'turn-2']]);
@@ -798,7 +815,7 @@ test('projects Computer Use screenshots and releases all native resources for a
   await call(
     provider,
     capabilityFrame({
-      sessionId: 'browser-session',
+      source: { kind: 'agent', sessionId: 'browser-session', turnId: 'turn-1' },
       toolName: 'browser_snapshot',
       arguments: {},
     }),
@@ -807,7 +824,7 @@ test('projects Computer Use screenshots and releases all native resources for a
   assert.deepEqual(browserReleased, ['manual-session', 'browser-session']);
   assert.deepEqual(computerReleased, ['manual-session', 'browser-session']);
 
-  const completed = await call(provider, computerFrame({ sessionId: 'completed-session', arguments: {} }));
+  const completed = await call(provider, computerFrame({ source: { kind: 'agent', sessionId: 'completed-session', turnId: 'turn-1' }, arguments: {} }));
   assert.deepEqual(completed, {
     content: [
       { type: 'text', text: 'captured' },
@@ -818,7 +835,7 @@ test('projects Computer Use screenshots and releases all native resources for a
   assert.deepEqual(browserReleased, ['manual-session', 'browser-session', 'completed-session']);
   assert.deepEqual(computerReleased, ['manual-session', 'browser-session', 'completed-session']);
 
-  const inFlight = call(provider, computerFrame({ sessionId: 'active-session', arguments: { wait: true } }));
+  const inFlight = call(provider, computerFrame({ source: { kind: 'agent', sessionId: 'active-session', turnId: 'turn-1' }, arguments: { wait: true } }));
   await started;
   await provider.close();
   await assert.rejects(inFlight, /provider closed/u);
@@ -1285,11 +1302,11 @@ test('forwards Host cancellation to an admitted Desktop invocation', async () =>
   await assert.rejects(inFlight, /Host cancelled invocation/u);
 });
 
-function tool<P, R>(
+function tool<P, R, Context = SessionToolContext>(
   name: string,
   parameters: z.ZodType<P>,
-  impl: (args: P, context: MakaToolContext) => Promise<R>,
-): MakaTool<P, R> {
+  impl: (args: P, context: Context) => Promise<R>,
+): MakaTool<P, R, Context> {
   return {
     name,
     displayName: name,
@@ -1372,8 +1389,7 @@ function capabilityFrame(overrides: Partial<ClientCapabilityCallFrame> = {}): Cl
     serverId: 'desktop_browser',
     toolName: 'browser_navigate',
     arguments: { url: 'https://example.com' },
-    sessionId: 'session-1',
-    turnId: 'turn-1',
+    source: { kind: 'agent', sessionId: 'session-1', turnId: 'turn-1' },
     toolCallId: 'tool-call-1',
     cwd: '/workspace',
     ...overrides,
@@ -1420,7 +1436,7 @@ test('WorkHub groups receive their target epoch and join Desktop interaction tur
   });
   const frame = capabilityFrame({ offerId: 'desktop_workhub', serverId: 'desktop_workhub', toolName: 'control', arguments: {} });
   const result = await call(provider, frame);
-  assert.deepEqual(watched, [[frame.sessionId, frame.turnId]]);
-  assert.deepEqual(result.content, [{ type: 'text', text: frame.sessionId }], 'WorkHub authority sees the real Host Session id, not a native resource alias');
+  assert.deepEqual(watched, [['session-1', 'turn-1']]);
+  assert.deepEqual(result.content, [{ type: 'text', text: frame.source.sessionId }], 'WorkHub authority sees the real Host Session id, not a native resource alias');
   await provider.close();
 });
diff --git a/apps/desktop/src/main/__tests__/runtime-host-onboarding.test.ts b/apps/desktop/src/main/__tests__/runtime-host-onboarding.test.ts
index 42cd3d157d..40d30bf0cc 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-onboarding.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-onboarding.test.ts
@@ -19,22 +19,22 @@
 
 import assert from 'node:assert/strict';
 import { test } from 'node:test';
-import type { EnvironmentRuntimeHostProfile } from '@maka/runtime-host/client';
-import type { DesktopRuntimeHostProfileAddInput } from '../../preload/bridge-contract.js';
-import type {
-  DesktopRuntimeHostManagedSshServiceTarget,
-  DesktopRuntimeHostManagedWslServiceTarget,
-} from '../runtime-host-managed-services.js';
 import { createDesktopRuntimeHostOnboarding } from '../runtime-host-onboarding.js';
 
-const OPERATOR = {
-  kind: 'node' as const,
-  platform: 'posix' as const,
-  nodePath: '/usr/bin/node',
-  modulePath: '/home/operator/.local/share/maka/operator.mjs',
+import type { NativeSetupResult } from '../native-runtime-host-installer.js';
+
+const installed: NativeSetupResult = {
+  operator: { kind: 'native', platform: 'posix', executablePath: '/host/maka' },
+  receipt: {
+    deployment: { deploymentId: '00000000-0000-4000-8000-000000000001', configRevision: 1,
+      rootId: 'a'.repeat(64), rootPath: '/host/state', executable: '/host/maka',
+      sha256: 'b'.repeat(64), mode: 'on_demand', websocket: '127.0.0.1:0' },
+    host: { hostEpoch: 'epoch', pid: 123, port: 4567 },
+    pairing: { rootId: 'a'.repeat(64), credentialId: 'credential', credential: 'pairing-secret' },
+  },
 };
 
-test('native SSH and WSL onboarding save persistent operators without legacy service authority', async () => {
+test('SSH and WSL onboarding use the verified native package without exposing credentials', async () => {
   for (const kind of ['ssh', 'wsl'] as const) {
     const operator = { kind: 'native' as const, platform: 'posix' as const,
       executablePath: '/home/operator/.local/share/Maka/native-cli/package/bin/maka' };
@@ -57,7 +57,6 @@ test('native SSH and WSL onboarding save persistent operators without legacy ser
         ssh: kind === 'ssh' ? install : async () => assert.fail('wrong transport'),
         wsl: kind === 'wsl' ? install : async () => assert.fail('wrong transport'),
       },
-      resolveSetupPackage: () => assert.fail('native onboarding must not resolve a Node package'),
       profiles: {
         addAndEnableVerified: async (input) => { saved = input; return { profileId: input.profile.id }; },
         addEnvironmentAndEnable: async (input) => { saved = input; return { profileId: input.profile.id }; },
@@ -76,134 +75,6 @@ test('native SSH and WSL onboarding save persistent operators without legacy ser
   }
 });
 
-test('persists a verified on-demand SSH profile without endpoint or credential projection', async () => {
-  let setupInput: unknown;
-  let saved:
-    | (DesktopRuntimeHostProfileAddInput & {
-        readonly managedService?: DesktopRuntimeHostManagedSshServiceTarget;
-      })
-    | undefined;
-  const harness = createHarness({
-    profiles: {
-      addAndEnableVerified: async (input) => {
-        saved = input;
-        return { profileId: input.profile.id };
-      },
-    },
-    runSetup: async (input, onProgress) => {
-      setupInput = input;
-      onProgress({ phase: 'installing_service' });
-      return {
-        serviceId: 'b'.repeat(64),
-        deploymentId: '00000000-0000-4000-8000-000000000001',
-        rootPath: '/home/operator/.config/Maka/workspaces/default',
-        operator: OPERATOR,
-        rootId: 'a'.repeat(64),
-        endpoint: 'ws://127.0.0.1:7443/runtime-host',
-        credential: 'secret-access-token',
-      };
-    },
-  });
-
-  const result = await harness.invoke('runtime-host-onboarding:start', {
-    kind: 'ssh',
-    name: 'Lab',
-    destination: 'operator@example.com',
-    projectDirectoryRoots: [{ label: 'Work', path: '/srv/work' }],
-  });
-
-  assert.equal((result as { kind?: string }).kind, 'complete');
-  assert.equal(saved?.profile.name, 'Lab');
-  if (saved?.profile.kind !== 'remote') assert.fail('expected remote profile');
-  assert.deepEqual(saved.profile.transport, {
-    kind: 'ssh',
-    destination: 'operator@example.com',
-    activation: {
-      kind: 'ssh_operator',
-      operator: OPERATOR,
-    },
-  });
-  assert.deepEqual(saved?.managedService, {
-    deployment: {
-      id: 'b'.repeat(64),
-      rootPath: '/home/operator/.config/Maka/workspaces/default',
-      deploymentId: '00000000-0000-4000-8000-000000000001',
-    },
-    control: {
-      kind: 'ssh_operator',
-      operator: OPERATOR,
-    },
-  });
-  assert.equal(saved?.credential, 'secret-access-token');
-  assert.deepEqual(
-    (setupInput as { projectDirectoryRoots?: unknown }).projectDirectoryRoots,
-    [{ label: 'Work', path: '/srv/work' }],
-  );
-  assert.equal((setupInput as { lifecycle?: unknown }).lifecycle, 'on_demand');
-  assert.equal((setupInput as { remotePlatform?: unknown }).remotePlatform, 'posix');
-  assert.doesNotMatch(JSON.stringify(harness.events), /secret-access-token/u);
-  await harness.onboarding.close();
-  assert.equal(harness.handlers.size, 0);
-});
-
-test('onboards WSL as a credential-free environment profile', async () => {
-  let saved:
-    | {
-        readonly profile: EnvironmentRuntimeHostProfile;
-        readonly managedService?: DesktopRuntimeHostManagedWslServiceTarget;
-      }
-    | undefined;
-  const peerTargets: string[] = [];
-  const harness = createHarness({
-    profiles: {
-      addEnvironmentAndEnable: async (input) => {
-        saved = input;
-        return {
-          profileId: input.profile.id,
-        };
-      },
-    },
-    runWslSetup: async (_input, _onProgress, onComplete) => {
-      onComplete();
-      return {
-        serviceId: 'a'.repeat(64),
-        deploymentId: '00000000-0000-4000-8000-000000000001',
-        rootPath: '/home/operator/.config/Maka/workspaces/default',
-        rootId: 'a'.repeat(64),
-        operator: OPERATOR,
-      };
-    },
-    resolveSetupPackage: (peerTarget) => {
-      peerTargets.push(peerTarget);
-      return { kind: 'npm', specifier: 'maka-agent@0.2.0' };
-    },
-  });
-
-  const result = await harness.invoke('runtime-host-onboarding:start', {
-    kind: 'wsl',
-    distribution: 'Ubuntu-24.04',
-  });
-
-  assert.equal((result as { kind?: string }).kind, 'complete');
-  assert.deepEqual(saved?.profile, {
-    id: saved?.profile.id,
-    name: 'Ubuntu-24.04',
-    kind: 'environment',
-    provider: { kind: 'wsl', distribution: 'Ubuntu-24.04' },
-    rootId: 'a'.repeat(64),
-    operator: OPERATOR,
-  });
-  assert.deepEqual(saved?.managedService, {
-    deployment: {
-      id: 'a'.repeat(64),
-      rootPath: '/home/operator/.config/Maka/workspaces/default',
-      deploymentId: '00000000-0000-4000-8000-000000000001',
-    },
-  });
-  assert.deepEqual(peerTargets, ['none']);
-  await harness.onboarding.close();
-});
-
 test('projects WSL setup failures as recoverable onboarding state', async () => {
   const harness = createHarness({
     resolveWslTargetIdentity: async () => {
@@ -268,24 +139,8 @@ test('finishes Host pairing after the cancellable SSH phase has completed', asyn
   });
   let pairingStarted = false;
   let completeReceived = false;
-  let finishSetup!: (value: {
-    serviceId: string;
-    deploymentId: string;
-    rootPath: string;
-    operator: typeof OPERATOR;
-    rootId: string;
-    endpoint: string;
-    credential: string;
-  }) => void;
-  const setupDrain = new Promise<{
-    serviceId: string;
-    deploymentId: string;
-    rootPath: string;
-    operator: typeof OPERATOR;
-    rootId: string;
-    endpoint: string;
-    credential: string;
-  }>((resolve) => {
+  let finishSetup!: (value: NativeSetupResult) => void;
+  const setupDrain = new Promise<NativeSetupResult>((resolve) => {
     finishSetup = resolve;
   });
   const harness = createHarness({
@@ -295,10 +150,12 @@ test('finishes Host pairing after the cancellable SSH phase has completed', asyn
         return pairing;
       },
     },
-    runSetup: async (_input, _onProgress, onComplete) => {
-      onComplete();
-      completeReceived = true;
-      return setupDrain;
+    nativeSetup: {
+      ssh: async (_input, onCommit) => {
+        onCommit();
+        completeReceived = true;
+        return setupDrain;
+      },
     },
   });
 
@@ -309,15 +166,7 @@ test('finishes Host pairing after the cancellable SSH phase has completed', asyn
   while (!completeReceived) await Promise.resolve();
   assert.equal(await harness.invoke('runtime-host-onboarding:cancel'), false);
 
-  finishSetup({
-    serviceId: 'b'.repeat(64),
-    deploymentId: '00000000-0000-4000-8000-000000000001',
-    rootPath: '/home/operator/.config/Maka/workspaces/default',
-    operator: OPERATOR,
-    rootId: 'a'.repeat(64),
-    endpoint: 'ws://127.0.0.1:7443/runtime-host',
-    credential: 'candidate-token',
-  });
+  finishSetup(installed);
   while (!pairingStarted) await Promise.resolve();
 
   finishPairing({ profileId: 'office' });
@@ -328,9 +177,11 @@ test('finishes Host pairing after the cancellable SSH phase has completed', asyn
 test('resolves the setup package only when onboarding starts', async () => {
   let resolutions = 0;
   const harness = createHarness({
-    resolveSetupPackage: () => {
-      resolutions += 1;
-      throw new Error('Desktop does not declare an exact Runtime Host setup package');
+    nativeSetup: {
+      resolvePackage: async () => {
+        resolutions += 1;
+        throw new Error('Desktop does not declare an exact Runtime Host setup package');
+      },
     },
   });
 
@@ -355,14 +206,15 @@ test('resolves the setup package only when onboarding starts', async () => {
 });
 
 type OnboardingInput = Parameters<typeof createDesktopRuntimeHostOnboarding>[0];
-type HarnessOverrides = Partial<Omit<OnboardingInput, 'ipcMain' | 'send' | 'profiles'>> & {
+type HarnessOverrides = Partial<Omit<OnboardingInput, 'ipcMain' | 'send' | 'profiles' | 'nativeSetup'>> & {
   readonly profiles?: Partial<OnboardingInput['profiles']>;
+  readonly nativeSetup?: Partial<OnboardingInput['nativeSetup']>;
 };
 
 function createHarness(overrides: HarnessOverrides = {}) {
   const handlers = new Map<string, (...args: unknown[]) => unknown>();
   const events: unknown[] = [];
-  const { profiles, ...rest } = overrides;
+  const { profiles, nativeSetup, ...rest } = overrides;
   const onboarding = createDesktopRuntimeHostOnboarding({
     clientInstanceId: 'stable-client',
     profiles: {
@@ -370,12 +222,18 @@ function createHarness(overrides: HarnessOverrides = {}) {
       addAndEnableVerified: async () => assert.fail('profile must not be saved'),
       ...profiles,
     },
-    setupPackageMode: 'published',
     resolveSshTargetIdentity: async () => ({ platform: 'darwin', architecture: 'x64' }),
     resolveWslTargetIdentity: async () => ({ platform: 'linux', architecture: 'x64', glibcVersion: '2.39' }),
-    resolveSetupPackage: () => ({ kind: 'npm', specifier: 'maka-agent@0.2.0' }),
-    runSetup: async () => assert.fail('SSH must not start'),
-    runWslSetup: async () => assert.fail('WSL must not start'),
+    nativeSetup: {
+      resolvePackage: async () => ({
+        artifact: { target: 'darwin-x64', version: '0.2.0', directory: '/verified/package',
+          executable: '/verified/package/bin/maka', integrity: 'sha512-' + 'A'.repeat(86) + '==' },
+        receiptSha256: 'c'.repeat(64),
+      }),
+      ssh: async () => assert.fail('SSH must not start'),
+      wsl: async () => assert.fail('WSL must not start'),
+      ...nativeSetup,
+    },
     listWslDistributions: async () => [],
     ...rest,
     ipcMain: {
diff --git a/apps/desktop/src/main/__tests__/runtime-host-profile-service.test.ts b/apps/desktop/src/main/__tests__/runtime-host-profile-service.test.ts
index e4ea152485..30b3791849 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-profile-service.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-profile-service.test.ts
@@ -18,7 +18,7 @@
  */
 
 import assert from "node:assert/strict";
-import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
+import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
 import { tmpdir } from "node:os";
 import { join } from "node:path";
 import { afterEach, test } from "node:test";
@@ -26,7 +26,6 @@ import { deferred } from "@maka/core/test-only/async-primitives";
 import {
   createClientRuntimeHostCredentialStore,
   createClientRuntimeHostProfileCatalog,
-  createRuntimeHostProfileCredentialStore,
   encodeRuntimeHostOwnerConnectionCode,
   LOCAL_RUNTIME_HOST_PROFILE,
   RuntimeHostPermanentReconnectError,
@@ -110,49 +109,7 @@ afterEach(async () => {
   );
 });
 
-for (const hasDeployment of [false, true]) {
-  test(`recovers an abandoned deployment lock before loading Host choices (saved=${hasDeployment})`, async () => {
-    const root = await clientRoot();
-    const catalog = createClientRuntimeHostProfileCatalog(root);
-    const managedServices = createDesktopRuntimeHostManagedServiceStore(root);
-    if (hasDeployment) {
-      await catalog.create(MANAGED_PROFILE, "token");
-      await managedServices.save(MANAGED_PROFILE, MANAGED_SERVICE);
-    }
-    const before = await managedServices.read();
-    await mkdir(join(root, "runtime-host-deployments.json.lock"));
-
-    const startup = await resolveDesktopRuntimeHostStartup(root, { catalog });
-    const service = createDesktopRuntimeHostProfileService({
-      clientDataRoot: root,
-      startup,
-      catalog,
-      managedServices,
-      states: () => [ready({ profile: LOCAL_RUNTIME_HOST_PROFILE })],
-      enable: async () => undefined,
-      disable: async () => undefined,
-      setDefault: () => undefined,
-      finalizePairing: async () => undefined,
-    });
-    const snapshot = await service.getSnapshot();
-    assert.equal(snapshot.defaultProfileId, LOCAL_RUNTIME_HOST_PROFILE.id);
-    assert.equal(snapshot.entries[0]?.readiness, "ready");
-    assert.equal(snapshot.entries.length, hasDeployment ? 2 : 1);
-    assert.deepEqual(await managedServices.read(), before);
-    if (hasDeployment) assert.equal(snapshot.entries[1]?.managedService, true);
-  });
-}
-
-test("does not discard unexpected contents in an abandoned deployment lock", async () => {
-  const root = await clientRoot();
-  const lock = join(root, "runtime-host-deployments.json.lock");
-  await mkdir(lock);
-  await writeFile(join(lock, "unexpected"), "retain me");
-  await assert.rejects(resolveDesktopRuntimeHostStartup(root), { code: "ENOTEMPTY" });
-  assert.equal(await readFile(join(lock, "unexpected"), "utf8"), "retain me");
-});
-
-test("migrates the former selected Host into enabled and default preferences", async () => {
+test("preserves unsupported preferences and reports their read failure", async () => {
   const root = await clientRoot();
   await createClientRuntimeHostProfileCatalog(root).create(PROFILE, "token");
   await writeFile(
@@ -162,56 +119,9 @@ test("migrates the former selected Host into enabled and default preferences", a
 
   const startup = await resolveDesktopRuntimeHostStartup(root);
 
-  assert.equal(startup.preferences.defaultProfileId, PROFILE.id);
-  assert.deepEqual(startup.preferences.enabledRemoteProfileIds, [PROFILE.id]);
-  assert.equal(startup.remotes[0]?.profile.id, PROFILE.id);
-  assert.equal(
-    JSON.parse(await readFile(join(root, "runtime-host-profile-selection.json"), "utf8"))
-      .schemaVersion,
-    2,
-  );
-});
-
-test('removes obsolete experimental Guest profiles and pairing intents at startup', async () => {
-  const root = await clientRoot();
-  const credentials = createClientRuntimeHostCredentialStore(root);
-  const catalog = createClientRuntimeHostProfileCatalog(root, credentials);
-  const guest = { ...PROFILE, id: 'shared-obsolete', access: 'session_guest' as const };
-  await createRuntimeHostProfileCredentialStore(credentials).set(guest, {
-    credential: 'guest-token',
-    profileIncarnationId: 'guest-incarnation',
-  });
-  await writeFile(
-    join(root, 'runtime-host-profiles.json'),
-    `${JSON.stringify({ schemaVersion: 3, profiles: [guest] })}\n`,
-  );
-  await writeFile(
-    join(root, 'runtime-host-profile-selection.json'),
-    `${JSON.stringify({
-      schemaVersion: 2,
-      defaultProfileId: guest.id,
-      enabledRemoteProfileIds: [guest.id],
-    })}\n`,
-  );
-  await writeDesktopRuntimeHostPairingIntents(credentials, [
-    createDesktopRuntimeHostPairingIntent({
-      target: { profile: guest, credential: 'guest-token' },
-      wasEnabled: true,
-    }),
-  ]);
-
-  const startup = await resolveDesktopRuntimeHostStartup(root, {
-    catalog,
-    credentialStore: credentials,
-  });
-
-  assert.deepEqual((await catalog.read()).profiles, []);
-  assert.deepEqual(startup.pairingIntents, []);
-  assert.deepEqual(startup.preferences, {
-    schemaVersion: 2,
-    defaultProfileId: 'local',
-    enabledRemoteProfileIds: [],
-  });
+  assert.ok(startup.preferencesReadFailure);
+  assert.equal(startup.preferences.defaultProfileId, 'local');
+  assert.equal(JSON.parse(await readFile(join(root, 'runtime-host-profile-selection.json'), 'utf8')).schemaVersion, 1);
 });
 
 test("starts Local and preserves remote preferences when the profile catalog is unreadable", async () => {
@@ -343,7 +253,8 @@ test("reuses the existing WSL profile when the same managed Host is added again"
     provider: { kind: "wsl" as const, distribution: "Ubuntu-24.04" },
     rootId: ROOT_ID,
     operator: {
-      kind: "legacy_posix_executable" as const,
+      kind: "native" as const,
+      platform: "posix" as const,
       executablePath: "/home/operator/.local/share/Maka/runtime-host-services/operator",
     },
   };
@@ -1124,6 +1035,7 @@ test("does not rotate a managed credential after its profile target changes", as
     deployment: {
       id: "e".repeat(64),
       rootPath: "/srv/other-maka",
+      deploymentId: "22222222-2222-4222-8222-222222222222",
     },
   };
   await catalog.remove(MANAGED_PROFILE.id);
@@ -1357,8 +1269,6 @@ test('discarding a committed rotation unlocks the restored local profile', async
   );
 
   restoringOldCredential = true;
-  const abandonedLock = join(root, 'runtime-host-profiles.json.lock');
-  await mkdir(abandonedLock);
   const discarded = await service.discardPairing(MANAGED_PROFILE.id);
   assert.equal(discarded.pairingRecoveryPending, undefined);
   assert.equal((await catalog.resolve(MANAGED_PROFILE.id)).credential, 'old-token');
@@ -1575,7 +1485,7 @@ test("keeps existing Hosts available while corrupt pairing recovery awaits resol
   assert.equal((await service.setEnabled(PROFILE.id, false)).entries[1]?.enabled, false);
 });
 
-test("migrates an interrupted SSH pairing from the released operator path", async () => {
+test("rejects an interrupted pairing with an obsolete operator path", async () => {
   const root = await clientRoot();
   const credentialStore = createClientRuntimeHostCredentialStore(root);
   await credentialStore.setSecret(
@@ -1607,18 +1517,8 @@ test("migrates an interrupted SSH pairing from the released operator path", asyn
 
   const startup = await resolveDesktopRuntimeHostStartup(root, { credentialStore });
 
-  assert.equal(startup.pairingReadFailure, undefined);
-  assert.deepEqual(startup.pairingIntents[0]?.target.profile.transport, {
-    kind: "ssh",
-    destination: "operator@example.com",
-    activation: {
-      kind: "ssh_operator",
-      operator: {
-        kind: "legacy_posix_executable",
-        executablePath: "/home/operator/.local/share/maka/operator",
-      },
-    },
-  });
+  assert.ok(startup.pairingReadFailure);
+  assert.deepEqual(startup.pairingIntents, []);
 });
 
 test("retries pairing recovery before discarding it", async () => {
diff --git a/apps/desktop/src/main/__tests__/runtime-host-session-execution-ipc-main.test.ts b/apps/desktop/src/main/__tests__/runtime-host-session-execution-ipc-main.test.ts
index 1e5ac98107..57aec57911 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-session-execution-ipc-main.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-session-execution-ipc-main.test.ts
@@ -783,7 +783,7 @@ test("sends canonical content and uploads owned Attachment bytes through the Hos
       return {
         disposition: "turn_started",
         turnId: "turn-1",
-        skillInvocation: { loaded: [], failed: [], receipts: [] },
+        preparation: [],
       };
     },
   });
@@ -848,7 +848,7 @@ test("sends canonical content and uploads owned Attachment bytes through the Hos
         label: "notes.txt",
       },
     ],
-    skillInvocation: { loaded: [], failed: [], receipts: [] },
+    preparation: [],
   });
   assert.deepEqual(changes, [
     { reason: "status-change", sessionId: "session-1", turnId: "turn-1" },
@@ -892,7 +892,7 @@ test("uploads a selected workspace file as a Host-owned Session Artifact", async
           return {
             disposition: "turn_started",
             turnId: "turn-1",
-            skillInvocation: { loaded: [], failed: [], receipts: [] },
+            preparation: [],
           };
         },
       }),
@@ -936,11 +936,11 @@ test("forwards explicit Skill invocation to the Host-owned Turn admission", asyn
           return {
             disposition: "turn_started",
             turnId: "turn-skill",
-            skillInvocation: {
+            preparation: fixturePreparation({
               loaded: [{ id: "review", name: "Review" }],
               failed: [],
               receipts: [],
-            },
+            }),
           };
         },
       }),
@@ -959,7 +959,7 @@ test("forwards explicit Skill invocation to the Host-owned Turn admission", asyn
     type: "send",
     text: "",
     displayText: "/skill:review",
-    skillIds: ["review"],
+    inputSelections: { "maka.skills": ["review"] },
   });
 
   assert.deepEqual(starts, [
@@ -968,7 +968,7 @@ test("forwards explicit Skill invocation to the Host-owned Turn admission", asyn
       messageId: "turn-skill",
       placement: "current_turn",
       content: { text: "", displayText: "/skill:review", inlineReferences: [] },
-      skillIds: ["review"],
+      inputSelections: { "maka.skills": ["review"] },
     },
   ]);
   assert.deepEqual(result, {
@@ -976,11 +976,11 @@ test("forwards explicit Skill invocation to the Host-owned Turn admission", asyn
     turnId: "turn-skill",
     attachments: [],
     inlineReferences: [],
-    skillInvocation: {
+    preparation: fixturePreparation({
       loaded: [{ id: "review", name: "Review" }],
       failed: [],
       receipts: [],
-    },
+    }),
   });
 });
 
@@ -996,7 +996,7 @@ test("submits an ordinary composer message once under its stable message identit
           return {
             disposition: "turn_started",
             turnId: "host-turn",
-            skillInvocation: { loaded: [], failed: [], receipts: [] },
+            preparation: [],
           };
         },
       }),
@@ -1033,7 +1033,7 @@ test("submits an ordinary composer message once under its stable message identit
     turnId: "host-turn",
     attachments: [],
     inlineReferences: [],
-    skillInvocation: { loaded: [], failed: [], receipts: [] },
+    preparation: [],
   });
 });
 
@@ -1329,11 +1329,12 @@ test('submits a slash Skill message and reports the Host Skill outcome', async (
           submits.push(input);
           return {
             disposition: 'blocked',
-            skillInvocation: {
+            message: 'Input unavailable',
+            preparation: fixturePreparation({
               loaded: [],
               failed: [{ request: 'missing', reason: 'not_found' }],
               receipts: [],
-            },
+            }),
           };
         },
       }),
@@ -1355,12 +1356,13 @@ test('submits a slash Skill message and reports the Host Skill outcome', async (
   }]);
   assert.deepEqual(result, {
     ok: false,
-    reason: 'skill_invocation_failed',
-    skillInvocation: {
+    reason: 'input_preparation_failed',
+    message: 'Input unavailable',
+    preparation: fixturePreparation({
       loaded: [],
       failed: [{ request: 'missing', reason: 'not_found' }],
       receipts: [],
-    },
+    }),
   });
 });
 
@@ -1379,7 +1381,7 @@ test("queues a mid-turn send as steering when the Host reports the session busy"
         getSession: async () => session(),
         submitMessage: async (input) => {
           submits.push(input);
-          return { disposition: "steering", queueRevision: 1, skillInvocation };
+          return { disposition: "steering", queueRevision: 1, preparation: fixturePreparation(skillInvocation) };
         },
       }),
       observer: unusedObserver(),
@@ -1414,7 +1416,7 @@ test("queues a mid-turn send as steering when the Host reports the session busy"
     turnId: "turn-1",
     attachments: [],
     inlineReferences: [],
-    skillInvocation,
+    preparation: fixturePreparation(skillInvocation),
   });
   assert.deepEqual(changes, [
     { reason: "status-change", sessionId: "session-1" },
@@ -1458,7 +1460,7 @@ test("resolves a twice-interrupted send as an unknown outcome", async () => {
       ok: false,
       reason: "outcome_unknown",
       messageId: "turn-1",
-      skillInvocation: { loaded: [], failed: [], receipts: [] },
+      preparation: [],
     },
   );
   assert.equal(submits, 2);
@@ -1501,7 +1503,7 @@ test("retries a dispatched send with its original message identity", async () =>
           return {
             disposition: "steering",
             queueRevision: 1,
-            skillInvocation: { loaded: [], failed: [], receipts: [] },
+            preparation: [],
           };
         },
       }),
@@ -1538,7 +1540,7 @@ test("retries a dispatched send with its original message identity", async () =>
     messageId: "turn-1",
     attachments: [],
     inlineReferences: [],
-    skillInvocation: { loaded: [], failed: [], receipts: [] },
+    preparation: [],
   });
   assert.deepEqual(
     await ipc.invoke("sessions:send", "session-1", {
@@ -1550,7 +1552,7 @@ test("retries a dispatched send with its original message identity", async () =>
       ok: false,
       reason: "outcome_unknown",
       messageId: "turn-unknown",
-      skillInvocation: { loaded: [], failed: [], receipts: [] },
+      preparation: [],
     },
   );
   assert.deepEqual(
@@ -1563,7 +1565,7 @@ test("retries a dispatched send with its original message identity", async () =>
       ok: false,
       reason: "outcome_unknown",
       messageId: "turn-unknown",
-      skillInvocation: { loaded: [], failed: [], receipts: [] },
+      preparation: [],
     },
   );
 });
@@ -1581,7 +1583,7 @@ test("answers a send with the Turn the Host started for it", async () => {
           return {
             disposition: "turn_started",
             turnId: "turn-9",
-            skillInvocation: { loaded: [], failed: [], receipts: [] },
+            preparation: [],
           };
         },
       }),
@@ -1608,7 +1610,7 @@ test("answers a send with the Turn the Host started for it", async () => {
     turnId: "turn-9",
     attachments: [],
     inlineReferences: [],
-    skillInvocation: { loaded: [], failed: [], receipts: [] },
+    preparation: [],
   });
   assert.deepEqual(submits, [{
     sessionId: "session-1",
@@ -1650,7 +1652,7 @@ test("propagates a busy explicit Skill send instead of degrading it to steering"
       turnId: "turn-1",
       text: "",
       displayText: "/skill:review",
-      skillIds: ["review"],
+      inputSelections: { "maka.skills": ["review"] },
     }),
     (error: unknown) =>
       error instanceof RuntimeHostOperationError && error.code === "session_busy",
@@ -1665,7 +1667,7 @@ test("propagates a busy explicit Skill send instead of degrading it to steering"
         displayText: "/skill:review",
         inlineReferences: [],
       },
-      skillIds: ["review"],
+      inputSelections: { "maka.skills": ["review"] },
     },
   ]);
 });
@@ -1682,7 +1684,7 @@ test("lets the Host queue a textual Skill token as steering", async () => {
           return {
             disposition: "steering",
             queueRevision: 1,
-            skillInvocation: { loaded: [], failed: [], receipts: [] },
+            preparation: [],
           };
         },
       }),
@@ -1707,7 +1709,7 @@ test("lets the Host queue a textual Skill token as steering", async () => {
       turnId: "turn-1",
       attachments: [],
       inlineReferences: [],
-      skillInvocation: { loaded: [], failed: [], receipts: [] },
+      preparation: [],
     },
   );
   assert.equal(submits.length, 1);
@@ -1721,11 +1723,12 @@ test("reports a Host-blocked Skill send as a Skill failure", async () => {
         getSession: async () => session(),
         submitMessage: async () => ({
           disposition: "blocked",
-          skillInvocation: {
+          message: 'Input unavailable',
+          preparation: fixturePreparation({
             loaded: [],
             failed: [{ request: "missing", reason: "not_found" }],
             receipts: [],
-          },
+          }),
         }),
       }),
       newId: () => "id-1",
@@ -1741,12 +1744,13 @@ test("reports a Host-blocked Skill send as a Skill failure", async () => {
     }),
     {
       ok: false,
-      reason: "skill_invocation_failed",
-      skillInvocation: {
+      reason: "input_preparation_failed",
+      message: 'Input unavailable',
+      preparation: fixturePreparation({
         loaded: [],
         failed: [{ request: "missing", reason: "not_found" }],
         receipts: [],
-      },
+      }),
     },
   );
 });
@@ -1766,7 +1770,7 @@ test("queues explicit Desktop follow-ups", async () => {
         getSession: async () => session(),
         submitMessage: async (input) => {
           submits.push(input);
-          return { disposition: "followup", queueRevision: 4, skillInvocation };
+          return { disposition: "followup", queueRevision: 4, preparation: fixturePreparation(skillInvocation) };
         },
       }),
       observer: unusedObserver(),
@@ -1816,7 +1820,7 @@ test("queues explicit Desktop follow-ups", async () => {
         },
       ],
       inlineReferences: [],
-      skillInvocation,
+      preparation: fixturePreparation(skillInvocation),
     },
   );
   assert.deepEqual(submits, [
@@ -1988,7 +1992,7 @@ test("binds steer and stop to Host-owned queue and active Turn identities", asyn
       return {
         disposition: "steering",
         queueRevision: 2,
-        skillInvocation: { loaded: [], failed: [], receipts: [] },
+        preparation: [],
       };
     },
     interruptTurn: async (input) => {
@@ -2072,7 +2076,7 @@ test("binds steer and stop to Host-owned queue and active Turn identities", asyn
       disposition: "steering",
       attachments: [],
       inlineReferences: [],
-      skillInvocation: { loaded: [], failed: [], receipts: [] },
+      preparation: [],
     },
   );
   assert.deepEqual(
@@ -2448,7 +2452,7 @@ test('steers WorkHub through Host admission even though the ordinary Session cat
       getSession: async () => null,
       submitMessage: async (input) => {
         submits.push(input);
-        return { disposition: 'steering', queueRevision: 1, skillInvocation: { loaded: [], failed: [], receipts: [] } };
+        return { disposition: 'steering', queueRevision: 1, preparation: [] };
       },
     }),
     observer: unusedObserver(), attachmentApprovals: createAttachmentApprovalRegistry(),
@@ -2460,3 +2464,7 @@ test('steers WorkHub through Host admission even though the ordinary Session cat
   assert.deepEqual(submits, [{ sessionId: WORKHUB_COORDINATION_SESSION_ID, messageId: 'workhub-steering', placement: 'current_turn', content: { text: 'Change direction immediately', inlineReferences: [] } }]);
   assert.equal((result as { disposition: string }).disposition, 'steering');
 });
+
+function fixturePreparation(receipt: unknown): import('@maka/runtime-host/protocol').InputReceipt[] {
+  return [{ source: { kind: 'input', name: 'maka.skills', packageId: 'maka.skills', entryId: 'skills', activation: "fixture", revision: "1" }, receipt: JSON.parse(JSON.stringify(receipt)) }];
+}
diff --git a/apps/desktop/src/main/__tests__/runtime-host-ssh-terminal.test.ts b/apps/desktop/src/main/__tests__/runtime-host-ssh-terminal.test.ts
index 482fb51d86..8914f86bc1 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-ssh-terminal.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-ssh-terminal.test.ts
@@ -18,10 +18,6 @@
  */
 
 import assert from 'node:assert/strict';
-import { createHash } from 'node:crypto';
-import { mkdtemp, rm, writeFile } from 'node:fs/promises';
-import { tmpdir } from 'node:os';
-import { join } from 'node:path';
 import { test } from 'node:test';
 import type { IPty } from 'node-pty';
 import { type RuntimeHostSshProcessFactory } from '@maka/runtime-host/client';
@@ -30,16 +26,15 @@ import {
   encodeRuntimeHostAccessManagementFrame,
   encodeRuntimeHostPeerManagementFrame,
   encodeRuntimeHostServiceManagementFrame,
-  encodeRuntimeHostSetupFrame,
   encodeRuntimeHostPeerMeshManagementFrame,
   runtimeHostAccessCredentialFingerprint,
   RUNTIME_HOST_OPERATOR_PEER_MANAGEMENT_CAPABILITY,
-  RUNTIME_HOST_SETUP_FRAME_PREFIX,
 } from '@maka/runtime-host/operator';
 import {
   createDesktopRuntimeHostSshTerminal,
   runtimeHostPeerTargetFromPlatform,
 } from '../runtime-host-ssh-terminal.js';
+import { NATIVE_ARTIFACT_PREFIX, NATIVE_SETUP_PREFIX } from '../native-runtime-host-setup.js';
 import { waitFor as pollFor } from '@maka/core/test-only/async-primitives';
 
 const OPERATOR = {
@@ -196,215 +191,87 @@ test('does not reopen a cancelled SSH prompt for late process output', async ()
   await harness.terminal.close();
 });
 
-test('keeps setup credentials out of the interactive terminal projection', async () => {
-  const harness = createHarness('pending');
-  const controller = new AbortController();
-  const progress: string[] = [];
-  const setup = harness.terminal.runSetup(
-    {
-      destination: 'operator@example.com',
-      setupPackage: { kind: 'npm', specifier: 'maka-agent@1.2.3+desktop.1' },
-      remotePlatform: 'posix',
-      principalId: 'desktop:stable-client',
-      signal: controller.signal,
-    },
-    (frame) => progress.push(frame.phase),
-  );
-  await waitFor(() => harness.pty.hasDataListener());
-  harness.pty.emitData('Password: ');
-  const progressFrame = encodeRuntimeHostSetupFrame({
-    schemaVersion: 1,
-    sequence: 0,
-    kind: 'progress',
-    phase: 'installing_service',
-  });
-  harness.pty.emitData(progressFrame.slice(0, 12));
-  harness.pty.emitData(progressFrame.slice(12));
-  const completeFrame = encodeRuntimeHostSetupFrame({
-    schemaVersion: 1,
-    sequence: 1,
-    kind: 'complete',
-    version: '0.1.0-beta.1',
-    serviceId: 'b'.repeat(64),
-    deploymentId: '00000000-0000-4000-8000-000000000001',
-    operator: OPERATOR,
-    rootPath: '/home/operator/.config/Maka/workspaces/default',
-    rootId: 'a'.repeat(64),
-    endpoint: 'ws://127.0.0.1:7443/runtime-host',
-    credentialId: 'credential-1',
-    credential: 'secret-access-token',
-  });
-  harness.pty.emitData(completeFrame);
-  controller.abort();
-  await Promise.resolve();
-  assert.deepEqual(harness.pty.killSignals, []);
-  harness.pty.exit(0);
-
-  const result = await setup;
-  assert.equal(result.credential, 'secret-access-token');
-  assert.deepEqual(progress, ['installing_service']);
-  assert.doesNotMatch(JSON.stringify(harness.events), /secret-access-token|MAKA_RUNTIME/u);
-  assert.match(JSON.stringify(harness.events), /Password/u);
-  const remoteCommand = harness.launchArgs.at(-1)?.at(-1) ?? '';
-  assert.match(remoteCommand, /mktemp -d/u);
-  assert.match(remoteCommand, /--prefix/u);
-  assert.match(remoteCommand, /trap.*HUP.*trap.*INT.*trap.*TERM/u);
-  assert.doesNotMatch(remoteCommand, /--update-existing/u);
-  await harness.terminal.close();
-});
-
-test('runs Windows setup through one encoded PowerShell command', async () => {
-  const harness = createHarness('pending');
-  const setup = harness.terminal.runSetup(
-    {
-      destination: 'operator@example.com',
-      setupPackage: { kind: 'npm', specifier: 'maka-agent@1.2.3' },
-      remotePlatform: 'win32',
-      principalId: 'desktop:stable-client',
-      lifecycle: 'on_demand',
-    },
-    () => undefined,
-  );
-  await waitFor(() => harness.pty.hasDataListener());
-  const remoteCommand = harness.launchArgs.at(-1)?.at(-1) ?? '';
-  assert.match(remoteCommand, /^powershell\.exe .* -EncodedCommand [A-Za-z0-9+/=]+$/u);
-  assert.doesNotMatch(remoteCommand, /\/bin\/sh|maka-agent/u);
-  const encoded = remoteCommand.split(' ').at(-1);
-  assert.ok(encoded);
-  const script = Buffer.from(encoded, 'base64').toString('utf16le');
-  assert.match(script, /npx\.cmd/u);
-  assert.match(script, /Remove-Item/u);
-
-  harness.pty.emitData(encodeRuntimeHostSetupFrame({
-    schemaVersion: 1,
-    sequence: 0,
-    kind: 'complete',
-    version: '1.2.3',
-    serviceId: 'b'.repeat(64),
-    deploymentId: '00000000-0000-4000-8000-000000000001',
-    operator: WINDOWS_OPERATOR,
-    rootPath: 'C:\\Users\\operator\\AppData\\Local\\Maka\\workspaces\\default',
-    rootId: 'a'.repeat(64),
-    endpoint: 'ws://127.0.0.1:7443/runtime-host',
-    credentialId: 'credential-1',
-    credential: 'secret-access-token',
-  }));
-  harness.pty.exit(0);
-  assert.deepEqual((await setup).operator, WINDOWS_OPERATOR);
-  await harness.terminal.close();
-});
-
-test('discards an oversized reserved setup line instead of projecting its tail', async () => {
-  const harness = createHarness('pending');
-  const setup = harness.terminal.runSetup(
-    {
-      destination: 'operator@example.com',
-      setupPackage: { kind: 'npm', specifier: 'maka-agent@1.2.3' },
-      remotePlatform: 'posix',
-      principalId: 'desktop:stable-client',
-    },
-    () => undefined,
-  );
-  await waitFor(() => harness.pty.hasDataListener());
-  harness.pty.emitData(`${RUNTIME_HOST_SETUP_FRAME_PREFIX}${'x'.repeat(21 * 1024)}`);
-  harness.pty.emitData('"credential":"must-not-reach-renderer"}\nvisible output\n');
-  harness.pty.exit(1);
-
-  await assert.rejects(setup, /oversized result/u);
-  assert.doesNotMatch(
-    JSON.stringify([harness.events, await harness.getSnapshot()]),
-    /must-not-reach-renderer/u,
-  );
-  assert.match(JSON.stringify(harness.events), /visible output/u);
-  await harness.terminal.close();
-});
-
-test('keeps a completed setup process owned until it exits', async () => {
-  const harness = createHarness('pending');
-  const setup = harness.terminal.runSetup(
-    {
-      destination: 'operator@example.com',
-      setupPackage: { kind: 'npm', specifier: 'maka-agent@1.2.3' },
-      remotePlatform: 'posix',
-      principalId: 'desktop:stable-client',
-    },
-    () => undefined,
-  );
-  await waitFor(() => harness.pty.hasDataListener());
-  harness.pty.emitData(encodeRuntimeHostSetupFrame({
-    schemaVersion: 1,
-    sequence: 0,
-    kind: 'complete',
-    version: '1.2.3',
-    serviceId: 'b'.repeat(64),
-    deploymentId: '00000000-0000-4000-8000-000000000001',
-    operator: OPERATOR,
-    rootPath: '/home/operator/.config/Maka/workspaces/default',
-    rootId: 'a'.repeat(64),
-    endpoint: 'ws://127.0.0.1:7443/runtime-host',
-    credentialId: 'credential-1',
-    credential: 'secret-access-token',
-  }));
-
-  await harness.terminal.close();
-
-  assert.deepEqual(harness.pty.killSignals, ['SIGTERM']);
-  assert.equal((await setup).credentialId, 'credential-1');
-});
-
-test('force-stops a cancelled setup when SSH ignores graceful termination', async () => {
-  const harness = createHarness('pending');
-  harness.pty.deferKill = true;
-  harness.pty.exitOnForceKill = true;
-  const controller = new AbortController();
-  const setup = harness.terminal.runSetup(
-    {
-      destination: 'operator@example.com',
-      setupPackage: { kind: 'npm', specifier: 'maka-agent@1.2.3' },
-      remotePlatform: 'posix',
-      principalId: 'desktop:stable-client',
-      signal: controller.signal,
-    },
-    () => undefined,
-  );
-  await waitFor(() => harness.pty.hasDataListener());
-  harness.pty.emitData('Password: ');
-
-  controller.abort();
-
-  await assert.rejects(setup, /aborted/u);
-  assert.deepEqual(harness.pty.killSignals, ['SIGTERM', 'SIGKILL']);
-  assert.deepEqual(harness.terminatedProcesses, [
-    { pid: 42, signal: 'SIGTERM' },
-    { pid: 42, signal: 'SIGKILL' },
-  ]);
-  assert.deepEqual(harness.eventKinds(), ['opened', 'data', 'dismissed']);
-  assert.deepEqual(await harness.getSnapshot(), { kind: 'idle', revision: 3 });
-  await harness.terminal.close();
-});
-
-test('does not signal a reused process identity when cancellation races SSH exit', async () => {
-  const harness = createHarness('pending');
-  const controller = new AbortController();
-  const setup = harness.terminal.runSetup(
-    {
-      destination: 'operator@example.com',
-      setupPackage: { kind: 'npm', specifier: 'maka-agent@1.2.3' },
-      remotePlatform: 'posix',
-      principalId: 'desktop:stable-client',
-      signal: controller.signal,
-    },
-    () => undefined,
-  );
-  await waitFor(() => harness.pty.hasDataListener());
-
-  controller.abort();
-  harness.pty.exit(0);
-
-  await assert.rejects(setup, /aborted/u);
-  await Promise.resolve();
-  assert.deepEqual(harness.pty.killSignals, []);
-  await harness.terminal.close();
+test('native SSH setup verifies the target package and keeps its receipt out of terminal output', async () => {
+  for (const windows of [false, true]) {
+    const directory = windows ? 'C:\\Maka\\package' : '/opt/maka/package';
+    const executable = windows ? `${directory}\\bin\\maka.exe` : `${directory}/bin/maka`;
+    const artifact = {
+      target: windows ? 'win32-x64' as const : 'linux-x64-gnu' as const,
+      version: '0.2.0', directory, executable,
+      ...(windows ? { serviceExecutable: `${directory}\\bin\\maka-service.exe` } : {}),
+      integrity: `sha512-${'A'.repeat(86)}==`,
+    };
+    const receipt = {
+      deployment: {
+        deploymentId: '00000000-0000-4000-8000-000000000001', configRevision: 1,
+        rootId: 'a'.repeat(64), rootPath: windows ? 'C:\\Maka\\state' : '/opt/maka/state',
+        executable, sha256: 'b'.repeat(64), mode: 'on_demand', websocket: '127.0.0.1:0',
+      },
+      host: { hostEpoch: 'host-epoch', pid: 123, port: 4567 },
+      pairing: { rootId: 'a'.repeat(64), credentialId: 'credential', credential: 'setup-private-token' },
+    };
+    const events: unknown[] = [];
+    const commands: string[] = [];
+    let committed = false;
+    let launch = 0;
+    const terminal = createDesktopRuntimeHostSshTerminal({
+      ipcMain: { handle: () => undefined, removeHandler: () => undefined },
+      send: (_channel, event) => events.push(event),
+      revealDelayMs: 0,
+      spawnPty: ((file: string, args: string[]) => {
+        const pty = new FakePty();
+        const step = launch++;
+        const encoded = args.at(-1) ?? '';
+        const script = windows && file === 'ssh'
+          ? Buffer.from(encoded.split(' ').at(-1)!, 'base64').toString('utf16le')
+          : encoded;
+        const payload = windows ? script.match(/FromBase64String\('([^']+)'\)/u)?.[1] : undefined;
+        const command = payload ? Buffer.from(payload, 'base64').toString('utf8') : script;
+        commands.push(command);
+        setImmediate(() => {
+          let frame: string;
+          if (step === 0) {
+            const name = command.match(/maka-native-[a-f0-9]{32}/u)?.[0];
+            assert.ok(name);
+            frame = `__MAKA_NATIVE_HOST_STAGE__${windows ? 'C:\\Temp\\' : '/tmp/'}${name}\n`;
+          } else if (step === 1) {
+            assert.equal(file, 'scp');
+            pty.exit(0);
+            return;
+          } else if (step === 2) {
+            assert.match(command, /fetch/u);
+            assert.ok(command.includes('c'.repeat(64)));
+            frame = `${NATIVE_ARTIFACT_PREFIX}${JSON.stringify(artifact)}\n`;
+          } else if (step === 3) {
+            frame = '__MAKA_NATIVE_HOST_CLEAN__ok\n';
+          } else {
+            assert.equal(step, 4);
+            assert.equal(committed, true);
+            assert.match(command, /desktop:client/u);
+            assert.doesNotMatch(command, /allow-interrupt|update-existing/u);
+            frame = `${NATIVE_SETUP_PREFIX}${JSON.stringify(receipt)}\n`;
+          }
+          // PTY chunks need not coincide with reserved frame boundaries.
+          pty.emitData(frame.slice(0, 11));
+          pty.emitData(frame.slice(11));
+          pty.exit(0);
+        });
+        return pty as unknown as IPty;
+      }) as typeof import('node-pty').spawn,
+    });
+    try {
+      const result = await terminal.runNativeSetup({
+        destination: 'operator@example.com', principalId: 'desktop:client',
+        package: { artifact, receiptSha256: 'c'.repeat(64) },
+      }, () => { committed = true; });
+      assert.deepEqual(result.receipt, receipt);
+      assert.equal(result.operator.executablePath, executable);
+      assert.equal(commands.length, 5);
+      assert.doesNotMatch(JSON.stringify(events), /setup-private-token|__MAKA_NATIVE_HOST_/u);
+    } finally {
+      await terminal.close();
+    }
+  }
 });
 
 test('reads a framed service result without projecting it into the SSH terminal', async () => {
@@ -1033,63 +900,6 @@ test('runs interactive operator activation as one strict framed SSH command', as
   await harness.terminal.close();
 });
 
-test('uploads a development release archive before running the same remote setup', async (t) => {
-  const directory = await mkdtemp(join(tmpdir(), 'maka-runtime-host-development-package-'));
-  t.after(() => rm(directory, { recursive: true, force: true }));
-  const archive = join(directory, 'maka-agent-development.tgz');
-  await writeFile(archive, 'development package');
-  const integrity = `sha512-${createHash('sha512').update('development package').digest('base64')}`;
-  const handlers = new Map<string, (...args: unknown[]) => unknown>();
-  const launches: Array<{ file: string; args: string[]; pty: FakePty }> = [];
-  const terminal = createDesktopRuntimeHostSshTerminal({
-    ipcMain: {
-      handle: (channel, handler) => handlers.set(channel, handler as (...args: unknown[]) => unknown),
-      removeHandler: (channel) => handlers.delete(channel),
-    },
-    send: () => undefined,
-    spawnPty: ((file: string, args: string[]) => {
-      const pty = new FakePty();
-      launches.push({ file, args, pty });
-      return pty as unknown as IPty;
-    }) as typeof import('node-pty').spawn,
-  });
-  t.after(() => terminal.close());
-
-  const setupInput = {
-    destination: 'operator@example.com',
-    setupPackage: {
-      kind: 'development_archive',
-      path: archive,
-      integrity,
-    } as const,
-    remotePlatform: 'posix' as const,
-    principalId: 'desktop:stable-client',
-  };
-  const setup = terminal.runSetup(setupInput, () => undefined);
-  await waitFor(() => launches.length === 1);
-  assert.equal(launches[0]?.file, 'scp');
-  assert.match(launches[0]?.args.at(-2) ?? '', /maka-agent-development\.tgz$/u);
-  assert.match(
-    launches[0]?.args.at(-1) ?? '',
-    /^operator@example\.com:\.\/\.maka-runtime-host-setup-.+\.tgz$/u,
-  );
-  launches[0]?.pty.exit(0);
-
-  await waitFor(() => launches.length === 2);
-  assert.equal(launches[1]?.file, 'ssh');
-  const remoteCommand = launches[1]?.args.at(-1) ?? '';
-  assert.match(remoteCommand, /--package.*maka-runtime-host-setup-.+\.tgz/u);
-  assert.match(remoteCommand, /MAKA_RUNTIME_HOST_SETUP_SOURCE_PACKAGE_INTEGRITY=/u);
-  assert.ok(remoteCommand.includes(integrity));
-  assert.match(remoteCommand, /--defer-pairing-commit/u);
-  assert.match(remoteCommand, /--update-existing/u);
-  assert.match(remoteCommand, /cd.*\$HOME/u);
-  assert.match(remoteCommand, /rm -f/u);
-  assert.match(remoteCommand, /exec \/bin\/sh -c/u);
-  launches[1]?.pty.exit(255);
-  await assert.rejects(setup, /exited with code 255/u);
-});
-
 function createHarness(
   mode: 'pending' | 'exit',
   options: { readonly managementTimeoutMs?: number } = {},
diff --git a/apps/desktop/src/main/__tests__/runtime-host-wsl-controller.test.ts b/apps/desktop/src/main/__tests__/runtime-host-wsl-controller.test.ts
index c73bf7344c..f0df9dd8f6 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-wsl-controller.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-wsl-controller.test.ts
@@ -19,21 +19,18 @@
 
 import assert from 'node:assert/strict';
 import type { ChildProcessWithoutNullStreams } from 'node:child_process';
-import { createHash } from 'node:crypto';
 import { EventEmitter } from 'node:events';
 import { PassThrough } from 'node:stream';
 import test from 'node:test';
 import type { RuntimeHostWslProcessFactory } from '@maka/runtime-host/client';
 import {
   encodeRuntimeHostServiceManagementFrame,
-  encodeRuntimeHostSetupFrame,
   RUNTIME_HOST_OPERATOR_PROJECT_DIRECTORY_CONFIGURATION_REQUEST_ENV,
   RUNTIME_HOST_SETUP_SOURCE_PACKAGE_INTEGRITY_ENV,
 } from '@maka/runtime-host/operator';
 import {
   resolveDesktopRuntimeHostWslTarget,
   runDesktopRuntimeHostWslManagement,
-  runDesktopRuntimeHostWslSetup,
   runDesktopRuntimeHostWslUpdate,
 } from '../runtime-host-wsl-controller.js';
 
@@ -166,102 +163,6 @@ test('WSL management invokes the stable operator directly with the exact deploym
   ]);
 });
 
-test('WSL setup forwards the development archive and its exact evidence', async () => {
-  const launches: string[][] = [];
-  const processFactory: RuntimeHostWslProcessFactory = (_executable, args) => {
-    launches.push([...args]);
-    const child = new EventEmitter() as ChildProcessWithoutNullStreams;
-    const stdin = new PassThrough();
-    const stdout = new PassThrough();
-    const stderr = new PassThrough();
-    Object.assign(child, { stdin, stdout, stderr, kill: () => true });
-    process.nextTick(() => {
-      if (launches.length === 1) stdout.end('/mnt/c/maka-development.tgz\n');
-      else {
-        stdout.end(encodeRuntimeHostSetupFrame({
-          schemaVersion: 1,
-          sequence: 0,
-          kind: 'complete',
-          version: '0.2.0-development',
-          serviceId: 'b'.repeat(64),
-          deploymentId: '00000000-0000-4000-8000-000000000001',
-          operator: { kind: 'native', platform: 'posix', executablePath: '/tmp/maka/maka' },
-          rootPath: '/tmp/maka/root',
-          rootId: 'a'.repeat(64),
-          endpoint: 'ws://127.0.0.1:7443/runtime-host',
-          credentialId: 'credential-1',
-          credential: 'secret-access-token',
-        }));
-      }
-      stderr.end();
-      child.emit('close', 0, null);
-    });
-    return child;
-  };
-  const integrity = `sha512-${createHash('sha512').update('archive evidence').digest('base64')}`;
-
-  const installed = await runDesktopRuntimeHostWslSetup({
-    distribution: 'Ubuntu',
-    setupPackage: {
-      kind: 'development_archive',
-      path: 'C:\\maka-development.tgz',
-      integrity,
-    },
-    principalId: 'desktop-owner:pairing',
-  }, () => undefined, undefined, { processFactory, wslExecutable: 'wsl.exe' });
-
-  assert.deepEqual(installed.operator, {
-    kind: 'native', platform: 'posix', executablePath: '/tmp/maka/maka',
-  });
-  assert.deepEqual(launches[0], [
-    '--distribution',
-    'Ubuntu',
-    '--exec',
-    'wslpath',
-    '-a',
-    '-u',
-    'C:\\maka-development.tgz',
-  ]);
-  const setupCommand = launches[1]?.at(-1) ?? '';
-  assert.match(setupCommand, /\$\{SHELL:-\/bin\/sh\}.*-lic/u);
-  assert.match(setupCommand, new RegExp(`${RUNTIME_HOST_SETUP_SOURCE_PACKAGE_INTEGRITY_ENV}=`, 'u'));
-  assert.ok(setupCommand.includes(integrity));
-  assert.match(setupCommand, /--update-existing/u);
-  assert.doesNotMatch(setupCommand, /--allow-interrupt-active-tasks/u);
-  assert.match(setupCommand, /--package.*\/mnt\/c\/maka-development\.tgz/u);
-});
-
-
-test('released WSL onboarding cannot authorize replacement or interruption', async () => {
-  let command = '';
-  await assert.rejects(runDesktopRuntimeHostWslSetup({
-    distribution: 'Ubuntu',
-    setupPackage: { kind: 'npm', specifier: 'maka-agent@0.2.0' },
-    principalId: 'desktop:client',
-  }, () => undefined, undefined, {
-    wslExecutable: 'wsl.exe',
-    processFactory: (_executable, args) => {
-      command = args.at(-1) ?? '';
-      const child = new EventEmitter() as ChildProcessWithoutNullStreams;
-      const stdout = new PassThrough();
-      const stderr = new PassThrough();
-      Object.assign(child, { stdin: new PassThrough(), stdout, stderr, kill: () => true });
-      process.nextTick(() => {
-        stdout.end(encodeRuntimeHostSetupFrame({
-          schemaVersion: 1, sequence: 0, kind: 'error',
-          error: { code: 'version_change_requires_update', message: 'Use the update workflow' },
-        }));
-        stderr.end();
-        child.emit('close', 1, null);
-      });
-      return child;
-    },
-  }), /Use the update workflow/u);
-  assert.doesNotMatch(command, /--update-existing|--allow-interrupt-active-tasks/u);
-  assert.match(command, /--reuse-existing-environment/u);
-});
-
-
 test('WSL update cancellation closes retirement input without killing the transaction', async () => {
   const abort = new AbortController();
   let finish!: () => void;
@@ -308,25 +209,3 @@ test('WSL update cancellation closes retirement input without killing the transa
   finish();
   await rejected;
 });
-
-
-test('released WSL discovery accepts an existing binding without a credential or package mutation', async () => {
-  const existing = {
-    schemaVersion: 1 as const, sequence: 0, kind: 'existing_environment' as const,
-    version: '0.3.0', serviceId: 'a'.repeat(64), rootId: 'a'.repeat(64), rootPath: '/state',
-    deploymentId: '00000000-0000-4000-8000-000000000001', operator: OPERATOR,
-  };
-  const result = await runDesktopRuntimeHostWslSetup({
-    distribution: 'Ubuntu', setupPackage: { kind: 'npm', specifier: 'maka-agent@0.2.0' }, principalId: 'desktop:client',
-  }, () => {}, undefined, {
-    wslExecutable: 'wsl.exe',
-    processFactory: () => {
-      const child = new EventEmitter() as ChildProcessWithoutNullStreams;
-      const stdout = new PassThrough(); const stderr = new PassThrough();
-      Object.assign(child, { stdin: new PassThrough(), stdout, stderr, kill: () => true });
-      process.nextTick(() => { stdout.end(encodeRuntimeHostSetupFrame(existing)); stderr.end(); child.emit('close', 0, null); });
-      return child;
-    },
-  });
-  assert.deepEqual(result, existing);
-});
diff --git a/apps/desktop/src/main/__tests__/runtime-host-wsl-handoff.test.ts b/apps/desktop/src/main/__tests__/runtime-host-wsl-handoff.test.ts
index 4d82b92e7a..a1ed9279ca 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-wsl-handoff.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-wsl-handoff.test.ts
@@ -92,29 +92,16 @@ test('WSL handoff carries exact source identity and separately forwards interrup
 });
 
 
-test('legacy operator without a configuration fingerprint remains fenced by source version and Host identity', async () => {
-  const { configurationFingerprint: _fingerprint, ...legacyService } = service;
+test('WSL handoff rejects a source without its configuration fingerprint', async () => {
+  const { configurationFingerprint: _fingerprint, ...unverifiedService } = service;
   const blocker = await resolveDesktopWslHostHandoff(profile, incompatible(), new AbortController().signal, {
     resolveBinding: async () => binding,
-    resolvePackage: async () => ({
-      kind: 'development_archive', path: '/selected.tgz', integrity: 'sha512-selected',
-      displayVersion: '0.3.0-dev-abcdef012345',
-    }),
-    status: async () => ({ schemaVersion: 1, kind: 'result', action: 'status', service: legacyService }),
-    update: async (input) => {
-      assert.equal(input.expectedConfigFingerprint, undefined);
-      assert.equal(input.expectedSourceVersion, legacyService.installedVersion);
-      assert.deepEqual(input.expectedHost, { hostEpoch: 'old-host', pid: 42 });
-      assert.equal(input.expectedTarget.deploymentId, binding.deployment.deploymentId);
-      return { schemaVersion: 1, kind: 'error', action: 'update', error: { code: 'target_mismatch', message: 'Source changed under deployment lock' } };
-    },
+    resolvePackage: async () => assert.fail('unverified source must not stage a package'),
+    status: async () => ({ schemaVersion: 1, kind: 'result', action: 'status', service: unverifiedService }),
+    update: async () => assert.fail('unverified source must not be updated'),
   });
-  assert.ok(blocker.replacement);
-  assert.deepEqual(blocker.packageChange, {
-    current: legacyService.installedVersion,
-    target: '0.3.0-dev-abcdef012345',
-  });
-  assert.deepEqual(await blocker.replacement.execute('refuse_active_work', () => {}, 'explicit'), { kind: 'changed' });
+  assert.equal(blocker.replacement, undefined);
+  assert.match(blocker.operatorStep!, /could not verify/u);
 });
 
 test('development handoff never presents package integrity as a version', async () => {
diff --git a/apps/desktop/src/main/__tests__/session-local.test.ts b/apps/desktop/src/main/__tests__/session-local.test.ts
index 75b5745017..c895e63285 100644
--- a/apps/desktop/src/main/__tests__/session-local.test.ts
+++ b/apps/desktop/src/main/__tests__/session-local.test.ts
@@ -45,7 +45,7 @@ import { createAttachmentApprovalRegistry } from '../attachment-approval.js';
 const accepted: TurnMessageSubmitResult = {
   disposition: 'turn_started',
   turnId: 'turn-1',
-  skillInvocation: { loaded: [], failed: [], receipts: [] },
+  preparation: [],
 };
 const intent = (messageId = 'message-1', sessionId = 'session-1'): LocalMessageIntent => ({
   command: { sessionId, messageId, placement: 'current_turn', content: { text: 'hello' } },
@@ -319,7 +319,7 @@ for (const refusal of ['operation-error', 'blocked-skill'] as const) {
         calls.push(input.messageId);
         if (input.messageId === 'message-1') {
           if (refusal === 'blocked-skill')
-            return { disposition: 'blocked', skillInvocation: accepted.skillInvocation };
+            return { disposition: 'blocked', message: 'Input unavailable', preparation: accepted.preparation };
           throw new RuntimeHostOperationError('turn.message.submit', 'session_busy', 'busy');
         }
         return accepted;
diff --git a/apps/desktop/src/main/__tests__/workbar-services-adapter.test.ts b/apps/desktop/src/main/__tests__/workbar-services-adapter.test.ts
index f9f724a310..df74a2470f 100644
--- a/apps/desktop/src/main/__tests__/workbar-services-adapter.test.ts
+++ b/apps/desktop/src/main/__tests__/workbar-services-adapter.test.ts
@@ -49,7 +49,7 @@ function createBridgeRecorder(): {
         disposition: 'steering',
         attachments: [],
         inlineReferences: [],
-        skillInvocation: { loaded: [], failed: [], receipts: [] },
+        preparation: [],
       },
     ],
   ]);
diff --git a/apps/desktop/src/main/browser-message-box.ts b/apps/desktop/src/main/browser-message-box.ts
index d3f1af108b..9ca5e481a4 100644
--- a/apps/desktop/src/main/browser-message-box.ts
+++ b/apps/desktop/src/main/browser-message-box.ts
@@ -107,6 +107,8 @@ export async function showBrowserMessageBoxWithRuntime(
 ): Promise<MessageBoxReturnValue> {
   activeBrowserMessageBoxPresentations += 1;
   try {
+    const cancelled = (): MessageBoxReturnValue => ({response: normalizeBrowserMessageBoxPresentation(options, {...appearance, dark: appearance.dark ?? runtime.shouldUseDarkColors}).cancelId, checkboxChecked: false});
+    if (options.signal?.aborted) return cancelled();
     const visibleParent = (): BrowserWindow | undefined =>
       parent && !parent.isDestroyed() && parent.isVisible() && !parent.isMinimized()
         ? parent
@@ -114,6 +116,9 @@ export async function showBrowserMessageBoxWithRuntime(
     try {
       return await presentBrowserMessageBox(runtime, options, visibleParent(), appearance);
     } catch (error) {
+      if (options.signal?.aborted) return cancelled();
+      // macOS parentless native dialogs ignore AbortSignal and block synchronously.
+      if (options.signal && !visibleParent()) throw error;
       runtime.onBrowserError(error);
       return await runtime.showNative(options, visibleParent());
     }
@@ -188,14 +193,19 @@ async function presentBrowserMessageBox(
         if (settled) return;
         settled = true;
         clearPresentationTimeout();
+        options.signal?.removeEventListener('abort', abort);
         resolve({ response, checkboxChecked: false });
       };
       const fail = (error: unknown): void => {
         if (settled) return;
         settled = true;
         clearPresentationTimeout();
+        options.signal?.removeEventListener('abort', abort);
         reject(error instanceof Error ? error : new Error(String(error)));
       };
+      const abort = () => finish(presentation.cancelId);
+      options.signal?.addEventListener('abort', abort, {once:true});
+      if (options.signal?.aborted) { abort(); return; }
       presentationTimeout = setTimeout(
         () => fail(new Error('Dialog renderer did not become interactive in time')),
         runtime.presentationTimeoutMs ?? DIALOG_PRESENTATION_TIMEOUT_MS,
diff --git a/apps/desktop/src/main/browser/browser-tools.ts b/apps/desktop/src/main/browser/browser-tools.ts
index 1997ba8bdc..6ab12ae98d 100644
--- a/apps/desktop/src/main/browser/browser-tools.ts
+++ b/apps/desktop/src/main/browser/browser-tools.ts
@@ -20,7 +20,7 @@
 import { z } from 'zod';
 import { htmlToMarkdown } from '@jackwener/opencli/utils';
 import type { IPage } from '@jackwener/opencli/types';
-import type { MakaTool } from '@maka/runtime/tool-runtime';
+import type { SessionTool } from '@maka/runtime/tool-runtime';
 import { browserOriginAdmission } from './browser-origin-admission.js';
 import type { BrowserOriginLease } from './browser-host.js';
 import {
@@ -162,7 +162,7 @@ function guardBrowserPage(page: IPage, lease: BrowserOriginLease): IPage {
   }) as IPage;
 }
 
-export function buildBrowserNavigateTool(): MakaTool<{ url: string }, string> {
+export function buildBrowserNavigateTool(): SessionTool<{ url: string }, string> {
   return {
     name: 'browser_navigate',
     displayName: '浏览器导航',
@@ -203,7 +203,7 @@ export function buildBrowserNavigateTool(): MakaTool<{ url: string }, string> {
   };
 }
 
-export function buildBrowserSnapshotTool(): MakaTool<Record<string, never>, string> {
+export function buildBrowserSnapshotTool(): SessionTool<Record<string, never>, string> {
   return {
     name: 'browser_snapshot',
     displayName: '浏览器快照',
@@ -233,7 +233,7 @@ export function buildBrowserSnapshotTool(): MakaTool<Record<string, never>, stri
   };
 }
 
-export function buildBrowserClickTool(): MakaTool<{ ref: string }, string> {
+export function buildBrowserClickTool(): SessionTool<{ ref: string }, string> {
   return {
     name: 'browser_click',
     displayName: '浏览器点击',
@@ -299,7 +299,7 @@ function navigationResult(url: string, requiresApproval: boolean): string {
   );
 }
 
-export function buildBrowserTypeTool(): MakaTool<{ ref: string; text: string; submit?: boolean }, string> {
+export function buildBrowserTypeTool(): SessionTool<{ ref: string; text: string; submit?: boolean }, string> {
   return {
     name: 'browser_type',
     displayName: '浏览器输入',
@@ -341,7 +341,7 @@ export function buildBrowserTypeTool(): MakaTool<{ ref: string; text: string; su
   };
 }
 
-export function buildBrowserWaitTool(): MakaTool<
+export function buildBrowserWaitTool(): SessionTool<
   { text?: string; selector?: string; time?: number; timeout?: number },
   string
 > {
@@ -424,7 +424,7 @@ export function buildBrowserWaitTool(): MakaTool<
   };
 }
 
-export function buildBrowserExtractTool(): MakaTool<{ selector?: string; start?: number }, string> {
+export function buildBrowserExtractTool(): SessionTool<{ selector?: string; start?: number }, string> {
   return {
     name: 'browser_extract',
     displayName: '浏览器提取',
@@ -503,7 +503,7 @@ export function readHtmlJs(selectorJson: string): string {
 }
 
 /** The six generic observe→act browser tools, in observe-before-act order. */
-export function buildBrowserTools(): MakaTool[] {
+export function buildBrowserTools(): SessionTool[] {
   return [
     buildBrowserNavigateTool(),
     buildBrowserSnapshotTool(),
@@ -511,5 +511,5 @@ export function buildBrowserTools(): MakaTool[] {
     buildBrowserTypeTool(),
     buildBrowserWaitTool(),
     buildBrowserExtractTool(),
-  ] as MakaTool[];
+  ] as SessionTool[];
 }
diff --git a/apps/desktop/src/main/client-plugin-remote-ipc.ts b/apps/desktop/src/main/client-plugin-remote-ipc.ts
index 08dd0d0c9e..838fb55f2c 100644
--- a/apps/desktop/src/main/client-plugin-remote-ipc.ts
+++ b/apps/desktop/src/main/client-plugin-remote-ipc.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { HOST_OPERATION_SPECS, type PluginRemoteInput, type PluginRemoteResult } from '@maka/runtime-host/protocol';
+import { HOST_OPERATION_SPECS, type PluginRemoteInput, type PluginRemoteResult, type PluginAuthorizationInput, type PluginAuthorizationResult } from '@maka/runtime-host/protocol';
 import type { IpcMain, IpcMainInvokeEvent, WebContents } from 'electron';
 import { isAbsolute } from 'node:path';
 import { randomUUID } from 'node:crypto';
@@ -29,6 +29,8 @@ interface DocumentOwner {
   readonly pending: Set<Promise<unknown>>;
   pendingOpens: number;
   closed: boolean;
+  readonly cancellation: AbortController;
+  readonly authorizations: Map<string, AbortController>;
   dispose(): void;
 }
 
@@ -41,6 +43,11 @@ export function registerClientPluginRemoteIpc(input: {
   };
   readonly ownsRenderer: (contents: WebContents) => boolean;
   readonly report: (error: unknown) => void;
+  readonly authorization?: {
+    validate(client: PluginAuthorizationInput['client']): Promise<void>;
+    confirm(input: PluginAuthorizationInput, signal: AbortSignal): Promise<boolean>;
+    request(input: PluginAuthorizationInput): Promise<PluginAuthorizationResult>;
+  };
   readonly files?: {
     validate(input: PluginClientQueryInput): Promise<void>;
     pick(): Promise<string | null>;
@@ -71,10 +78,11 @@ export function registerClientPluginRemoteIpc(input: {
     if (previous?.nonce === nonce) return previous;
     previous?.dispose();
     const state: DocumentOwner = {
-      nonce, documents: new Set(), pending: new Set(), pendingOpens: 0, closed: false,
+      nonce, documents: new Set(), pending: new Set(), pendingOpens: 0, closed: false, cancellation: new AbortController(), authorizations: new Map(),
       dispose() {
         if (state.closed) return;
         state.closed = true;
+        state.cancellation.abort();
         if (owners.get(sender) === state) owners.delete(sender);
         sender.removeListener('did-start-navigation', navigation);
         sender.removeListener('render-process-gone', dispose);
@@ -151,6 +159,43 @@ export function registerClientPluginRemoteIpc(input: {
     return path;
   });
 
+  input.ipcMain.handle('plugins:authorization-cancel', (event, nonce: unknown, expectedEpoch: unknown, requestId: unknown) => {
+    const owner = owners.get(event.sender);
+    if (closed || !owner || owner.closed || owner.nonce !== nonce || expectedEpoch !== epoch ||
+      !input.ownsRenderer(event.sender) || event.senderFrame?.frameToken !== event.sender.mainFrame.frameToken) return;
+    if (typeof requestId === 'string') owner.authorizations.get(requestId)?.abort();
+  });
+
+  input.ipcMain.handle('plugins:authorization', async (event, nonce: unknown, expectedEpoch: unknown, raw: unknown, requestId: unknown): Promise<PluginAuthorizationResult> => {
+    const owner = ownerFor(event, nonce);
+    if (expectedEpoch !== epoch) throw new Error('Client connection has retired');
+    const authorization = input.authorization;
+    if (!authorization) throw new Error('Plugin authorization is unavailable');
+    const value = HOST_OPERATION_SPECS['plugin.authorization'].decodeInput(raw);
+    if (typeof requestId !== 'string' || !/^[0-9a-f-]{36}$/i.test(requestId) || owner.authorizations.has(requestId))
+      throw new Error('Invalid authorization request identity');
+    if (owner.authorizations.size >= 32) throw new Error('Authorization request limit exceeded');
+    const cancellation = new AbortController();
+    owner.authorizations.set(requestId, cancellation);
+    const signal = AbortSignal.any([cancellation.signal, owner.cancellation.signal, AbortSignal.timeout(90_000)]);
+    return track(owner.pending, (async () => {
+      try {
+      await authorization.validate(value.client);
+      signal.throwIfAborted();
+      if (value.command.kind === 'approve') {
+        if (!await authorization.confirm(value, signal) || signal.aborted) return {kind:'grant',grant:null};
+      }
+      signal.throwIfAborted();
+      // Revalidate publication after consent; a pending dialog never follows a replacement.
+      await authorization.validate(value.client);
+      signal.throwIfAborted();
+      // Beyond this cut Host owns the accepted command. Cancellation never
+      // retracts a durable grant; its operation identity recovers the receipt.
+      return await authorization.request(value);
+      } finally { owner.authorizations.delete(requestId); }
+    })());
+  });
+
   return async () => {
     closed = true;
     for (const owner of owners.values()) owner.dispose();
diff --git a/apps/desktop/src/main/main-process-diagnostics.ts b/apps/desktop/src/main/main-process-diagnostics.ts
index 7adc9c9463..8858fd398e 100644
--- a/apps/desktop/src/main/main-process-diagnostics.ts
+++ b/apps/desktop/src/main/main-process-diagnostics.ts
@@ -493,7 +493,7 @@ export function formatDesktopDiagnosticReport(
       `Protocol: v${host.protocolVersion} · compatibility ${host.compatibilityEpoch}`,
       `State: ${host.state}`,
       `Process: ${host.pid} · uptime ${host.processUptimeSeconds}s`,
-      `Runtime: Node ${host.nodeVersion} · ${host.platform} ${host.osRelease} (${host.arch})`,
+      `Platform: ${host.platform} ${host.osRelease} (${host.arch})`,
       `Activity: ${host.connections} connections · ${host.activeOperations} operations · ${host.activeResidencies} residencies`,
       `Recent Runtime Host logs (${host.logs.length})`,
       ...(host.logs.length > 0 ? host.logs : ['<none captured>']),
diff --git a/apps/desktop/src/main/managed-artifact-preview-tools.ts b/apps/desktop/src/main/managed-artifact-preview-tools.ts
index 939a78c2f5..50db0639d5 100644
--- a/apps/desktop/src/main/managed-artifact-preview-tools.ts
+++ b/apps/desktop/src/main/managed-artifact-preview-tools.ts
@@ -17,14 +17,14 @@
  * under the License.
  */
 
-import type { MakaTool } from '@maka/runtime/tool-runtime';
+import type { SessionTool } from '@maka/runtime/tool-runtime';
 import { z } from 'zod';
 import type { ArtifactPreviewEndpoint } from './managed-artifact-preview.js';
 
 export function buildManagedArtifactPreviewTools(
   prepare: (sessionId: string, artifactId: string, signal: AbortSignal) => Promise<ArtifactPreviewEndpoint>,
-): readonly MakaTool[] {
-  const tool: MakaTool<{ artifactId: string }, ArtifactPreviewEndpoint> = {
+): readonly SessionTool[] {
+  const tool: SessionTool<{ artifactId: string }, ArtifactPreviewEndpoint> = {
     name: 'ArtifactPreview',
     displayName: 'Prepare HTML preview',
     description: 'Create a Desktop-managed, temporary HTTP URL for an HTML Artifact in the current session. No shell server or file:// navigation is needed. The URL is a bearer capability: do not share it. It expires after 30 minutes or when the client disconnects. Only self-contained HTML is supported: inline scripts/styles and embedded images; remote subresources, fetch requests, forms and local file access are blocked. This is not OS network isolation: an external browser can navigate [...]
diff --git a/apps/desktop/src/main/permission-response-guard.ts b/apps/desktop/src/main/permission-response-guard.ts
index b2390fd984..6d389c4554 100644
--- a/apps/desktop/src/main/permission-response-guard.ts
+++ b/apps/desktop/src/main/permission-response-guard.ts
@@ -36,6 +36,7 @@ import { MAX_ATTACHMENT_COUNT } from '@maka/core/attachments';
 import { isAttachmentRef, isCanonicalStorageRef, type AttachmentRef } from '@maka/core/events';
 
 import { isOrchestrationMode, isTurnOrchestrationSource } from '@maka/core/orchestration';
+import { decodeInputSelections, type InputSelections } from '@maka/runtime-host/protocol';
 
 const MAX_PERMISSION_REQUEST_ID_LENGTH = 128;
 const MAX_TURN_ID_LENGTH = 128;
@@ -62,7 +63,7 @@ interface NormalizedSendSessionCommand {
   turnId?: string;
   text: string;
   displayText?: string;
-  skillIds?: string[];
+  inputSelections?: InputSelections;
   attachmentItems?: unknown;
   retainedAttachments?: AttachmentRef[];
   turnOrchestration?: TurnOrchestration;
@@ -208,7 +209,7 @@ export function normalizeSessionSendCommand(input: unknown): NormalizedSendSessi
   const text = normalizeSendText(value.text);
   const displayText =
     value.displayText === undefined ? undefined : normalizeSendText(value.displayText);
-  const skillIds = normalizeSessionSkillIds(value.skillIds);
+  const inputSelections = decodeInputSelections(value.inputSelections);
   // A send may carry structured content instead of text (a pure quote or a
   // pure attachment, #4804). Only the presence is decided here: attachment
   // state, ownership, and size limits stay with the ingestion checks, and
@@ -227,7 +228,7 @@ export function normalizeSessionSendCommand(input: unknown): NormalizedSendSessi
   const hasAttachmentItems = (attachmentItems.attachmentItems?.length ?? 0) > 0;
   if (
     !text.trim() &&
-    skillIds.length === 0 &&
+    Object.keys(inputSelections).length === 0 &&
     (quotes?.length ?? 0) === 0 &&
     !hasAttachmentItems &&
     (retainedAttachments.retainedAttachments?.length ?? 0) === 0
@@ -240,7 +241,7 @@ export function normalizeSessionSendCommand(input: unknown): NormalizedSendSessi
     ...normalizeOptionalSendTurnId(value.turnId),
     text,
     ...(displayText !== undefined ? { displayText } : {}),
-    ...(skillIds.length > 0 ? { skillIds } : {}),
+    ...(Object.keys(inputSelections).length > 0 ? { inputSelections } : {}),
     ...attachmentItems,
     ...retainedAttachments,
     ...(value.turnOrchestration !== undefined
@@ -385,26 +386,6 @@ function normalizeSendText(input: unknown): string {
   return input;
 }
 
-export function normalizeSessionSkillIds(input: unknown): string[] {
-  if (input === undefined) return [];
-  if (
-    !Array.isArray(input) ||
-    input.length > 50 ||
-    input.some(
-      (id) =>
-        typeof id !== 'string' ||
-        id.length === 0 ||
-        id.length > 512 ||
-        // The field name is retained for wire compatibility. Values may be a
-        // legacy id or a stable scope-aware ref such as project:maka:writer.
-        !/^[A-Za-z0-9][A-Za-z0-9._-]*(?::[A-Za-z0-9][A-Za-z0-9._-]*)*$/.test(id),
-    )
-  ) {
-    throw new Error('Invalid send skillIds');
-  }
-  return [...input];
-}
-
 export function normalizeStopSessionInput(input: unknown): NormalizedStopSessionInput {
   if (input === undefined) return {};
   const value = requireObject(input, 'Invalid stop session input');
diff --git a/apps/desktop/src/main/plugin-authorization-dialog.ts b/apps/desktop/src/main/plugin-authorization-dialog.ts
new file mode 100644
index 0000000000..578a59daa8
--- /dev/null
+++ b/apps/desktop/src/main/plugin-authorization-dialog.ts
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { AuthorizationCapability } from '@maka-agent/plugin-sdk/client';
+import type { PluginAuthorizationInput } from '@maka/runtime-host/protocol';
+import type { MessageBoxOptions } from 'electron';
+
+/** Application-owned copy; plugins supply a proposal, never the confirmation UI. */
+export function pluginAuthorizationDialog(input: PluginAuthorizationInput, locale: string, signal: AbortSignal): MessageBoxOptions {
+  if (input.command.kind !== 'approve') throw new Error('Consent requires a proposal');
+  const zh = locale.startsWith('zh');
+  const labels: Record<AuthorizationCapability, readonly [string,string]> = {
+    read_files: ['Read workspace files', '读取工作区文件'],
+    write_files: ['Modify workspace files', '修改工作区文件'],
+    network: ['Make network requests', '发送网络请求'],
+    models: ['Call models (may incur usage costs)', '调用模型(可能产生费用)'],
+    processes: ['Run programs and terminals', '运行程序与终端'],
+    client_capabilities: ['Invoke connected client capabilities', '调用已连接客户端的能力'],
+    executions: ['Create, submit, inspect and control executions', '创建、提交、查询与控制执行'],
+    notifications: ['Send notifications', '发送通知'],
+  };
+  const {request} = input.command;
+  const target = request.target.kind === 'profile' ? 'profile' : request.target.kind === 'session'
+    ? `${request.target.sessionId} (${zh ? '当前工作区与权限' : 'current workspace and permissions'})`
+    : `${request.target.workspace.kind === 'project' ? request.target.workspace.projectId : request.target.workspace.path} (${request.target.permissionMode})`;
+  return {
+    type: 'warning', title: zh ? '插件后台授权' : 'Plugin background authorization',
+    message: `${input.client.extensionId}\n${zh ? '允许此插件在后台工作?' : 'Allow this plugin to work in the background?'}`,
+    detail: [request.title, `${zh ? '范围' : 'Scope'}: ${input.scope}`, `${zh ? '目标' : 'Target'}: ${target}`,
+      request.capabilities.map((capability) => labels[capability][zh ? 1 : 0]).join('\n'),
+      zh ? '授权在重启后仍有效,直到撤销。目标权限变化会使授权失效。' : 'Authorization survives restart until revoked. Changes to the target permissions invalidate it.',
+    ].join('\n\n'),
+    buttons: zh ? ['取消','允许'] : ['Cancel','Allow'], cancelId:0, defaultId:0, signal,
+  };
+}
diff --git a/apps/desktop/src/main/runtime-host-boot.ts b/apps/desktop/src/main/runtime-host-boot.ts
index 4cbac05bcb..590fc60441 100644
--- a/apps/desktop/src/main/runtime-host-boot.ts
+++ b/apps/desktop/src/main/runtime-host-boot.ts
@@ -231,7 +231,6 @@ import {
 } from "./runtime-host-ssh-terminal.js";
 import {
   runDesktopRuntimeHostWslManagement,
-  runDesktopRuntimeHostWslSetup,
   resolveDesktopRuntimeHostWslTarget,
   runNativeRuntimeHostWslSetup,
   prepareNativeRuntimeHostWslPackage,
@@ -261,6 +260,7 @@ import { RuntimeHostOAuthPresentation } from "./runtime-host-oauth-presentation.
 import { registerRuntimeHostPermissionsIpc } from "./runtime-host-permissions-ipc-main.js";
 import { registerRuntimeHostRendererIpc } from "./runtime-host-renderer-ipc-main.js";
 import { registerClientPluginRemoteIpc } from './client-plugin-remote-ipc.js';
+import { pluginAuthorizationDialog } from './plugin-authorization-dialog.js';
 import { registerRuntimeHostSearchIpc } from "./runtime-host-search-ipc-main.js";
 import { createRuntimeHostProjectCatalog } from "./runtime-host-project-catalog.js";
 import { createRuntimeHostDefaultRecovery } from "./runtime-host-default-recovery.js";
@@ -793,18 +793,14 @@ const runtimeHostOnboarding = createDesktopRuntimeHostOnboarding({
   ipcMain,
   clientInstanceId: runtimeHostClientInstanceId,
   profiles: runtimeHostProfileService,
-  ...(!isE2e ? { nativeSetup: {
+  nativeSetup: {
     resolvePackage: resolveNativePackage,
     ssh: runtimeHostSshTerminal.runNativeSetup,
     wsl: runNativeRuntimeHostWslSetup,
-  } } : {}),
-  runSetup: runtimeHostSshTerminal.runSetup,
-  runWslSetup: runDesktopRuntimeHostWslSetup,
+  },
   resolveWslTargetIdentity: resolveDesktopRuntimeHostWslTarget,
   listWslDistributions: listRuntimeHostWslDistributions,
-  setupPackageMode: runtimeHostSetupPackage.mode,
   resolveSshTargetIdentity: runtimeHostSshTerminal.resolveTargetIdentity,
-  resolveSetupPackage: runtimeHostSetupPackage.resolve,
   send: (snapshot) =>
     mainWindowController.send("runtime-host-onboarding:changed", snapshot),
 });
@@ -1309,7 +1305,7 @@ const startLocalRuntimeHostManager = () => startRuntimeHostDesktopManager(
               const target = runtimePolicyTargetsByEpoch.get(scope.targetEpoch);
               if (!target?.isActive()) throw new Error('Preview target is no longer active');
               return managedArtifactPreview.prepare(scope.targetEpoch, target.client, sessionId, artifactId, signal);
-            }),
+            }).map(tool => ({ context: 'session' as const, tool })),
           },
           {
             offerId: "desktop_settings",
@@ -1818,6 +1814,15 @@ function registerHostClientIpc(
     ipcMain: scopedIpc, client,
     ownsRenderer: (contents) => mainWindowController.ownsRenderer(contents),
     report: (error) => console.error('[plugins] Remote cleanup failed:', error),
+    authorization: {
+      validate: async (identity) => { await client.request('plugin.client.query', {kind:'bundle',entryId:identity.entryId,activation:identity.activation,clientDigest:identity.clientDigest,offset:0}); },
+      confirm: async (input, signal) => {
+        const locale = desktopLocale.current();
+        const result = await showDesktopMessageBox(pluginAuthorizationDialog(input, locale, signal), {locale});
+        return result.response === 1;
+      },
+      request: (input) => client.request('plugin.authorization', input),
+    },
     files: usesHostWorkspace ? undefined : {
       validate: async (input) => { await client.request('plugin.client.query', input); },
       pick: async () => {
diff --git a/apps/desktop/src/main/runtime-host-bot-session-adapter.ts b/apps/desktop/src/main/runtime-host-bot-session-adapter.ts
index e9beb307a4..3902583d9f 100644
--- a/apps/desktop/src/main/runtime-host-bot-session-adapter.ts
+++ b/apps/desktop/src/main/runtime-host-bot-session-adapter.ts
@@ -151,13 +151,7 @@ export function createRuntimeHostBotSessionAdapter(
           if (started.kind === 'blocked') {
             return {
               kind: 'errored' as const,
-              reason: started.skillInvocation.failed
-                .map((failure) =>
-                  failure.reason === 'too_many_requests'
-                    ? `Skill request limit exceeded: ${failure.requestLimit}`
-                    : `${failure.request}: ${failure.reason}`,
-                )
-                .join(', '),
+              reason: started.message,
             };
           }
         } catch (error) {
diff --git a/apps/desktop/src/main/runtime-host-client.ts b/apps/desktop/src/main/runtime-host-client.ts
index 9f85c1e48d..3b7f1be08f 100644
--- a/apps/desktop/src/main/runtime-host-client.ts
+++ b/apps/desktop/src/main/runtime-host-client.ts
@@ -106,7 +106,6 @@ import {
   SESSION_TRANSCRIPT_BOOTSTRAP_MAX_BYTES,
   type SessionCatalogChangedFrame,
   type ScheduledTaskChangedFrame,
-  type SessionCatalogItem,
   type SessionCatalogProjection,
   type SharedSessionCatalogProjection,
   type CollaborationAccessQueryResult,
@@ -653,7 +652,7 @@ export class DesktopRuntimeHostClient {
   async listSessions(): Promise<SessionCatalogProjection[]> {
     this.#assertOpen();
     try {
-      return (await readRuntimeHostSessions(this.connection)).map(requireSessionProjection);
+      return await readRuntimeHostSessions(this.connection);
     } catch (error) {
       if (error instanceof DesktopRuntimeHostClientError) throw error;
       if (!(error instanceof RuntimeHostCatalogReadError)) throw error;
@@ -914,17 +913,13 @@ export class DesktopRuntimeHostClient {
         "Runtime Host returned an invalid Session catalog lookup",
       );
     }
-    return result.session === null
-      ? null
-      : requireSessionProjection(result.session);
+    return result.session;
   }
 
   async createSession(
     input: SessionCreateInput,
   ): Promise<SessionCatalogProjection> {
-    return requireSessionProjection(
-      await this.request("session.create", input),
-    );
+    return this.request("session.create", input);
   }
 
   listExternalSessionSources(): Promise<ExternalSessionSourceQueryResult> {
@@ -942,9 +937,7 @@ export class DesktopRuntimeHostClient {
     readonly sourceSessionId: string;
   }): Promise<ExternalSessionImportResult<SessionCatalogProjection>> {
     const result = await this.request("external-session.import", input);
-    return result.kind === 'imported'
-      ? { kind: 'imported', session: requireSessionProjection(result.session) }
-      : result;
+    return result;
   }
 
   exportSessionBundle(input: {
@@ -996,12 +989,7 @@ export class DesktopRuntimeHostClient {
     sessionId: string,
     readThroughMessageId: string,
   ): Promise<SessionCatalogProjection> {
-    return requireSessionProjection(
-      await this.request("session.read_marker.set", {
-        sessionId,
-        readThroughMessageId,
-      }),
-    );
+    return this.request("session.read_marker.set", { sessionId, readThroughMessageId });
   }
 
   readExecutionBoundary(sessionId: string): Promise<ExecutionBoundarySummary> {
@@ -1012,9 +1000,7 @@ export class DesktopRuntimeHostClient {
     sessionId: string,
     state: SessionLifecycleState,
   ): Promise<SessionCatalogProjection> {
-    return requireSessionProjection(
-      await this.request("session.lifecycle.set", { sessionId, state }),
-    );
+    return this.request("session.lifecycle.set", { sessionId, state });
   }
 
   /**
@@ -1095,7 +1081,7 @@ export class DesktopRuntimeHostClient {
           ? await this.request("session.branch.create", request)
           : await this.request("session.revision.create", request);
       if (result.kind === "committed")
-        return requireSessionProjection(result.session);
+        return result.session;
     }
     throw revisionConflict(`${kind} copy`, input.sourceSessionId);
   }
@@ -1708,7 +1694,7 @@ export class DesktopRuntimeHostClient {
       const current = await this.#requireSession(sessionId);
       const result = await update(current);
       if (result.kind === "committed")
-        return requireSessionProjection(result.session);
+        return result.session;
     }
     throw revisionConflict("update", sessionId);
   }
@@ -1800,16 +1786,6 @@ class DesktopSessionHandle implements DesktopRuntimeHostSession {
   }
 }
 
-function requireSessionProjection(
-  item: SessionCatalogItem,
-): SessionCatalogProjection {
-  if (!("kind" in item)) return item;
-  throw new DesktopRuntimeHostClientError(
-    "unsupported_session",
-    `Runtime Host Session is not representable by this Desktop Client: ${item.id}`,
-  );
-}
-
 function clientClosed(): DesktopRuntimeHostClientError {
   return new DesktopRuntimeHostClientError(
     "client_closed",
diff --git a/apps/desktop/src/main/runtime-host-local-remote-access.ts b/apps/desktop/src/main/runtime-host-local-remote-access.ts
index 4f9a3fd861..f554779dd0 100644
--- a/apps/desktop/src/main/runtime-host-local-remote-access.ts
+++ b/apps/desktop/src/main/runtime-host-local-remote-access.ts
@@ -29,7 +29,6 @@ import {
   type HostHandoffBlocker,
 } from '@maka/runtime-host/client';
 import {
-  createRuntimeHostLegacyPosixOperatorCommand,
   runtimeHostManagedOperatorCommand,
   decodeRuntimeHostOperatorCommand,
   resolveRuntimeHostManagedDeploymentAuthority,
@@ -77,16 +76,6 @@ interface LocalServiceSetupPending {
   readonly allowInterruptActiveTasks: boolean;
 }
 
-/** Released schema-v1 setup intent written before managed ownership was established. */
-interface LocalServiceLegacyHandoff {
-  readonly schemaVersion: 1;
-  readonly state: 'handoff';
-  readonly rootPath: string;
-  readonly rootId: string;
-  readonly coordinationRelays: readonly string[];
-  readonly allowInterruptActiveTasks: boolean;
-}
-
 interface LocalServiceManaged extends LocalServiceTarget {
   readonly state: 'managed';
 }
@@ -163,7 +152,6 @@ interface LocalServiceUninstalling extends LocalServiceTarget {
 }
 
 type LocalServiceLifecycle =
-  | LocalServiceLegacyHandoff
   | LocalServiceSetupPending
   | LocalServiceManaged
   | LocalServicePeerChanging
@@ -237,7 +225,7 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
     });
 
   const adoptCommittedSetup = async (
-    setup: LocalServiceSetupPending | LocalServiceLegacyHandoff,
+    setup: LocalServiceSetupPending,
   ): Promise<
     | { readonly kind: 'absent' | 'transition' }
     | { readonly kind: 'managed'; readonly managed: LocalServiceManaged }
@@ -245,16 +233,7 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
     const authority = await resolveManagedDeploymentAuthority(setup.rootId);
     if (!authority) return { kind: 'absent' };
     if (authority.kind === 'transition') return authority;
-    const target =
-      setup.schemaVersion === 1
-        ? {
-            ...authority.target,
-            operator: createRuntimeHostLegacyPosixOperatorCommand(
-              join(authority.deploymentRoot, 'operator'),
-            ),
-          }
-        : authority.target;
-    const managed = managedLifecycle(target);
+    const managed = managedLifecycle(authority.target);
     await writeDocument(lifecyclePath, managed);
     return { kind: 'managed', managed };
   };
@@ -276,7 +255,7 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
           return {
             state: 'unavailable',
             message:
-              lifecycle.state === 'setupPending' || lifecycle.state === 'handoff'
+              lifecycle.state === 'setupPending'
                 ? 'Local Runtime Host setup is being recovered'
                 : lifecycle.state === 'peerChanging'
                   ? 'Local Runtime Host remote access is being recovered'
@@ -317,14 +296,6 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
         if (recovered.kind === 'active_tasks') return recovered;
         lifecycle = managedLifecycle(lifecycle);
       }
-      if (lifecycle?.state === 'handoff') {
-        const recovered = await recoverLegacyHandoff(lifecycle);
-        if (recovered.kind === 'active_tasks') return recovered;
-        if (recovered.kind === 'external') {
-          throw new Error('The Local Runtime Host is already managed outside this Desktop');
-        }
-        lifecycle = recovered.managed;
-      }
       if (lifecycle?.state === 'setupPending') {
         const committed = await adoptCommittedSetup(lifecycle);
         if (committed.kind === 'managed') {
@@ -487,51 +458,6 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
     }
   };
 
-  const recoverLegacyHandoff = async (
-    legacy: LocalServiceLegacyHandoff,
-  ): Promise<
-    | { readonly kind: 'active_tasks' }
-    | { readonly kind: 'external' }
-    | { readonly kind: 'complete'; readonly managed: LocalServiceManaged }
-  > => {
-    const pending = pendingSetup(legacy);
-    const authority = await adoptCommittedSetup(legacy);
-    if (authority.kind === 'managed') {
-      return { kind: 'complete', managed: authority.managed };
-    }
-    if (authority.kind === 'transition') {
-      await writeDocument(lifecyclePath, pending);
-      return finishSetup(pending, 'recovery');
-    }
-
-    const manager = requireManager(input.manager);
-    const retirement = await manager.retireOwnedLocalHost(
-      legacy.allowInterruptActiveTasks ? 'interrupt_active_work' : 'refuse_active_work',
-    );
-    if (retirement.kind === 'active_tasks') return { kind: 'active_tasks' };
-    if (retirement.kind === 'not_owned') {
-      const raced = await adoptCommittedSetup(legacy);
-      if (raced.kind === 'managed') {
-        return { kind: 'complete', managed: raced.managed };
-      }
-      if (raced.kind === 'absent') {
-        await removeDocument(lifecyclePath);
-        return { kind: 'external' };
-      }
-    }
-
-    try {
-      await writeDocument(lifecyclePath, pending);
-      const setupPackage = await input.resolveSetupPackage(closing.signal);
-      const reconcile = () => reconcileSetup(pending, setupPackage);
-      return retirement.kind === 'not_owned'
-        ? await manager.runManagedLocalHostChange(reconcile)
-        : await reconcile();
-    } finally {
-      if (retirement.kind === 'retired') retirement.resume();
-    }
-  };
-
   const createConnectionCode = (): Promise<string> =>
     serialize(async () => {
       const managed = requireManaged(
@@ -823,9 +749,10 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
           if (facts.hostEpoch === observed.connection.hostEpoch && facts.pid === observed.registration.pid &&
             facts.state === 'ready' && facts.connections >= 1 && facts.activeOperations >= 1) {
             // The accepted diagnostic connection and query are our own, not work
-            // requiring consent. Legacy residency evidence stays conservative.
+            // requiring consent. The Host supplies the drain count directly.
             activity = { connections: facts.connections - 1, activeOperations: facts.activeOperations - 1,
               processUptimeSeconds: facts.processUptimeSeconds, residencies: facts.residencies,
+              drainResidencies: facts.drainResidencies,
               ...(observed.connection.cooperativeHandoff ? { cooperativeHandoff: true } : {}) };
           }
         } catch { /* Missing activity evidence never authorizes automatic interruption. */ }
@@ -958,18 +885,16 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
           return true;
         });
       }
-      if (observed?.state !== 'setupPending' && observed?.state !== 'handoff') return false;
+      if (observed?.state !== 'setupPending') return false;
       return serialize(async () => {
         operationSignal.throwIfAborted();
         const pending = await readLifecycle(lifecyclePath, input.rootPath, input.rootId);
-        if (pending?.state !== 'setupPending' && pending?.state !== 'handoff') return false;
-        const current = pendingSetup(pending);
+        if (pending?.state !== 'setupPending') return false;
         const authority = await adoptCommittedSetup(pending);
         if (authority.kind === 'absent') return false;
         if (authority.kind === 'managed') return true;
-        if (pending.state === 'handoff') await writeDocument(lifecyclePath, current);
         const setupPackage = await input.resolveSetupPackage(operationSignal);
-        await reconcileSetup(current, setupPackage, operationSignal);
+        await reconcileSetup(pending, setupPackage, operationSignal);
         return true;
       });
     },
@@ -988,12 +913,11 @@ export function createDesktopLocalRuntimeHostRemoteAccess(input: {
           }
           return;
         }
-        if (lifecycle.state === 'handoff' || lifecycle.state === 'setupPending') {
+        if (lifecycle.state === 'setupPending') {
           const committed = await adoptCommittedSetup(lifecycle);
           if (committed.kind === 'managed') return;
           if (!input.directPeerAvailable) return;
-          if (lifecycle.state === 'handoff') await recoverLegacyHandoff(lifecycle);
-          else await finishSetup(lifecycle, 'recovery');
+          await finishSetup(lifecycle, 'recovery');
           return;
         }
       }),
@@ -1186,19 +1110,6 @@ function requireManagementTarget(
   return lifecycle;
 }
 
-function pendingSetup(
-  intent: LocalServiceSetupPending | LocalServiceLegacyHandoff,
-): LocalServiceSetupPending {
-  return {
-    schemaVersion: 2,
-    state: 'setupPending',
-    rootPath: intent.rootPath,
-    rootId: intent.rootId,
-    coordinationRelays: intent.coordinationRelays,
-    allowInterruptActiveTasks: intent.allowInterruptActiveTasks,
-  };
-}
-
 function managedLifecycle(intent: LocalServiceTarget): LocalServiceManaged {
   return {
     schemaVersion: 2,
@@ -1215,7 +1126,7 @@ function hasManagedServiceTarget(lifecycle: LocalServiceLifecycle): lifecycle is
   | LocalServiceManaged
   | LocalServicePeerChanging
   | LocalServiceUninstalling {
-  return lifecycle.state !== 'handoff' && lifecycle.state !== 'setupPending';
+  return lifecycle.state !== 'setupPending';
 }
 
 async function readLifecycle(
@@ -1232,55 +1143,13 @@ async function readLifecycle(
   }
   if (
     !isRecord(value) ||
-    (value.schemaVersion !== 1 && value.schemaVersion !== 2) ||
+    value.schemaVersion !== 2 ||
     value.rootPath !== rootPath ||
     value.rootId !== rootId
   ) {
     throw new Error('Local Runtime Host service lifecycle is invalid');
   }
-  let record: Record<string, unknown> = value;
-  if (record.schemaVersion === 1 && record.state === 'handoff') {
-    assertExactKeys(record, [
-      'schemaVersion',
-      'state',
-      'rootPath',
-      'rootId',
-      'coordinationRelays',
-      'allowInterruptActiveTasks',
-    ]);
-    if (typeof record.allowInterruptActiveTasks !== 'boolean') {
-      throw new Error('Local Runtime Host setup intent is invalid');
-    }
-    return {
-      schemaVersion: 1,
-      state: 'handoff',
-      rootPath,
-      rootId,
-      coordinationRelays: requireAddresses(record.coordinationRelays),
-      allowInterruptActiveTasks: record.allowInterruptActiveTasks,
-    };
-  }
-  let migrated = false;
-  if (record.schemaVersion === 1) {
-    if (record.state === 'setupPending') {
-      record = { ...record, schemaVersion: 2 };
-    } else {
-      if (typeof record.operatorPath !== 'string') {
-        throw new Error('Local Runtime Host service receipt is invalid');
-      }
-      const { operatorPath, ...legacy } = record;
-      record = {
-        ...legacy,
-        schemaVersion: 2,
-        operator: createRuntimeHostLegacyPosixOperatorCommand(operatorPath),
-      };
-    }
-    migrated = true;
-  }
-  const finish = async <T extends LocalServiceLifecycle>(lifecycle: T): Promise<T> => {
-    if (migrated) await writeDocument(path, lifecycle);
-    return lifecycle;
-  };
+  const record = value;
   if (record.state === 'setupPending') {
     assertExactKeys(record, [
       'schemaVersion',
@@ -1293,14 +1162,14 @@ async function readLifecycle(
     if (typeof record.allowInterruptActiveTasks !== 'boolean') {
       throw new Error('Local Runtime Host setup intent is invalid');
     }
-    return finish({
+    return {
       schemaVersion: 2,
       state: record.state,
       rootPath,
       rootId,
       coordinationRelays: requireAddresses(record.coordinationRelays),
       allowInterruptActiveTasks: record.allowInterruptActiveTasks,
-    });
+    };
   }
   const target = requireServiceTarget(record, rootPath);
   const targetKeys = [
@@ -1336,7 +1205,7 @@ async function readLifecycle(
   ) {
     throw new Error('Local Runtime Host service lifecycle is invalid');
   }
-  if (record.state === 'managed') return finish({ ...target, state: 'managed' });
+  if (record.state === 'managed') return { ...target, state: 'managed' };
   if (typeof record.allowInterruptActiveTasks !== 'boolean') {
     throw new Error('Local Runtime Host service intent is invalid');
   }
@@ -1344,19 +1213,19 @@ async function readLifecycle(
     if (typeof record.peerEnabled !== 'boolean') {
       throw new Error('Local Runtime Host peer intent is invalid');
     }
-    return finish({
+    return {
       ...target,
       state: 'peerChanging',
       peerEnabled: record.peerEnabled,
       coordinationRelays: requireAddresses(record.coordinationRelays),
       allowInterruptActiveTasks: record.allowInterruptActiveTasks,
-    });
+    };
   }
-  return finish({
+  return {
     ...target,
     state: record.state,
     allowInterruptActiveTasks: record.allowInterruptActiveTasks,
-  });
+  };
 }
 
 function assertExactKeys(value: Record<string, unknown>, keys: readonly string[]): void {
diff --git a/apps/desktop/src/main/runtime-host-managed-services.ts b/apps/desktop/src/main/runtime-host-managed-services.ts
index 1c3547d058..28ea264e30 100644
--- a/apps/desktop/src/main/runtime-host-managed-services.ts
+++ b/apps/desktop/src/main/runtime-host-managed-services.ts
@@ -22,7 +22,6 @@ import { mkdir, open, readFile, rename, rm } from "node:fs/promises";
 import { dirname, join } from "node:path";
 import {
   decodePersistedRuntimeHostProfile,
-  migrateRuntimeHostProfileOperatorCommand,
   sameEnvironmentRuntimeHostDeployment,
   sameResolvedRuntimeHostProfileTarget,
   type EnvironmentRuntimeHostProfile,
@@ -32,7 +31,6 @@ import {
 } from "@maka/runtime-host/client";
 import { requireHostRootId } from "@maka/runtime-host/protocol";
 import {
-  createRuntimeHostLegacyPosixOperatorCommand,
   decodeRuntimeHostOperatorCommand,
   type RuntimeHostOperatorCommand,
 } from "@maka/runtime-host/operator";
@@ -47,7 +45,7 @@ const PATH_MAX_BYTES = 4 * 1024;
 export interface DesktopRuntimeHostDeploymentBinding {
   readonly id: string;
   readonly rootPath: string;
-  readonly deploymentId?: string;
+  readonly deploymentId: string;
 }
 
 type ManagedSshRuntimeHostProfile = RemoteRuntimeHostProfile & {
@@ -153,7 +151,6 @@ export function createDesktopRuntimeHostManagedServiceStore(
 ): DesktopRuntimeHostManagedServiceStore {
   return new FileDesktopRuntimeHostManagedServiceStore(
     join(clientDataRoot, "runtime-host-deployments.json"),
-    join(clientDataRoot, "runtime-host-managed-services.json"),
   );
 }
 
@@ -187,11 +184,9 @@ export function sameDesktopRuntimeHostManagedServiceBinding(
 
 class FileDesktopRuntimeHostManagedServiceStore implements DesktopRuntimeHostManagedServiceStore {
   readonly #path: string;
-  readonly #legacyPath: string;
 
-  constructor(path: string, legacyPath: string) {
+  constructor(path: string) {
     this.#path = path;
-    this.#legacyPath = legacyPath;
   }
 
   async read(): Promise<DesktopRuntimeHostManagedServiceDocument> {
@@ -203,28 +198,13 @@ class FileDesktopRuntimeHostManagedServiceStore implements DesktopRuntimeHostMan
     try {
       contents = await readFile(this.#path, "utf8");
     } catch (error) {
-      if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
-      try {
-        contents = await readFile(this.#legacyPath, "utf8");
-      } catch (legacyError) {
-        if ((legacyError as NodeJS.ErrnoException).code === "ENOENT") return emptyDocument();
-        throw legacyError;
-      }
-      const migrated = decodeLegacyDocument(JSON.parse(contents));
-      await writeDocument(this.#path, migrated);
-      await removeLegacyDocument(this.#legacyPath);
-      return migrated;
+      if ((error as NodeJS.ErrnoException).code === "ENOENT") return emptyDocument();
+      throw error;
     }
     if (Buffer.byteLength(contents, "utf8") > DOCUMENT_MAX_BYTES) {
       throw new Error("Runtime Host managed service document is too large");
     }
-    const value: unknown = JSON.parse(contents);
-    const migrated = decodeDocument(value);
-    if ((value as { readonly schemaVersion?: unknown }).schemaVersion === 1) {
-      await writeDocument(this.#path, migrated);
-    }
-    await removeLegacyDocument(this.#legacyPath);
-    return migrated;
+    return decodeDocument(JSON.parse(contents));
   }
 
   save(
@@ -390,9 +370,8 @@ function decodeDocument(
   if (!Array.isArray(record.bindings)) {
     throw new Error("Runtime Host managed service document is invalid");
   }
-  if (record.schemaVersion === 1) return migrateVersionOneDocument(record.bindings);
   if (record.schemaVersion !== SCHEMA_VERSION) {
-    throw new Error("Runtime Host managed service document is invalid");
+    throw new Error("Runtime Host managed service document schema is unsupported");
   }
   if (record.bindings.length > BINDING_COUNT_MAX) {
     throw new Error(
@@ -470,98 +449,16 @@ function decodeDocument(
   });
 }
 
-function migrateVersionOneDocument(bindings: unknown[]): DesktopRuntimeHostManagedServiceDocument {
-  return decodeDocument({
-    schemaVersion: SCHEMA_VERSION,
-    bindings: bindings.map((candidate) => {
-      const profile = migrateRuntimeHostProfileOperatorCommand(
-        (candidate as { readonly profile?: unknown } | null)?.profile,
-      );
-      const decodedProfile = decodePersistedRuntimeHostProfile(profile);
-      const binding = requireExactRecord(
-        candidate,
-        "Runtime Host managed service binding",
-        decodedProfile.kind === "environment"
-          ? ["deployment", "profile", "state"]
-          : ["control", "deployment", "profile", "state"],
-      );
-      if (decodedProfile.kind === "environment") return { ...binding, profile };
-      const control = requireExactRecord(binding.control, "Managed Runtime Host control route", [
-        "kind",
-        "operatorPath",
-      ]);
-      if (control.kind !== "ssh_operator") {
-        throw new Error("Managed Runtime Host control route is invalid");
-      }
-      return {
-        ...binding,
-        profile,
-        control: {
-          kind: "ssh_operator",
-          operator: createRuntimeHostLegacyPosixOperatorCommand(
-            requirePosixOperatorPath(control.operatorPath),
-          ),
-        },
-      };
-    }),
-  });
-}
-
-function decodeLegacyDocument(value: unknown): DesktopRuntimeHostManagedServiceDocument {
-  const record = requireExactRecord(value, "Legacy Runtime Host managed service document", [
-    "schemaVersion",
-    "bindings",
-  ]);
-  if (record.schemaVersion !== 1 || !Array.isArray(record.bindings)) {
-    throw new Error("Legacy Runtime Host managed service document is invalid");
-  }
-  return decodeDocument({
-    schemaVersion: SCHEMA_VERSION,
-    bindings: record.bindings.map((candidate) => {
-      const binding = requireExactRecord(candidate, "Legacy Runtime Host service binding", [
-        "profile",
-        "service",
-        "state",
-      ]);
-      const service = requireExactRecord(binding.service, "Managed Runtime Host service", [
-        "id",
-        "operatorPath",
-        "rootPath",
-      ]);
-      const operatorPath = requirePosixOperatorPath(service.operatorPath);
-      return {
-        profile: migrateRuntimeHostProfileOperatorCommand(binding.profile),
-        deployment: {
-          id: requireHostRootId(service.id),
-          rootPath: requirePath(service.rootPath, "Managed Runtime Host State Root"),
-        },
-        control: {
-          kind: "ssh_operator",
-          operator: createRuntimeHostLegacyPosixOperatorCommand(operatorPath),
-        },
-        state: binding.state,
-      };
-    }),
-  });
-}
-
 function decodeDeployment(value: unknown): DesktopRuntimeHostDeploymentBinding {
-  const hasDeploymentId =
-    value !== null &&
-    typeof value === "object" &&
-    !Array.isArray(value) &&
-    Object.hasOwn(value, "deploymentId");
   const record = requireExactRecord(
     value,
     "Managed Runtime Host deployment",
-    hasDeploymentId ? ["deploymentId", "id", "rootPath"] : ["id", "rootPath"],
+    ["deploymentId", "id", "rootPath"],
   );
   return Object.freeze({
     id: requireHostRootId(record.id),
     rootPath: requirePath(record.rootPath, "Managed Runtime Host State Root"),
-    ...(record.deploymentId === undefined
-      ? {}
-      : { deploymentId: requireDeploymentId(record.deploymentId) }),
+    deploymentId: requireDeploymentId(record.deploymentId),
   });
 }
 
@@ -624,14 +521,6 @@ function requirePath(value: unknown, label: string): string {
   return value;
 }
 
-function requirePosixOperatorPath(value: unknown): string {
-  const path = requirePath(value, "Managed Runtime Host operator path");
-  if (!path.startsWith("/")) {
-    throw new Error("Managed Runtime Host operator path must be absolute");
-  }
-  return path;
-}
-
 function requireExactRecord(
   value: unknown,
   label: string,
@@ -714,13 +603,3 @@ async function writeDocument(
     await rm(temporaryPath, { force: true });
   }
 }
-
-async function removeLegacyDocument(path: string): Promise<void> {
-  try {
-    await rm(path);
-  } catch (error) {
-    if ((error as NodeJS.ErrnoException).code === "ENOENT") return;
-    throw error;
-  }
-  await syncDirectory(dirname(path));
-}
diff --git a/apps/desktop/src/main/runtime-host-management.ts b/apps/desktop/src/main/runtime-host-management.ts
index 7579613490..aac49c8b09 100644
--- a/apps/desktop/src/main/runtime-host-management.ts
+++ b/apps/desktop/src/main/runtime-host-management.ts
@@ -24,7 +24,6 @@ import {
 } from '@maka/runtime-host/client';
 import {
   RUNTIME_HOST_OPERATOR_ACCESS_MANAGEMENT_CAPABILITY,
-  RUNTIME_HOST_OPERATOR_PEER_RELAY_DISCOVERY_CAPABILITY,
   RUNTIME_HOST_OPERATOR_PEER_WEBRTC_STUN_CAPABILITY,
   decodeRuntimeHostWebRtcStunPolicy,
   isProductReleaseVersion,
@@ -197,21 +196,11 @@ export function createDesktopRuntimeHostManagement(input: {
     if (managed.state !== 'active' && managementAction !== 'uninstall') {
       throw new Error('Finish uninstalling this Runtime Host service before managing it');
     }
-    if (
-      managementAction !== 'status' &&
-      managementAction !== 'logs' &&
-      !deployment.deploymentId &&
-      !(managementAction === 'uninstall' && managed.state !== 'active')
-    ) {
-      throw new Error(
-        'Re-onboard this Runtime Host before changing it; its legacy binding has no deployment generation',
-      );
-    }
     const expectedTarget = {
       serviceId: deployment.id,
       rootPath: deployment.rootPath,
       rootId: managed.profile.rootId,
-      ...(deployment.deploymentId ? { deploymentId: deployment.deploymentId } : {}),
+      deploymentId: deployment.deploymentId,
     };
     if (!isDesktopRuntimeHostManagedSshServiceBinding(managed)) {
       if (managementAction !== 'status') {
@@ -375,11 +364,6 @@ export function createDesktopRuntimeHostManagement(input: {
     if (managed.state !== 'active') {
       throw new Error('This Runtime Host profile is not available for managed service changes');
     }
-    if (!managed.deployment.deploymentId) {
-      throw new Error(
-        'Re-onboard this Runtime Host before changing it; its legacy binding has no deployment generation',
-      );
-    }
     return {
       profileId,
       managed,
@@ -461,29 +445,7 @@ export function createDesktopRuntimeHostManagement(input: {
     const webRtcStunAvailable = adaptiveCapability.operatorCapabilities?.includes(
       RUNTIME_HOST_OPERATOR_PEER_WEBRTC_STUN_CAPABILITY,
     ) === true;
-    const legacyCapability = webRtcStunAvailable
-      ? adaptiveCapability
-      : await input.runServiceManagement({
-          destination: target.transport.destination,
-          ...(target.transport.sshPort === undefined
-            ? {}
-            : { sshPort: target.transport.sshPort }),
-          operator: target.managed.control.operator,
-          action: 'status',
-          expectedTarget: target.expectedTarget,
-          capabilityRequest: RUNTIME_HOST_OPERATOR_PEER_RELAY_DISCOVERY_CAPABILITY,
-        });
-    if (legacyCapability.kind === 'error') throw new Error(legacyCapability.error.message);
-    if (legacyCapability.action !== 'status') {
-      throw new Error('Runtime Host returned an unrelated capability result');
-    }
-    return {
-      ...target,
-      available: webRtcStunAvailable || legacyCapability.operatorCapabilities?.includes(
-        RUNTIME_HOST_OPERATOR_PEER_RELAY_DISCOVERY_CAPABILITY,
-      ) === true,
-      webRtcStunAvailable,
-    };
+    return { ...target, available: webRtcStunAvailable };
   };
 
   const unavailablePeerSnapshot = async (
@@ -505,7 +467,7 @@ export function createDesktopRuntimeHostManagement(input: {
   const getDirectPeer = async (
     profileIdValue: unknown,
   ): Promise<DesktopRuntimeHostDirectPeerSnapshot> => {
-    const { profileId, managed, transport, expectedTarget, available, webRtcStunAvailable } =
+    const { profileId, managed, transport, expectedTarget, available } =
       await peerManagementTarget(profileIdValue);
     if (!available) return unavailablePeerSnapshot(profileId);
     const response = await input.runPeerManagement({
@@ -513,7 +475,7 @@ export function createDesktopRuntimeHostManagement(input: {
       ...(transport.sshPort === undefined ? {} : { sshPort: transport.sshPort }),
       operator: managed.control.operator,
       action: 'status',
-      ...(webRtcStunAvailable ? { webRtcStunStatus: true } : {}),
+      webRtcStunStatus: true,
       expectedTarget,
     });
     if (response.kind !== 'result') {
@@ -549,15 +511,11 @@ export function createDesktopRuntimeHostManagement(input: {
       transport,
       expectedTarget,
       available,
-      webRtcStunAvailable,
     } =
       await peerManagementTarget(profileIdValue);
     if (!available) {
       throw new Error('Update this Runtime Host before managing Direct peer access');
     }
-    if (webRtcStunPolicy && !webRtcStunAvailable) {
-      throw new Error('Update this Runtime Host before changing its STUN policy');
-    }
     const peerProfile = await input.profiles.resolveManagedDirectPeerProfile(profileId);
     if (peerProfile.enabled) {
       throw new Error('Disable the Direct peer profile before changing its listener');
@@ -571,7 +529,7 @@ export function createDesktopRuntimeHostManagement(input: {
       ...(enabledValue ? { coordinationRelays } : {}),
       ...(enabledValue ? { automaticRelayDiscovery: automaticRelayDiscoveryValue } : {}),
       ...(enabledValue && webRtcStunPolicy ? { webRtcStunPolicy } : {}),
-      ...(webRtcStunAvailable ? { webRtcStunStatus: true } : {}),
+      webRtcStunStatus: true,
       expectedTarget,
     });
     if (response.kind !== 'result') {
diff --git a/apps/desktop/src/main/runtime-host-native-capabilities.ts b/apps/desktop/src/main/runtime-host-native-capabilities.ts
index fdaf0fe129..c93e945788 100644
--- a/apps/desktop/src/main/runtime-host-native-capabilities.ts
+++ b/apps/desktop/src/main/runtime-host-native-capabilities.ts
@@ -19,7 +19,7 @@
 
 import { Buffer } from "node:buffer";
 import type { ComputerUseToolSet } from '@maka/runtime/computer-use-tools';
-import type { MakaTool } from '@maka/runtime/tool-runtime';
+import type { MakaTool, SessionTool, SessionToolContext } from '@maka/runtime/tool-runtime';
 import {
   createOAuthPresentationClientProvider,
   type ClientCapabilityProvider,
@@ -60,11 +60,17 @@ export interface DesktopIdentifiedCapabilityTool {
   readonly toolName: string;
 }
 
+/** Declares that this implementation needs a Session, not an Agent Turn. */
+export interface DesktopSessionCapabilityTool {
+  readonly context: 'session';
+  readonly tool: SessionTool;
+}
+
 export interface DesktopCapabilityGroup {
   readonly offerId: string;
   readonly label: string;
   readonly description: string;
-  readonly tools: readonly (MakaTool | DesktopIdentifiedCapabilityTool)[];
+  readonly tools: readonly (MakaTool | DesktopIdentifiedCapabilityTool | DesktopSessionCapabilityTool)[];
   /**
    * Marks a dynamically sourced group: tools the decoder rejects are omitted
    * with a diagnostic, the group may be chunked past the single-offer tool
@@ -74,10 +80,9 @@ export interface DesktopCapabilityGroup {
   readonly dynamic?: boolean;
 }
 
-interface PreparedDesktopCapabilityTool {
-  readonly tool: MakaTool;
+type PreparedDesktopCapabilityTool = NativeToolBinding & {
   readonly descriptor: ClientCapabilityToolDescriptor;
-}
+};
 
 interface PreparedDesktopCapabilityGroup {
   readonly offerId: string;
@@ -87,7 +92,9 @@ interface PreparedDesktopCapabilityGroup {
   readonly dynamic?: boolean;
 }
 
-type NativeToolBinding = Pick<PreparedDesktopCapabilityTool, "tool">;
+type NativeToolBinding =
+  | { readonly kind: 'session'; readonly tool: SessionTool }
+  | { readonly kind: 'agent'; readonly tool: MakaTool };
 
 type DesktopToolModelOutput = Awaited<
   ReturnType<NonNullable<MakaTool["toModelOutput"]>>
@@ -98,7 +105,7 @@ type DesktopToolContentPart = Extract<
 >["value"][number];
 
 export interface DesktopNativeCapabilityProviderInput {
-  readonly browserTools: readonly MakaTool[];
+  readonly browserTools: readonly SessionTool[];
   readonly resolveBrowserUrl: (input: {
     readonly sessionId: string;
     readonly toolName: string;
@@ -211,6 +218,7 @@ export function createDesktopNativeCapabilityProvider(
     offers: () => offers,
     services: () => [...serviceOffers],
     call: (frame, options) => {
+      if (frame.source.sessionId === null) throw new Error('This native tool requires a Session');
       if (closed)
         throw new Error("Desktop native capability provider is closed");
       if (providerOptions.isTargetValid?.() === false) {
@@ -234,7 +242,7 @@ export function createDesktopNativeCapabilityProvider(
         () => undefined,
       );
       activeInvocations.set(invocation, {
-        sessionId: frame.sessionId,
+        sessionId: frame.source.sessionId,
         settled,
       });
       void settled.finally(() => activeInvocations.delete(invocation));
@@ -356,7 +364,7 @@ function capabilityGroups(
             label: "Browser",
             description:
               "Operate the embedded browser owned by this Desktop client.",
-            tools: input.browserTools,
+            tools: input.browserTools.map(tool => ({ context: 'session' as const, tool })),
           },
         ]
       : []),
@@ -384,6 +392,11 @@ async function invokeNativeTool(
   invocation: AbortController,
   usedSessionIds: Set<string>,
 ): Promise<ClientCapabilityCallResult> {
+  const source = frame.source;
+  if (source.sessionId === null) throw new Error('This native tool requires a Session');
+  if (binding.kind === 'agent' && source.kind !== 'agent') {
+    throw new Error('This tool requires an Agent invocation');
+  }
   const hostPathAccess = providerOptions.hostPathAccess ?? "cwd";
   if (hostPathAccess === "none" && frame.cwd !== undefined) {
     throw new Error("Desktop native capability does not accept a Host path");
@@ -400,8 +413,8 @@ async function invokeNativeTool(
   signal.throwIfAborted();
   const sessionId =
     frame.offerId === BROWSER_OFFER_ID || frame.offerId === COMPUTER_USE_OFFER_ID
-      ? providerOptions.nativeSessionId?.(frame.sessionId) ?? frame.sessionId
-      : frame.sessionId;
+      ? providerOptions.nativeSessionId?.(source.sessionId) ?? source.sessionId
+      : source.sessionId;
   const admissionEvidence =
     frame.offerId === BROWSER_OFFER_ID
       ? {
@@ -429,22 +442,25 @@ async function invokeNativeTool(
     }
   }
   signal.throwIfAborted();
-  usedSessionIds.add(frame.sessionId);
-  providerOptions.onSessionUsed?.(frame.sessionId);
-  if (frame.offerId === COMPUTER_USE_OFFER_ID || frame.offerId === "desktop_workhub") {
-    providerOptions.onDesktopInteractionTurnUsed?.(frame.sessionId, frame.turnId);
+  usedSessionIds.add(source.sessionId);
+  providerOptions.onSessionUsed?.(source.sessionId);
+  if (source.kind === 'agent' && (frame.offerId === COMPUTER_USE_OFFER_ID || frame.offerId === "desktop_workhub")) {
+    providerOptions.onDesktopInteractionTurnUsed?.(source.sessionId, source.turnId);
   }
-  const execute = () =>
-    binding.tool.impl(args, {
+  const context: SessionToolContext = {
       sessionId,
-      turnId: frame.turnId,
       cwd,
       toolCallId: frame.toolCallId,
       abortSignal: signal,
       emitOutput() {},
       requestUserForm: options.requestInteraction,
       ...(options.progress ? { emitProgress: options.progress } : {}),
-    });
+  };
+  const execute = () => {
+    if (binding.kind === 'session') return binding.tool.impl(args, context);
+    if (source.kind !== 'agent') throw new Error('This tool requires an Agent invocation');
+    return binding.tool.impl(args, { ...context, turnId: source.turnId });
+  };
   const output = await (admissionEvidence.kind === "browser_url"
     ? withBrowserOriginAdmission(
         { sessionId, url: admissionEvidence.url },
@@ -501,7 +517,10 @@ function prepareCapabilityGroups(
 ): PreparedDesktopCapabilityGroup[] {
   return groups.flatMap((group) => {
     const tools = group.tools.flatMap((entry): PreparedDesktopCapabilityTool[] => {
-      const tool = isIdentifiedEntry(entry) ? entry.tool : entry;
+      const tool = 'tool' in entry ? entry.tool : entry;
+      const binding: NativeToolBinding = 'context' in entry
+        ? { kind: 'session', tool: entry.tool }
+        : { kind: 'agent', tool };
       const identity = isIdentifiedEntry(entry)
         ? { serverId: entry.serverId, toolName: entry.toolName }
         : undefined;
@@ -521,7 +540,7 @@ function prepareCapabilityGroups(
         );
         return [];
       }
-      return [{ tool, descriptor }];
+      return [{ ...binding, descriptor }];
     });
     if (group.dynamic && tools.length === 0) return [];
     return chunkPreparedGroup({ ...group, tools });
@@ -628,9 +647,9 @@ function manifestFitsBudget(
 }
 
 function isIdentifiedEntry(
-  entry: MakaTool | DesktopIdentifiedCapabilityTool,
+  entry: MakaTool | DesktopIdentifiedCapabilityTool | DesktopSessionCapabilityTool,
 ): entry is DesktopIdentifiedCapabilityTool {
-  return 'tool' in entry;
+  return 'serverId' in entry && 'toolName' in entry;
 }
 
 function capabilityToolDescriptor(
@@ -701,7 +720,8 @@ function indexBindings(
 ): Map<string, NativeToolBinding> {
   const bindings = new Map<string, NativeToolBinding>();
   for (const group of groups) {
-    for (const { tool, descriptor } of group.tools) {
+    for (const binding of group.tools) {
+      const { tool, descriptor } = binding;
       const key = bindingKey({
         offerId: group.offerId,
         serverId: descriptor.serverId,
@@ -712,7 +732,7 @@ function indexBindings(
           `Duplicate Desktop native capability tool: ${group.offerId}/${tool.name}`,
         );
       }
-      bindings.set(key, { tool });
+      bindings.set(key, binding);
     }
   }
   return bindings;
diff --git a/apps/desktop/src/main/runtime-host-onboarding.ts b/apps/desktop/src/main/runtime-host-onboarding.ts
index 27ed4b0787..68177125bc 100644
--- a/apps/desktop/src/main/runtime-host-onboarding.ts
+++ b/apps/desktop/src/main/runtime-host-onboarding.ts
@@ -19,28 +19,12 @@
 
 import { randomUUID } from 'node:crypto';
 import type { IpcMain } from 'electron';
-import {
-  parseRuntimeHostSetupEndpoint,
-  type RuntimeHostNodeOperatorCommand,
-  type RuntimeHostNativeOperatorCommand,
-  type RuntimeHostOperatorCommand,
-  type RuntimeHostSetupPhase,
-} from '@maka/runtime-host/operator';
 import type {
   DesktopRuntimeHostOnboardingInput,
   DesktopRuntimeHostOnboardingSnapshot,
 } from '../preload/bridge-contract.js';
 import type { DesktopRuntimeHostProfileService } from './runtime-host-profile-service.js';
-import {
-  runtimeHostPeerTargetFromPlatform,
-  type RuntimeHostTargetIdentity,
-  type DesktopRuntimeHostSshSetupInput,
-} from './runtime-host-ssh-terminal.js';
-import type { DesktopRuntimeHostWslSetupInput } from './runtime-host-wsl-controller.js';
-import type {
-  DesktopRuntimeHostDevelopmentPeerTarget,
-  DesktopRuntimeHostSetupPackage,
-} from './runtime-host-setup-package.js';
+import type { RuntimeHostTargetIdentity } from './runtime-host-target.js';
 import { requireProjectDirectoryRoots } from '../shared/runtime-host-project-directory-policy.js';
 import type { NativeRuntimeHostPackage } from './native-runtime-host-setup.js';
 import type { NativeSetupInput, NativeSetupResult } from './native-runtime-host-installer.js';
@@ -53,8 +37,7 @@ type OnboardingState = DesktopRuntimeHostOnboardingSnapshot extends infer Snapsh
   : never;
 
 export function createDesktopRuntimeHostOnboarding(input: {
-  /** Production uses native packages; the legacy route remains for TS fixtures. */
-  readonly nativeSetup?: {
+  readonly nativeSetup: {
     resolvePackage(identity: RuntimeHostTargetIdentity, signal?: AbortSignal, budget?: NativeHostBudget, onProgress?: (progress: import('../shared/native-runtime-host-management.js').NativeHostProgress) => void): Promise<NativeRuntimeHostPackage>;
     ssh(input: NativeSetupInput & { readonly destination: string; readonly sshPort?: number }, onCommit: () => void): Promise<NativeSetupResult>;
     wsl(input: NativeSetupInput & { readonly distribution: string }, onCommit: () => void): Promise<NativeSetupResult>;
@@ -65,46 +48,17 @@ export function createDesktopRuntimeHostOnboarding(input: {
     DesktopRuntimeHostProfileService,
     'addEnvironmentAndEnable' | 'addAndEnableVerified'
   >;
-  readonly runSetup: (
-    input: DesktopRuntimeHostSshSetupInput,
-    onProgress: (frame: { readonly phase: RuntimeHostSetupPhase }) => void,
-    onComplete: () => void,
-  ) => Promise<{
-    readonly rootId: string;
-    readonly rootPath: string;
-    readonly serviceId: string;
-    readonly deploymentId: string;
-    readonly operator: RuntimeHostOperatorCommand;
-    readonly endpoint: string;
-    readonly credential: string;
-  }>;
-  readonly runWslSetup: (
-    input: DesktopRuntimeHostWslSetupInput,
-    onProgress: (frame: { readonly phase: RuntimeHostSetupPhase }) => void,
-    onComplete: () => void,
-  ) => Promise<{
-    readonly rootId: string;
-    readonly rootPath: string;
-    readonly serviceId: string;
-    readonly deploymentId: string;
-    readonly operator: RuntimeHostNodeOperatorCommand<'posix'> | RuntimeHostNativeOperatorCommand<'posix'>;
-  }>;
   readonly listWslDistributions: () => Promise<readonly string[]>;
   readonly resolveWslTargetIdentity: (input: {
     readonly distribution: string;
     readonly signal?: AbortSignal;
   }) => Promise<Extract<RuntimeHostTargetIdentity, { platform: 'linux' }>>;
   readonly send: (snapshot: DesktopRuntimeHostOnboardingSnapshot) => void;
-  readonly setupPackageMode: 'published' | 'development';
   readonly resolveSshTargetIdentity: (input: {
     readonly destination: string;
     readonly sshPort?: number;
     readonly signal?: AbortSignal;
   }) => Promise<RuntimeHostTargetIdentity>;
-  readonly resolveSetupPackage: (
-    peerTarget: DesktopRuntimeHostDevelopmentPeerTarget,
-    signal?: AbortSignal,
-  ) => DesktopRuntimeHostSetupPackage | Promise<DesktopRuntimeHostSetupPackage>;
 }): { close(): Promise<void> } {
   let revision = 0;
   let snapshot: DesktopRuntimeHostOnboardingSnapshot = { kind: 'idle', revision };
@@ -150,107 +104,8 @@ export function createDesktopRuntimeHostOnboarding(input: {
     signal: AbortSignal,
   ): Promise<DesktopRuntimeHostOnboardingSnapshot> => {
     try {
-      if (input.nativeSetup) {
-        const budget = new NativeHostBudget(180_000, signal);
-        return await budget.wait(runNative(request, signal, input.nativeSetup, budget), 'Host setup');
-      }
-      if (request.kind === 'wsl') {
-        publish({ kind: 'running', phase: 'connecting_wsl' });
-        const target = await input.resolveWslTargetIdentity({ distribution: request.distribution, signal });
-        publish({ kind: 'running', phase: 'preparing_cli' });
-        const peerTarget = input.setupPackageMode === 'development'
-          ? runtimeHostPeerTargetFromPlatform(target.platform, target.architecture) : 'none';
-        const setupPackage = await input.resolveSetupPackage(peerTarget, signal);
-        return await runWsl(request, setupPackage, signal);
-      }
-      const targetIdentity = await resolveSshTargetIdentity(request, signal);
-      const peerTarget = input.setupPackageMode === 'development'
-        ? runtimeHostPeerTargetFromPlatform(targetIdentity.platform, targetIdentity.architecture)
-        : 'none';
-      const setupPackage = await input.resolveSetupPackage(
-        peerTarget,
-        signal,
-      );
-      const lifecycle = setupPackage.kind === 'npm' ? 'on_demand' : 'supervised';
-      signal.throwIfAborted();
-      publish({ kind: 'running', phase: 'connecting_ssh' });
-      let commitStarted = false;
-      const beginCommit = () => {
-        if (commitStarted) return;
-        commitStarted = true;
-        if (active) active.cancellable = false;
-        publish({
-          kind: 'running',
-          phase: 'connecting_host',
-        });
-      };
-      const complete = await input.runSetup(
-        {
-          destination: request.destination,
-          ...(request.sshPort === undefined ? {} : { sshPort: request.sshPort }),
-          setupPackage,
-          remotePlatform: targetIdentity.platform === 'win32' ? 'win32' : 'posix',
-          lifecycle,
-          principalId: `desktop:${input.clientInstanceId}`,
-          ...(request.projectDirectoryRoots
-            ? { projectDirectoryRoots: request.projectDirectoryRoots }
-            : {}),
-          signal,
-        },
-        (progress) => {
-          if (commitStarted) return;
-          publish({
-            kind: 'running',
-            phase: progress.phase,
-          });
-        },
-        beginCommit,
-      );
-      beginCommit();
-      const endpoint = parseRuntimeHostSetupEndpoint(complete.endpoint);
-      if (!endpoint) throw new Error('Remote Maka setup returned an invalid endpoint');
-      const profileId = `remote-${randomUUID()}`;
-      const profileName = request.name?.trim() || request.destination;
-      const connected = await input.profiles.addAndEnableVerified({
-        profile: {
-          id: profileId,
-          name: profileName,
-          kind: 'remote',
-          rootId: complete.rootId,
-          transport: {
-            kind: 'ssh',
-            destination: request.destination,
-            ...(request.sshPort === undefined ? {} : { sshPort: request.sshPort }),
-            ...(lifecycle === 'on_demand'
-              ? {
-                  activation: {
-                    kind: 'ssh_operator' as const,
-                    operator: complete.operator,
-                  },
-                }
-              : {
-                  remotePort: endpoint.port,
-                  websocketPath: endpoint.websocketPath,
-                }),
-          },
-        },
-        credential: complete.credential,
-        managedService: {
-          deployment: {
-            id: complete.serviceId,
-            rootPath: complete.rootPath,
-            deploymentId: complete.deploymentId,
-          },
-          control: {
-            kind: 'ssh_operator',
-            operator: complete.operator,
-          },
-        },
-      });
-      return publish({
-        kind: 'complete',
-        profileId: connected.profileId,
-      });
+      const budget = new NativeHostBudget(180_000, signal);
+      return await budget.wait(runNative(request, signal, input.nativeSetup, budget), 'Host setup');
     } catch (error) {
       if (signal.aborted) return publish({ kind: 'idle' });
       return publish({
@@ -263,7 +118,7 @@ export function createDesktopRuntimeHostOnboarding(input: {
   const runNative = async (
     request: DesktopRuntimeHostOnboardingInput,
     signal: AbortSignal,
-    native: NonNullable<typeof input.nativeSetup>,
+    native: typeof input.nativeSetup,
     budget: NativeHostBudget,
   ): Promise<DesktopRuntimeHostOnboardingSnapshot> => {
     publish({ kind: 'running', phase: request.kind === 'wsl' ? 'connecting_wsl' : 'connecting_ssh' });
@@ -316,71 +171,6 @@ export function createDesktopRuntimeHostOnboarding(input: {
     return publish({ kind: 'complete', profileId: result.profileId });
   };
 
-  const resolveSshTargetIdentity = async (
-    request: Extract<DesktopRuntimeHostOnboardingInput, { readonly kind: 'ssh' }>,
-    signal: AbortSignal,
-  ): Promise<RuntimeHostTargetIdentity> => {
-    publish({ kind: 'running', phase: 'connecting_ssh' });
-    const identity = await input.resolveSshTargetIdentity({
-      destination: request.destination,
-      ...(request.sshPort === undefined ? {} : { sshPort: request.sshPort }),
-      signal,
-    });
-    publish({ kind: 'running', phase: 'preparing_cli' });
-    return identity;
-  };
-
-  const runWsl = async (
-    request: Extract<DesktopRuntimeHostOnboardingInput, { readonly kind: 'wsl' }>,
-    setupPackage: DesktopRuntimeHostSetupPackage,
-    signal: AbortSignal,
-  ): Promise<DesktopRuntimeHostOnboardingSnapshot> => {
-    publish({ kind: 'running', phase: 'connecting_wsl' });
-    let commitStarted = false;
-    const beginCommit = () => {
-      if (commitStarted) return;
-      commitStarted = true;
-      if (active) active.cancellable = false;
-      publish({ kind: 'running', phase: 'connecting_host' });
-    };
-    const complete = await input.runWslSetup(
-      {
-        distribution: request.distribution,
-        setupPackage,
-        principalId: `desktop:${input.clientInstanceId}`,
-        ...(request.projectDirectoryRoots
-          ? { projectDirectoryRoots: request.projectDirectoryRoots }
-          : {}),
-        signal,
-      },
-      (progress) => {
-        if (!commitStarted) publish({ kind: 'running', phase: progress.phase });
-      },
-      beginCommit,
-    );
-    beginCommit();
-    const profileId = `environment-${randomUUID()}`;
-    const profile = {
-      id: profileId,
-      name: request.name?.trim() || request.distribution,
-      kind: 'environment' as const,
-      provider: { kind: 'wsl' as const, distribution: request.distribution },
-      rootId: complete.rootId,
-      operator: complete.operator,
-    };
-    const connected = await input.profiles.addEnvironmentAndEnable({
-      profile,
-      managedService: {
-        deployment: {
-          id: complete.serviceId,
-          rootPath: complete.rootPath,
-          deploymentId: complete.deploymentId,
-        },
-      },
-    });
-    return publish({ kind: 'complete', profileId: connected.profileId });
-  };
-
   const channels = [
     'runtime-host-onboarding:getSnapshot',
     'runtime-host-onboarding:start',
diff --git a/apps/desktop/src/main/runtime-host-pairing-journal.ts b/apps/desktop/src/main/runtime-host-pairing-journal.ts
index d5868e4eda..38169122e3 100644
--- a/apps/desktop/src/main/runtime-host-pairing-journal.ts
+++ b/apps/desktop/src/main/runtime-host-pairing-journal.ts
@@ -19,7 +19,6 @@
 
 import {
   decodeRemoteRuntimeHostProfile,
-  migrateRuntimeHostProfileOperatorCommand,
   RUNTIME_HOST_ACCESS_CREDENTIAL_MAX_BYTES,
   sameResolvedRuntimeHostProfileTarget,
   type RemoteRuntimeHostProfile,
@@ -187,9 +186,7 @@ function decodePairingTarget(
 ): DesktopRuntimeHostPairingIntent['target'] {
   const record = requireExactRecord(value, ['profile', 'credential']);
   return {
-    profile: decodeRemoteRuntimeHostProfile(
-      migrateRuntimeHostProfileOperatorCommand(record.profile),
-    ),
+    profile: decodeRemoteRuntimeHostProfile(record.profile),
     credential: requireCredential(record.credential),
   };
 }
diff --git a/apps/desktop/src/main/runtime-host-profile-service.ts b/apps/desktop/src/main/runtime-host-profile-service.ts
index 7926c2112f..9d3a62ea72 100644
--- a/apps/desktop/src/main/runtime-host-profile-service.ts
+++ b/apps/desktop/src/main/runtime-host-profile-service.ts
@@ -18,7 +18,7 @@
  */
 
 import { createHash, randomUUID } from "node:crypto";
-import { lstat, open, readFile, rename, rm, rmdir } from "node:fs/promises";
+import { open, readFile, rename, rm } from "node:fs/promises";
 import { dirname, join } from "node:path";
 import { NativeHostBudget } from './native-runtime-host-operation.js';
 import {
@@ -44,7 +44,7 @@ import {
 import { runtimeHostAccessCredentialFingerprint } from "@maka/runtime-host/operator";
 import type { HostPeerEndpoint } from '@maka/runtime-host/protocol';
 import type { CredentialStore } from "@maka/storage/credential-store";
-import { withFileUpdateLock } from "@maka/storage/file-update-lock";
+import { withProcessLifetimeFileUpdateLock } from "@maka/storage/process-lifetime-file-update-lock";
 import type {
   DesktopRuntimeHostProfileAddInput,
   DesktopRuntimeHostProfileAddResult,
@@ -170,12 +170,6 @@ export async function resolveDesktopRuntimeHostStartup(
     readPreferences?: () => Promise<DesktopRuntimeHostPreferences>;
   } = {},
 ): Promise<DesktopRuntimeHostStartup> {
-  // The Desktop single-instance lock is held before startup opens any stores.
-  // Reclaim only legacy directory markers here, before concurrent readers can
-  // start; current deployment writers use a process-lifetime OS lease.
-  await recoverAbandonedDesktopFileUpdateLock(
-    join(clientDataRoot, "runtime-host-deployments.json"),
-  );
   const preferencesPath = join(clientDataRoot, PREFERENCES_FILE);
   let preferences: DesktopRuntimeHostPreferences;
   let preferencesReadFailure: Error | undefined;
@@ -227,25 +221,6 @@ export async function resolveDesktopRuntimeHostStartup(
       unavailable,
     };
   }
-  const obsoleteProfileIds = new Set(
-    document.profiles.flatMap((profile) =>
-      profile.kind === 'remote' && profile.access === 'session_guest' ? [profile.id] : [],
-    ),
-  );
-  if (obsoleteProfileIds.size > 0) {
-    await recoverAbandonedDesktopFileUpdateLock(join(clientDataRoot, PROFILE_FILE));
-  }
-  for (const profileId of obsoleteProfileIds) await catalog.remove(profileId);
-  if (obsoleteProfileIds.size > 0) document = await catalog.read();
-  if (!pairingReadFailure) {
-    const retained = pairingIntents.filter(
-      (intent) => intent.target.profile.access !== 'session_guest',
-    );
-    if (retained.length !== pairingIntents.length) {
-      await writeDesktopRuntimeHostPairingIntents(credentialStore, retained);
-      pairingIntents = retained;
-    }
-  }
   const profileIds = new Set(document.profiles.map((profile) => profile.id));
   const defaultProfile = document.profiles.find(
     (profile) => profile.id === preferences.defaultProfileId,
@@ -354,7 +329,6 @@ export function createDesktopRuntimeHostProfileService(input: {
 
   const mutateProfiles = <T>(operation: () => Promise<T>): Promise<T> =>
     mutate(async () => {
-      await recoverAbandonedDesktopFileUpdateLock(profilePath);
       assertPreferencesWritable();
       if (pairingReadFailure) {
         throw new Error(
@@ -429,7 +403,7 @@ export function createDesktopRuntimeHostProfileService(input: {
   };
 
   const persist = async (next: DesktopRuntimeHostPreferences): Promise<void> => {
-    await withFileUpdateLock(profilePath, () =>
+    await withProcessLifetimeFileUpdateLock(profilePath, () =>
       writeRuntimeHostPreferences(preferencesPath, next),
     );
     preferences = next;
@@ -1128,14 +1102,6 @@ export function createDesktopRuntimeHostProfileService(input: {
     },
     markManagedServiceUninstalling(expected) {
       return mutateProfiles(async () => {
-        if (
-          !expected.deployment.deploymentId &&
-          expected.state !== 'uninstalling'
-        ) {
-          throw new Error(
-            'Re-onboard this Runtime Host before uninstalling it; its legacy binding has no deployment generation',
-          );
-        }
         requirePairingComplete(expected.profile.id);
         const document = await catalog.read();
         const current = document.profiles.find(
@@ -1488,27 +1454,6 @@ function assertRootIsNotEnabled(
   }
 }
 
-async function recoverAbandonedDesktopFileUpdateLock(targetPath: string): Promise<void> {
-  const lockPath = `${targetPath}.lock`;
-  const lock = await lstat(lockPath).catch((error: unknown) => {
-    if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;
-    throw error;
-  });
-  if (
-    !lock ||
-    !lock.isDirectory() ||
-    lock.isSymbolicLink()
-  ) {
-    return;
-  }
-  // Electron's single-instance authority excludes another Desktop writer for
-  // this client data root. Legacy directory locks contain no owner identity,
-  // so only reclaim an old, empty marker; unexpected contents still fail loud.
-  await rmdir(lockPath).catch((error: unknown) => {
-    if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
-  });
-}
-
 async function persistIfCurrentTarget(
   catalog: RuntimeHostProfileCatalog,
   profilePath: string,
@@ -1516,7 +1461,7 @@ async function persistIfCurrentTarget(
   target: ResolvedRuntimeHostProfile,
   preferences: DesktopRuntimeHostPreferences,
 ): Promise<void> {
-  await withFileUpdateLock(profilePath, async () => {
+  await withProcessLifetimeFileUpdateLock(profilePath, async () => {
     const current = await catalog.resolve(target.profile.id);
     if (!sameResolvedRuntimeHostProfileTarget(current, target)) {
       throw new Error("Runtime Host profile changed while it was being enabled");
@@ -1637,37 +1582,14 @@ async function readRuntimeHostPreferences(path: string): Promise<DesktopRuntimeH
     value = JSON.parse(await readFile(path, "utf8"));
   } catch (error) {
     if ((error as NodeJS.ErrnoException).code === "ENOENT") return defaultPreferences();
-    if (!(error instanceof SyntaxError)) throw error;
-    console.error("[runtime-host] preferences are invalid; using Local defaults");
-    return defaultPreferences();
-  }
-  if (isLegacySelection(value)) {
-    const migrated = {
-      schemaVersion: PREFERENCES_SCHEMA_VERSION,
-      defaultProfileId: value.profileId,
-      enabledRemoteProfileIds:
-        value.profileId === LOCAL_RUNTIME_HOST_PROFILE.id ? [] : [value.profileId],
-    } as const;
-    await writeRuntimeHostPreferences(path, migrated);
-    return migrated;
+    throw error;
   }
   if (!isRuntimeHostPreferences(value)) {
-    console.error("[runtime-host] preferences are invalid; using Local defaults");
-    return defaultPreferences();
+    throw new Error("Runtime Host preferences format is invalid or unsupported");
   }
   return value;
 }
 
-function isLegacySelection(value: unknown): value is { schemaVersion: 1; profileId: string } {
-  return Boolean(
-    value &&
-    typeof value === "object" &&
-    !Array.isArray(value) &&
-    (value as { schemaVersion?: unknown }).schemaVersion === 1 &&
-    typeof (value as { profileId?: unknown }).profileId === "string",
-  );
-}
-
 function isRuntimeHostPreferences(value: unknown): value is DesktopRuntimeHostPreferences {
   if (!value || typeof value !== "object" || Array.isArray(value)) return false;
   const input = value as Partial<DesktopRuntimeHostPreferences>;
diff --git a/apps/desktop/src/main/runtime-host-session-execution-ipc-main.ts b/apps/desktop/src/main/runtime-host-session-execution-ipc-main.ts
index 8f9ff5ff60..c0a6c0fba3 100644
--- a/apps/desktop/src/main/runtime-host-session-execution-ipc-main.ts
+++ b/apps/desktop/src/main/runtime-host-session-execution-ipc-main.ts
@@ -129,7 +129,6 @@ type RuntimeHostSessionExecutionClient = Pick<
 >;
 
 /** No Skill was named, so the Host resolved none. */
-const EMPTY_SKILL_INVOCATION = { loaded: [], failed: [], receipts: [] } as const;
 const DESKTOP_MESSAGE_QUERY_MAX_ENTRIES = 4_096;
 
 async function submitMessageWithReconnect(
@@ -429,7 +428,7 @@ export function registerRuntimeHostSessionExecutionIpc(
         command.displayText ??
         (command.text.trim().length > 0
           ? command.text
-          : (command.skillIds ?? []).map((id) => `/skill:${id}`).join(" "));
+          : Object.values(command.inputSelections ?? {}).flat().join(" "));
       const inlineReferences = mergeWorkspaceFileInlineReferences({
         displayText,
         workspaceFileReferences: command.workspaceFileReferences,
@@ -455,7 +454,7 @@ export function registerRuntimeHostSessionExecutionIpc(
           ...(command.quotes ? { quotes: command.quotes } : {}),
           inlineReferences,
         },
-        ...((command.skillIds?.length ?? 0) > 0 ? { skillIds: command.skillIds } : {}),
+        ...(command.inputSelections ? { inputSelections: command.inputSelections } : {}),
         ...(command.turnOrchestration
           ? { turnOrchestration: command.turnOrchestration }
           : {}),
@@ -465,14 +464,15 @@ export function registerRuntimeHostSessionExecutionIpc(
           ok: false as const,
           reason: 'outcome_unknown' as const,
           messageId,
-          skillInvocation: EMPTY_SKILL_INVOCATION,
+          preparation: [],
         };
       }
       if (submitted.disposition === "blocked") {
         return {
           ok: false as const,
-          reason: "skill_invocation_failed" as const,
-          skillInvocation: submitted.skillInvocation,
+          reason: "input_preparation_failed" as const,
+          message: submitted.message,
+          preparation: submitted.preparation,
         };
       }
       if (submitted.disposition === "turn_started") {
@@ -484,7 +484,7 @@ export function registerRuntimeHostSessionExecutionIpc(
           turnId: submitted.turnId,
           attachments,
           inlineReferences,
-          skillInvocation: submitted.skillInvocation,
+          preparation: submitted.preparation,
         };
       }
       // The sending surface believed this Session idle; nudge it to refresh so
@@ -497,7 +497,7 @@ export function registerRuntimeHostSessionExecutionIpc(
         ...(sideConversation ? { messageId } : {}),
         attachments,
         inlineReferences,
-        skillInvocation: submitted.skillInvocation,
+        preparation: submitted.preparation,
       };
     },
   );
@@ -532,7 +532,7 @@ export function registerRuntimeHostSessionExecutionIpc(
         command.displayText ??
         (command.text.trim().length > 0
           ? command.text
-          : (command.skillIds ?? []).map((id) => `/skill:${id}`).join(" "));
+          : Object.values(command.inputSelections ?? {}).flat().join(" "));
       const inlineReferences = mergeWorkspaceFileInlineReferences({
         displayText,
         workspaceFileReferences: command.workspaceFileReferences,
@@ -555,7 +555,7 @@ export function registerRuntimeHostSessionExecutionIpc(
           ...(command.quotes ? { quotes: command.quotes } : {}),
           inlineReferences,
         },
-        ...((command.skillIds?.length ?? 0) > 0 ? { skillIds: command.skillIds } : {}),
+        ...(command.inputSelections ? { inputSelections: command.inputSelections } : {}),
         ...(command.turnOrchestration
           ? { turnOrchestration: command.turnOrchestration }
           : {}),
@@ -564,8 +564,9 @@ export function registerRuntimeHostSessionExecutionIpc(
       if (result.disposition === 'blocked') {
         return {
           ok: false as const,
-          reason: 'skill_invocation_failed' as const,
-          skillInvocation: result.skillInvocation,
+          reason: 'input_preparation_failed' as const,
+          message: result.message,
+          preparation: result.preparation,
         };
       }
       if (result.disposition === "turn_started") {
@@ -578,7 +579,7 @@ export function registerRuntimeHostSessionExecutionIpc(
           turnId: result.turnId,
           attachments,
           inlineReferences,
-          skillInvocation: result.skillInvocation,
+          preparation: result.preparation,
         };
       }
       // The submitting surface believed this Session idle when it steered;
@@ -589,7 +590,7 @@ export function registerRuntimeHostSessionExecutionIpc(
         disposition: result.disposition,
         attachments,
         inlineReferences,
-        skillInvocation: result.skillInvocation,
+        preparation: result.preparation,
       };
     },
   );
diff --git a/apps/desktop/src/main/runtime-host-ssh-terminal.ts b/apps/desktop/src/main/runtime-host-ssh-terminal.ts
index 14fe5c4347..1a0bda207f 100644
--- a/apps/desktop/src/main/runtime-host-ssh-terminal.ts
+++ b/apps/desktop/src/main/runtime-host-ssh-terminal.ts
@@ -42,7 +42,6 @@ import {
   decodeRuntimeHostPeerManagementFrame,
   decodeRuntimeHostPeerMeshManagementFrame,
   decodeRuntimeHostServiceManagementFrame,
-  decodeRuntimeHostSetupFrame,
   RUNTIME_HOST_ACTIVATION_FRAME_MAX_BYTES,
   RUNTIME_HOST_ACTIVATION_FRAME_PREFIX,
   RUNTIME_HOST_ACCESS_MANAGEMENT_FRAME_PREFIX,
@@ -54,7 +53,6 @@ import {
   RUNTIME_HOST_PEER_MESH_MANAGEMENT_FRAME_MAX_BYTES,
   RUNTIME_HOST_PEER_MESH_MANAGEMENT_FRAME_PREFIX,
   RUNTIME_HOST_SERVICE_MANAGEMENT_FRAME_PREFIX,
-  RUNTIME_HOST_SETUP_FRAME_PREFIX,
   RUNTIME_HOST_SETUP_SOURCE_PACKAGE_INTEGRITY_ENV,
   type RuntimeHostAccessManagementFrame,
   type RuntimeHostActivationResult,
@@ -70,7 +68,6 @@ import {
   type RuntimeHostServiceManagementAction,
   type RuntimeHostServiceManagementFrame,
   type RuntimeHostServiceUpdatePhase,
-  type RuntimeHostSetupFrame,
   type RuntimeHostWebRtcStunPolicy,
 } from '@maka/runtime-host/operator';
 import type {
@@ -111,7 +108,6 @@ type DesktopRuntimeHostSshProcess = RuntimeHostSshProcess & {
 
 const TERMINAL_REVEAL_DELAY_MS = 500;
 const TERMINAL_OUTPUT_MAX = 64 * 1024;
-const SETUP_FRAME_PENDING_MAX = 20 * 1024;
 const MANAGEMENT_FRAME_PENDING_MAX = 128 * 1024;
 const ACCESS_MANAGEMENT_FRAME_PENDING_MAX = 768 * 1024;
 const PEER_MANAGEMENT_FRAME_PENDING_MAX = 128 * 1024;
@@ -119,17 +115,6 @@ const SETUP_TIMEOUT_MS = 10 * 60_000;
 const MANAGEMENT_TIMEOUT_MS = 2 * 60_000;
 const PROCESS_STOP_GRACE_MS = 2_000;
 
-export interface DesktopRuntimeHostSshSetupInput {
-  readonly destination: string;
-  readonly sshPort?: number;
-  readonly setupPackage: DesktopRuntimeHostSetupPackage;
-  readonly remotePlatform: RuntimeHostOperatorPlatform;
-  readonly principalId: string;
-  readonly lifecycle?: 'supervised' | 'on_demand';
-  readonly projectDirectoryRoots?: readonly { readonly label: string; readonly path: string }[];
-  readonly signal?: AbortSignal;
-}
-
 export interface DesktopRuntimeHostSshTargetInput {
   readonly destination: string;
   readonly sshPort?: number;
@@ -260,7 +245,6 @@ export type RuntimeHostServiceUpdateReconciliationTerminalFrame = Extract<
   { kind: 'result' | 'error'; action: 'reconcile_update' }
 >;
 
-type RuntimeHostSetupCompleteFrame = Extract<RuntimeHostSetupFrame, { kind: 'complete' }>;
 
 export function createDesktopRuntimeHostSshTerminal(input: {
   readonly ipcMain: Pick<IpcMain, 'handle' | 'removeHandler'>;
@@ -280,11 +264,6 @@ export function createDesktopRuntimeHostSshTerminal(input: {
   resolveTargetIdentity(
     input: DesktopRuntimeHostSshTargetInput,
   ): Promise<RuntimeHostTargetIdentity>;
-  runSetup(
-    input: DesktopRuntimeHostSshSetupInput,
-    onProgress: (frame: Extract<RuntimeHostSetupFrame, { kind: 'progress' }>) => void,
-    onComplete?: (frame: RuntimeHostSetupCompleteFrame) => void,
-  ): Promise<RuntimeHostSetupCompleteFrame>;
   runNativeSetup(input: NativeSetupInput & DesktopRuntimeHostSshTargetInput, onCommit: () => void): Promise<NativeSetupResult>;
   prepareNativePackage(input: NativeSetupInput & DesktopRuntimeHostSshTargetInput): Promise<RuntimeHostNativeOperatorCommand>;
   runServiceManagement(
@@ -800,80 +779,6 @@ export function createDesktopRuntimeHostSshTerminal(input: {
     },
     runNativeSetup: (setupInput, onCommit) => installNativeRuntimeHost(nativeTransport(setupInput), setupInput, onCommit),
     prepareNativePackage: (setupInput) => prepareNativeRuntimeHost(nativeTransport(setupInput), setupInput),
-    runSetup: async (setupInput, onProgress, onComplete) => {
-      if (closed) throw new Error('Runtime Host SSH terminal is closed');
-      setupInput.signal?.throwIfAborted();
-      const cancellation = cancellableUntilComplete(setupInput.signal);
-      try {
-        const destination = normalizeRuntimeHostSshDestination(setupInput.destination);
-        const sshPort = setupInput.sshPort === undefined
-          ? undefined
-          : requireSetupPort(setupInput.sshPort);
-        const setupPackage = await prepareSetupPackage(
-          setupInput.setupPackage,
-          destination,
-          sshPort,
-          setupInput.principalId,
-          startTerminalProcess,
-          cancellation.signal,
-          input.processStopGraceMs,
-          dismissPresentation,
-          input.terminateProcessTree,
-        );
-        const remoteCommand = runtimeHostSetupRemoteCommand(setupPackage, setupInput);
-        let complete: RuntimeHostSetupCompleteFrame | undefined;
-        let setupFailure: Error | undefined;
-        let setupTerminal: ActiveTerminal | undefined;
-        const filter = createRuntimeHostFramedOutputFilter({
-          prefix: RUNTIME_HOST_SETUP_FRAME_PREFIX,
-          pendingMaxBytes: SETUP_FRAME_PENDING_MAX,
-          decode: decodeRuntimeHostSetupFrame,
-          label: 'Remote Maka setup',
-          onFrame: (frame) => {
-            if (frame.kind === 'progress') onProgress(frame);
-            else if (frame.kind === 'complete') {
-              if (!cancellation.commit()) return;
-              complete = frame;
-              onComplete?.(frame);
-              if (setupTerminal) completePresentation(setupTerminal);
-            } else setupFailure = new Error(frame.kind === 'error' ? frame.error.message : 'SSH setup returned a local environment binding');
-          },
-          onError: (error) => {
-            setupFailure = error;
-          },
-        });
-        const { process, terminal } = startTerminalProcess(
-          'ssh',
-          sshRemoteCommandArgs(destination, sshPort, remoteCommand),
-          filter.push,
-        );
-        setupTerminal = terminal;
-        if (complete) completePresentation(terminal);
-        const wait = await waitForTerminalProcess(process, {
-          signal: cancellation.signal,
-          timeoutMs: SETUP_TIMEOUT_MS,
-          stopGraceMs: input.processStopGraceMs,
-          onAbort: () => dismissPresentation(terminal),
-        }, input.terminateProcessTree);
-        filter.finish();
-        if (setupFailure) throw setupFailure;
-        if (!complete) {
-          throw new Error(
-            wait.timedOut
-              ? 'Remote Maka setup timed out'
-              : wait.exit.code === 0
-              ? 'Remote Maka setup ended without a completion result'
-              : wait.exit.code === 2
-                ? 'The released Maka CLI on this channel does not support automated Runtime Host setup'
-                : `Remote Maka setup exited with code ${String(wait.exit.code)}`,
-          );
-        }
-        completePresentation(terminal);
-        return complete;
-      } finally {
-        cancellation.close();
-      }
-    },
     runServiceManagement: async (managementInput) => {
       const frame = await runFramedManagement({
         ...managementInput,
@@ -1071,31 +976,6 @@ export function runtimeHostPeerTargetFromPlatform(
   throw new Error(`Direct peer is not available on ${target}`);
 }
 
-function cancellableUntilComplete(signal: AbortSignal | undefined): {
-  readonly signal: AbortSignal;
-  commit(): boolean;
-  close(): void;
-} {
-  const controller = new AbortController();
-  let committed = false;
-  const onAbort = () => {
-    if (!committed) controller.abort();
-  };
-  if (signal?.aborted) onAbort();
-  else signal?.addEventListener('abort', onAbort, { once: true });
-  const close = () => signal?.removeEventListener('abort', onAbort);
-  return {
-    signal: controller.signal,
-    commit() {
-      if (controller.signal.aborted) return false;
-      committed = true;
-      close();
-      return true;
-    },
-    close,
-  };
-}
-
 type PreparedSetupPackage =
   | { readonly kind: 'npm'; readonly specifier: string }
   | {
@@ -1260,42 +1140,6 @@ async function settlesWithin(promise: Promise<unknown>, timeoutMs: number): Prom
   }
 }
 
-function runtimeHostSetupRemoteCommand(
-  setupPackage: PreparedSetupPackage,
-  input: Pick<
-    DesktopRuntimeHostSshSetupInput,
-    'principalId' | 'projectDirectoryRoots' | 'lifecycle' | 'remotePlatform'
-  >,
-): string {
-  if (!/^[A-Za-z0-9_.:-]{1,128}$/u.test(input.principalId)) {
-    throw new Error('Runtime Host setup principal is invalid');
-  }
-  return runtimeHostPackageRemoteCommand(setupPackage, [
-    'runtime-host',
-    'setup',
-    '--principal',
-    input.principalId,
-    '--preset',
-    'desktop-client',
-    '--lifecycle',
-    input.lifecycle === 'on_demand' ? 'on-demand' : 'supervised',
-    // Development archives identify every source revision as a distinct exact
-    // package. Re-running Add computer is the explicit replacement gesture in
-    // that environment; released packages keep using the normal update UI.
-    ...(setupPackage.kind === 'development_archive' ? ['--update-existing'] : []),
-    '--defer-pairing-commit',
-    ...(input.projectDirectoryRoots === undefined
-      ? []
-      : input.projectDirectoryRoots.length === 0
-        ? ['--no-project-roots']
-        : input.projectDirectoryRoots.flatMap(({ label, path }) => [
-            '--project-root-json',
-            JSON.stringify({ label, path }),
-          ])),
-    '--json',
-  ], {}, input.remotePlatform);
-}
-
 function runtimeHostActivationRemoteCommand(
   input: RuntimeHostSshOperatorActivationInput,
 ): string {
@@ -1366,7 +1210,7 @@ function runtimeHostUpdateRemoteCommand(
         RUNTIME_HOST_OPERATOR_ACCESS_MANAGEMENT_CAPABILITY,
       [RUNTIME_HOST_OPERATOR_PROJECT_DIRECTORY_CONFIGURATION_REQUEST_ENV]: '1',
     },
-    input.operator.kind === 'legacy_posix_executable' ? 'posix' : input.operator.platform,
+    input.operator.platform,
   );
 }
 
diff --git a/apps/desktop/src/main/runtime-host-wsl-controller.ts b/apps/desktop/src/main/runtime-host-wsl-controller.ts
index cdea0be866..a409105369 100644
--- a/apps/desktop/src/main/runtime-host-wsl-controller.ts
+++ b/apps/desktop/src/main/runtime-host-wsl-controller.ts
@@ -26,20 +26,15 @@ import {
   type RuntimeHostWslProcessFactory,
 } from '@maka/runtime-host/client';
 import {
-  decodeRuntimeHostSetupFrame,
   decodeRuntimeHostServiceManagementFrame,
   RUNTIME_HOST_OPERATOR_PROJECT_DIRECTORY_CONFIGURATION_REQUEST_ENV,
   RUNTIME_HOST_OPERATOR_RETIREMENT_CANCELLATION_ENV,
   RUNTIME_HOST_SERVICE_MANAGEMENT_FRAME_PREFIX,
-  RUNTIME_HOST_SETUP_FRAME_PREFIX,
   RUNTIME_HOST_SETUP_SOURCE_PACKAGE_INTEGRITY_ENV,
   decodeRuntimeHostPosixOperatorCommand,
   runtimeHostOperatorInvocation,
-  type RuntimeHostNodeOperatorCommand,
   type RuntimeHostNativeOperatorCommand,
   type RuntimeHostPosixOperatorCommand,
-  type RuntimeHostSetupFrame,
-  type RuntimeHostSetupPhase,
   type RuntimeHostServiceManagementFrame,
 } from '@maka/runtime-host/operator';
 import { createRuntimeHostFramedOutputFilter } from './runtime-host-framed-output.js';
@@ -124,10 +119,6 @@ export async function resolveDesktopRuntimeHostWslTarget(
   });
 }
 
-type RuntimeHostSetupCompleteFrame = Extract<RuntimeHostSetupFrame, { kind: 'complete' | 'existing_environment' }>;
-type RuntimeHostWslSetupCompleteFrame = Omit<RuntimeHostSetupCompleteFrame, 'operator'> & {
-  readonly operator: RuntimeHostNodeOperatorCommand<'posix'> | RuntimeHostNativeOperatorCommand<'posix'>;
-};
 type RuntimeHostManagementTerminalFrame = Exclude<
   RuntimeHostServiceManagementFrame,
   { readonly kind: 'progress' }
@@ -229,65 +220,6 @@ export async function runDesktopRuntimeHostWslManagement(
   return terminal;
 }
 
-export interface DesktopRuntimeHostWslSetupInput {
-  readonly distribution: string;
-  readonly setupPackage: DesktopRuntimeHostSetupPackage;
-  readonly principalId: string;
-  readonly projectDirectoryRoots?: readonly {
-    readonly label: string;
-    readonly path: string;
-  }[];
-  readonly signal?: AbortSignal;
-}
-
-export async function runDesktopRuntimeHostWslSetup(
-  input: DesktopRuntimeHostWslSetupInput,
-  onProgress: (frame: { readonly phase: RuntimeHostSetupPhase }) => void,
-  onComplete?: () => void,
-  overrides: {
-    readonly processFactory?: RuntimeHostWslProcessFactory;
-    readonly wslExecutable?: string;
-  } = {},
-): Promise<RuntimeHostWslSetupCompleteFrame> {
-  input.signal?.throwIfAborted();
-  const distribution = normalizeRuntimeHostWslDistribution(input.distribution);
-  const processFactory = overrides.processFactory ?? spawnWsl;
-  const executable = overrides.wslExecutable ?? resolveSystemRuntimeHostWslExecutable();
-  const setupPackage = await resolveWslPackageSpecifier(
-    input.setupPackage,
-    distribution,
-    executable,
-    processFactory,
-  );
-  const command = runtimeHostWslSetupCommand(setupPackage, input, input.setupPackage.kind === 'development_archive');
-  const child = processFactory(executable, ['--distribution', distribution, '--exec', '/bin/sh', '-lc', command]);
-  return runWslFramedProcess({
-    child,
-    signal: input.signal,
-    prefix: RUNTIME_HOST_SETUP_FRAME_PREFIX,
-    decode: decodeRuntimeHostSetupFrame,
-    label: 'WSL Maka setup',
-    onFrame: (frame) => {
-      if (frame.kind === 'progress') {
-        onProgress(frame);
-        return undefined;
-      }
-      if (frame.kind === 'error') throw new Error(frame.error.message);
-      if (frame.operator.platform !== 'posix') {
-        throw new Error('WSL Runtime Host setup returned a non-POSIX operator');
-      }
-      return {
-        ...frame,
-        operator: {
-          ...frame.operator,
-          platform: 'posix',
-        },
-      };
-    },
-    onResult: () => onComplete?.(),
-  });
-}
-
 /** Runs the explicitly selected successor, which owns the existing durable update transaction. */
 export async function runDesktopRuntimeHostWslUpdate(
   input: {
@@ -375,48 +307,6 @@ async function resolveWslPackageSpecifier(
   return { specifier: path, integrity: setupPackage.integrity };
 }
 
-function runtimeHostWslSetupCommand(
-  setupPackage: { readonly specifier: string; readonly integrity?: string },
-  input: Pick<DesktopRuntimeHostWslSetupInput, 'principalId' | 'projectDirectoryRoots'>,
-  development: boolean,
-): string {
-  if (!/^[A-Za-z0-9_.:-]{1,128}$/u.test(input.principalId)) {
-    throw new Error('Runtime Host setup principal is invalid');
-  }
-  const args = [
-    'maka',
-    'runtime-host',
-    'setup',
-    '--principal',
-    input.principalId,
-    '--preset',
-    'desktop-client',
-    '--lifecycle',
-    'on-demand',
-    '--repair-root-after-remount',
-    // Source development explicitly selects a new archive; released onboarding
-    // must never replace an existing shared deployment as a side effect of Connect.
-    ...(development ? ['--update-existing'] : ['--reuse-existing-environment']),
-    ...(input.projectDirectoryRoots === undefined
-      ? []
-      : input.projectDirectoryRoots.length === 0
-        ? ['--no-project-roots']
-        : input.projectDirectoryRoots.flatMap(({ label, path }) => [
-            '--project-root-json',
-            JSON.stringify({ label, path }),
-          ])),
-    '--json',
-  ];
-  const invocation = ['npx', '--yes', '--package', setupPackage.specifier, ...args]
-    .map(quotePosix)
-    .join(' ');
-  const environment = setupPackage.integrity
-    ? `${RUNTIME_HOST_SETUP_SOURCE_PACKAGE_INTEGRITY_ENV}=${quotePosix(setupPackage.integrity)} `
-    : '';
-  const command = `maka_prefix=$(mktemp -d) || exit 1; trap 'rm -rf -- "$maka_prefix"' EXIT; cd "$maka_prefix" || exit 1; ${environment}${invocation}`;
-  return runtimeHostWslLoginCommand(command);
-}
-
 function runtimeHostWslLoginCommand(command: string): string {
   const loginCommand = `exec /bin/sh -c ${quotePosix(command)}`;
   return `exec "\${SHELL:-/bin/sh}" -lic ${quotePosix(loginCommand)}`;
diff --git a/apps/desktop/src/main/runtime-host-wsl-handoff.ts b/apps/desktop/src/main/runtime-host-wsl-handoff.ts
index 67cc4034b4..7fbb759866 100644
--- a/apps/desktop/src/main/runtime-host-wsl-handoff.ts
+++ b/apps/desktop/src/main/runtime-host-wsl-handoff.ts
@@ -63,7 +63,7 @@ export async function resolveDesktopWslHostHandoff(
     action: 'status', expectedTarget, signal,
   });
   if (status.kind !== 'result' || status.action !== 'status' || status.service.lifecycle?.mode !== 'on_demand' ||
-      !status.service.pid || !status.service.installedVersion) {
+      !status.service.pid || !status.service.installedVersion || !status.service.configurationFingerprint) {
     return { ...base, operatorStep: guidance.source };
   }
   const setupPackage = await deps.resolvePackage(signal);
@@ -73,8 +73,6 @@ export async function resolveDesktopWslHostHandoff(
     return { ...base, operatorStep: guidance.target };
   }
   const currentVersion = status.service.installedVersion;
-  // Legacy operators omit the fingerprint. The transaction still fences the source
-  // package version, deployment identity and exact Host generation under its lease.
   const expectedConfigFingerprint = status.service.configurationFingerprint;
   const expectedHost = { hostEpoch: error.hostEpoch, pid: status.service.pid };
   const identity = JSON.stringify([base.identity, binding.deployment, expectedHost, currentVersion, expectedConfigFingerprint, setupPackage]);
diff --git a/apps/desktop/src/main/session-local-service.ts b/apps/desktop/src/main/session-local-service.ts
index 558bd3e84c..f466d015a4 100644
--- a/apps/desktop/src/main/session-local-service.ts
+++ b/apps/desktop/src/main/session-local-service.ts
@@ -650,7 +650,7 @@ export function registerDesktopSessionLocalIpc(deps: {
                 quotes: command.quotes,
                 inlineReferences,
               },
-              ...(command.skillIds?.length ? { skillIds: command.skillIds } : {}),
+              ...(command.inputSelections ? { inputSelections: command.inputSelections } : {}),
               ...(command.turnOrchestration ? { turnOrchestration: command.turnOrchestration } : {}),
             },
           }),
@@ -668,7 +668,7 @@ export function registerDesktopSessionLocalIpc(deps: {
         disposition: 'locally_saved',
         attachments: retained,
         inlineReferences,
-        skillInvocation: { loaded: [], failed: [], receipts: [] },
+        preparation: [],
       };
     },
   );
diff --git a/apps/desktop/src/preload/bridge-contract.d.ts b/apps/desktop/src/preload/bridge-contract.d.ts
index 2115b23ce0..aaff099e39 100644
--- a/apps/desktop/src/preload/bridge-contract.d.ts
+++ b/apps/desktop/src/preload/bridge-contract.d.ts
@@ -856,6 +856,7 @@ export interface MakaBridge {
   };
 
   clientPlugins: {
+    authorization(host: DesktopRuntimeHostRef, connectionEpoch: string, input: OperationInput<'plugin.authorization'>, registerCancellation: (cancel: () => void) => void): Promise<OperationOutput<'plugin.authorization'>>;
     file(host: DesktopRuntimeHostRef, connectionEpoch: string, identity: ClientIdentity, input: ClientFileRequest): Promise<string | null>;
     connection(host: DesktopRuntimeHostRef): Promise<{ epoch: string; hostEpoch: string; localFiles: boolean }>;
     session(host: DesktopRuntimeHostRef, connectionEpoch: string, sessionId: string): Promise<string>;
@@ -1119,7 +1120,7 @@ export interface MakaBridge {
         turnId: string;
         text: string;
         displayText?: string;
-        skillIds?: string[];
+        inputSelections?: import("@maka/runtime-host/protocol").InputSelections;
         attachmentItems?: RendererIngestInput[];
         retainedAttachments?: import('@maka/core/events').AttachmentRef[];
         turnOrchestration?: TurnOrchestration;
@@ -1142,7 +1143,7 @@ export interface MakaBridge {
           messageId?: never;
           attachments: import('@maka/core/events').AttachmentRef[];
           inlineReferences: import('@maka/core/events').InlineReference[];
-          skillInvocation: import('@maka/runtime/skill-invocation').SkillInvocationResult;
+          preparation: import('@maka/runtime-host/protocol').InputReceipt[];
         }
       | {
           ok: true;
@@ -1156,12 +1157,13 @@ export interface MakaBridge {
           messageId: string;
           attachments: import('@maka/core/events').AttachmentRef[];
           inlineReferences: import('@maka/core/events').InlineReference[];
-          skillInvocation: import('@maka/runtime/skill-invocation').SkillInvocationResult;
+          preparation: import('@maka/runtime-host/protocol').InputReceipt[];
         }
       | {
           ok: false;
-          reason: 'skill_invocation_failed';
-          skillInvocation: import('@maka/runtime/skill-invocation').SkillInvocationResult;
+          reason: 'input_preparation_failed';
+          message: string;
+          preparation: import('@maka/runtime-host/protocol').InputReceipt[];
         }
       | {
           ok: false;
@@ -1172,7 +1174,7 @@ export interface MakaBridge {
           ok: false;
           reason: 'outcome_unknown';
           messageId: string;
-          skillInvocation: import('@maka/runtime/skill-invocation').SkillInvocationResult;
+          preparation: import('@maka/runtime-host/protocol').InputReceipt[];
         }
     >;
     stop(
@@ -1195,7 +1197,7 @@ export interface MakaBridge {
         messageId: string;
         text: string;
         displayText?: string;
-        skillIds?: string[];
+        inputSelections?: import("@maka/runtime-host/protocol").InputSelections;
         turnOrchestration?: TurnOrchestration;
         attachmentItems?: RendererIngestInput[];
         retainedAttachments?: import('@maka/core/events').AttachmentRef[];
@@ -1214,12 +1216,13 @@ export interface MakaBridge {
           turnId?: string;
           attachments: import('@maka/core/events').AttachmentRef[];
           inlineReferences: import('@maka/core/events').InlineReference[];
-          skillInvocation: import('@maka/runtime/skill-invocation').SkillInvocationResult;
+          preparation: import('@maka/runtime-host/protocol').InputReceipt[];
         }
       | {
           ok: false;
-          reason: 'skill_invocation_failed';
-          skillInvocation: import('@maka/runtime/skill-invocation').SkillInvocationResult;
+          reason: 'input_preparation_failed';
+          message: string;
+          preparation: import('@maka/runtime-host/protocol').InputReceipt[];
         }
       | {
           ok: false;
diff --git a/apps/desktop/src/preload/preload.ts b/apps/desktop/src/preload/preload.ts
index 7747615536..382c614c88 100644
--- a/apps/desktop/src/preload/preload.ts
+++ b/apps/desktop/src/preload/preload.ts
@@ -1409,6 +1409,17 @@ const makaBridge = {
   workHubControl: workHubControlBridge,
   workHubPresentation: workHubPresentationBridge,
   clientPlugins: {
+    async authorization(host, connectionEpoch, input, registerCancellation) {
+      const requestId = crypto.randomUUID();
+      let cancelled = false;
+      let cancelPending: (() => void) | undefined;
+      registerCancellation(() => { cancelled = true; cancelPending?.(); });
+      const scope = await runtimeHostScope(host);
+      if (cancelled) throw new Error('Client plugin retired before authorization');
+      const pending = ipcRenderer.invoke('plugins:authorization', scope, browserDocumentId, connectionEpoch, input, requestId);
+      cancelPending = () => { void ipcRenderer.invoke('plugins:authorization-cancel', scope, browserDocumentId, connectionEpoch, requestId).catch(() => {}); };
+      try { return await pending; } finally { cancelPending = undefined; }
+    },
     subscribeContext(host, handler) {
       const disposers = [
         subscribeSelectedRuntimeHostEvent('connections:event', host, handler),
diff --git a/apps/desktop/src/renderer/app-shell-chat-actions.ts b/apps/desktop/src/renderer/app-shell-chat-actions.ts
index 8dff1f4450..e4b29ab40e 100644
--- a/apps/desktop/src/renderer/app-shell-chat-actions.ts
+++ b/apps/desktop/src/renderer/app-shell-chat-actions.ts
@@ -24,7 +24,7 @@ import type * as DesktopBridge from '../preload/bridge-contract.js';
 import type { QuoteRef } from '@maka/core/events';
 import type { OrchestrationMode } from '@maka/core/orchestration';
 import type { SandboxBoundaryResponse } from '@maka/core/sandbox-boundary';
-import type { SkillInvocationResult } from '@maka/runtime/skill-invocation';
+import type { InputReceipt } from '@maka/runtime-host/protocol';
 import type { ThinkingLevel } from '@maka/core/model-thinking';
 import type { TurnOrchestration } from '@maka/core/runtime-inputs';
 import type { UiLocale } from '@maka/core/ui-locale';
@@ -234,7 +234,7 @@ export function createAppShellChatActions(deps: {
 
   /** Only an unreconciled submission keeps its row because Host admission may have succeeded. */
   type SubmittedMessage =
-    | { kind: 'projected'; skillInvocation: SkillInvocationResult; turnId?: string }
+    | { kind: 'projected'; preparation: InputReceipt[]; turnId?: string }
     | { kind: 'unreconciled' }
     | { kind: 'refused' };
 
@@ -277,7 +277,7 @@ export function createAppShellChatActions(deps: {
       return { kind: 'refused' };
     }
     if (result.disposition === 'locally_saved') {
-      return { kind: 'projected', skillInvocation: result.skillInvocation };
+      return { kind: 'projected', preparation: result.preparation };
     }
     if (surfaceVisible) skillFeedback.showSubmissionFeedback(uiLocale, toastApi, result, sessionId);
     // The row is updated whether or not the surface is on screen: attachments,
@@ -285,7 +285,7 @@ export function createAppShellChatActions(deps: {
     // they come back to it.
     publishTransientUserMessage(sessionId, {
       id: messageId,
-      text: input.displayText ?? skillFeedback.skillInvocationDisplayText(input.command.text, result.skillInvocation),
+      text: input.displayText ?? skillFeedback.skillInvocationDisplayText(input.command.text, result.preparation),
       attachments: [...result.attachments],
       transientPlacement: placement,
       pendingSteering: result.disposition === 'turn_started' ? false : input.pendingSteering,
@@ -296,7 +296,7 @@ export function createAppShellChatActions(deps: {
     }, true);
     return {
       kind: 'projected',
-      skillInvocation: result.skillInvocation,
+      preparation: result.preparation,
       ...(result.turnId ? { turnId: result.turnId } : {}),
     };
   }
diff --git a/apps/desktop/src/renderer/features/client-plugins/host-runtime.ts b/apps/desktop/src/renderer/features/client-plugins/host-runtime.ts
index 3f86dfe27b..0411656594 100644
--- a/apps/desktop/src/renderer/features/client-plugins/host-runtime.ts
+++ b/apps/desktop/src/renderer/features/client-plugins/host-runtime.ts
@@ -95,7 +95,7 @@ export class ClientHostRuntime {
       else this.options.report(diagnostic);
     };
     const runtime = this.create({ ...this.options, source: transport.source,
-      remote: transport.remote, localFiles: transport.localFiles, report });
+      remote: transport.remote, localFiles: transport.localFiles, authorization: transport.authorization, report });
     let revision = 0;
     let retry = 0;
     let timer: ReturnType<typeof setTimeout> | undefined;
diff --git a/apps/desktop/src/renderer/features/client-plugins/ports.ts b/apps/desktop/src/renderer/features/client-plugins/ports.ts
index 479fe6526b..ab11870a18 100644
--- a/apps/desktop/src/renderer/features/client-plugins/ports.ts
+++ b/apps/desktop/src/renderer/features/client-plugins/ports.ts
@@ -18,7 +18,7 @@
  */
 
 import type { ClientDescriptor } from '@maka-agent/plugin-sdk/client';
-import type { ClientSnapshot, ClientRemoteFactory, ClientFilesFactory } from '@maka/ui/client-plugins';
+import type { ClientSnapshot, ClientRemoteFactory, ClientFilesFactory, ClientAuthorizationFactory } from '@maka/ui/client-plugins';
 
 export interface ClientHostRef { readonly profileId: string; readonly hostId: string }
 export interface ClientPluginServices {
@@ -27,6 +27,7 @@ export interface ClientPluginServices {
   connect(host: ClientHostRef): {
     readonly remote: ClientRemoteFactory;
     readonly localFiles?: ClientFilesFactory;
+    readonly authorization?: ClientAuthorizationFactory;
     session(sessionId: string): Promise<string>;
     snapshot(signal: AbortSignal): Promise<ClientSnapshot>;
     source(descriptor: ClientDescriptor, signal: AbortSignal): Promise<string>;
diff --git a/apps/desktop/src/renderer/platform/desktop/client-plugin-remote.ts b/apps/desktop/src/renderer/platform/desktop/client-plugin-remote.ts
index 801e6b69c0..0442dfe3c4 100644
--- a/apps/desktop/src/renderer/platform/desktop/client-plugin-remote.ts
+++ b/apps/desktop/src/renderer/platform/desktop/client-plugin-remote.ts
@@ -17,118 +17,17 @@
  * under the License.
  */
 
-import type { ClientIdentity, ClientRemote } from '@maka-agent/plugin-sdk/client';
-import type { Json } from '@maka-agent/plugin-sdk/host';
+import { createPluginRemote } from '@maka/runtime-host/client';
 import type { ClientRemoteFactory } from '@maka/ui/client-plugins';
-import type { PluginRemoteBinding, PluginRemoteTarget } from '@maka/runtime-host/protocol';
 import type { MakaBridge } from '../../../preload/bridge-contract.js';
 import type { ClientHostRef } from '../../features/client-plugins/index.js';
 
-/** One document per UI instance, lazily allocated and never retargeted. */
 export function clientPluginRemote(
   transport: MakaBridge['clientPlugins']['remote'],
   host: ClientHostRef,
   connectionEpoch: string,
 ): ClientRemoteFactory {
-  return (identity: ClientIdentity, signal: AbortSignal) => {
-    let document: Promise<string> | undefined;
-    let closing: Promise<void> | undefined;
-    const request = (input: Parameters<typeof transport>[2]) => transport(host, connectionEpoch, input);
-    const assertLive = () => {
-      signal.throwIfAborted();
-      if (closing) throw new Error('Client Remote is closed');
-    };
-    const getDocument = () => {
-      assertLive();
-      document ??= request({ kind: 'open_document' }).then((result) => {
-        if (result.kind !== 'document') throw new Error('Unexpected Remote document result');
-        return result.document;
-      });
-      return document;
-    };
-    const close = (): Promise<void> => {
-      if (closing) return closing;
-      signal.removeEventListener('abort', retired);
-      closing = (async () => {
-        if (!document) return;
-        let id: string;
-        try { id = await document; } catch { return; }
-        const result = await request({ kind: 'close_document', document: id });
-        // Connection retirement revokes this local lease. Host owns draining
-        // the old connection and fences backend instances if cleanup fails.
-        if (result.kind !== 'closed' && result.kind !== 'connection_retired')
-          throw new Error('Remote cleanup was not confirmed');
-      })();
-      return closing;
-    };
-    const retired = () => { void close().catch(() => {}); };
-    signal.addEventListener('abort', retired, { once: true });
-    if (signal.aborted) retired();
-
-    const bind = (name: string, sessionId: string | undefined, handler: 'method' | 'stream') => {
-      const binding: PluginRemoteBinding = { client: identity, method: name, sessionId: sessionId ?? null };
-      let target: Promise<PluginRemoteTarget> | undefined;
-      return async () => {
-        assertLive();
-        target ??= request({ kind: 'bind', binding }).then((result) => {
-          if (result.kind !== 'bound' || result.handler !== handler) throw new Error('Remote handler kind mismatch');
-          return result.target;
-        });
-        const [destination, owner] = await Promise.all([target, getDocument()]);
-        assertLive();
-        return { binding, target: destination, document: owner };
-      };
-    };
-    const api: ClientRemote = {
-      method<I extends Json, O extends Json>(name: string, sessionId?: string) {
-        const bound = bind(name, sessionId, 'method');
-        return async (input: I): Promise<O> => {
-          const result = await request({ kind: 'call', ...await bound(), input });
-          if (result.kind !== 'value') throw new Error('Unexpected Remote method result');
-          return result.value as O;
-        };
-      },
-      stream<I extends Json, O extends Json>(name: string, sessionId?: string) {
-        const bound = bind(name, sessionId, 'stream');
-        return async function* (input: I, cancellation?: AbortSignal): AsyncGenerator<O> {
-          cancellation?.throwIfAborted();
-          const origin = await bound();
-          cancellation?.throwIfAborted();
-          const opened = await request({ kind: 'open', ...origin, input });
-          if (opened.kind !== 'opened') throw new Error('Unexpected Remote stream result');
-          const handle = { document: origin.document, stream: opened.stream };
-          let ended = false;
-          let stopping: Promise<void> | undefined;
-          const stop = () => {
-            if (ended || closing) return Promise.resolve();
-            stopping ??= request({ kind: 'close', ...handle }).then((result) => {
-              if (result.kind !== 'closed' && result.kind !== 'connection_retired')
-                throw new Error('Remote stream cleanup was not confirmed');
-            });
-            return stopping;
-          };
-          // A generator return() cannot preempt its own awaited next(). Signal
-          // the Host separately so an idle provider read can actually finish.
-          const abort = () => { void stop().catch(() => {}); };
-          cancellation?.addEventListener('abort', abort, { once: true });
-          try {
-            for (;;) {
-              assertLive();
-              cancellation?.throwIfAborted();
-              const result = await request({ kind: 'next', ...handle });
-              cancellation?.throwIfAborted();
-              if (result.kind === 'pending') continue;
-              if (result.kind === 'end') { ended = true; return; }
-              if (result.kind !== 'item') throw new Error('Unexpected Remote stream item');
-              yield result.item as O;
-            }
-          } finally {
-            cancellation?.removeEventListener('abort', abort);
-            await stop();
-          }
-        };
-      },
-    };
-    return { api, close };
-  };
+  return (identity, signal) => createPluginRemote(
+    (input) => transport(host, connectionEpoch, input), identity, signal,
+  );
 }
diff --git a/apps/desktop/src/renderer/platform/desktop/create-client-plugin-services.ts b/apps/desktop/src/renderer/platform/desktop/create-client-plugin-services.ts
index 26b5dc1dd8..655689a0f9 100644
--- a/apps/desktop/src/renderer/platform/desktop/create-client-plugin-services.ts
+++ b/apps/desktop/src/renderer/platform/desktop/create-client-plugin-services.ts
@@ -55,6 +55,37 @@ export function createDesktopClientPluginServices(
           if (!connectionEpoch) throw new Error('Client catalog has no connection identity');
           return clientPluginRemote(bridge.clientPlugins.remote, host, connectionEpoch)(identity, signal);
         },
+        authorization(identity, signal) {
+          if (!connectionEpoch) throw new Error('Client catalog has no connection identity');
+          const epoch = connectionEpoch;
+          const invoke = async (scope: import('@maka-agent/plugin-sdk/client').AuthorizationScope, command: Parameters<typeof bridge.clientPlugins.authorization>[2]['command']) => {
+            signal.throwIfAborted();
+            let cancel: (() => void) | undefined;
+            try {
+              return await bounded(bridge.clientPlugins.authorization(host, epoch, {client: identity, scope, command}, (stop) => {
+                cancel = stop;
+                signal.addEventListener('abort', stop, {once:true});
+                if (signal.aborted) stop();
+              }), signal);
+            } finally { if (cancel) signal.removeEventListener('abort', cancel); }
+          };
+          return {
+            async approve(scope, request) {
+              const result = await invoke(scope, {kind:'approve',request});
+              if (result.kind !== 'grant') throw new Error('Invalid authorization result');
+              return result.grant;
+            },
+            async query(scope, id) {
+              const result = await invoke(scope, {kind:'query',id});
+              if (result.kind !== 'grant') throw new Error('Invalid authorization result');
+              return result.grant;
+            },
+            async revoke(scope, id) {
+              const result = await invoke(scope, {kind:'revoke',id});
+              if (result.kind !== 'revoked') throw new Error('Invalid authorization result');
+            },
+          };
+        },
         localFiles(identity, signal) {
           if (!connectionEpoch) throw new Error('Client catalog has no connection identity');
           if (!localFiles) return undefined;
diff --git a/apps/desktop/src/renderer/skill-invocation-feedback.ts b/apps/desktop/src/renderer/skill-invocation-feedback.ts
index 4e71b0817c..d4f51fedec 100644
--- a/apps/desktop/src/renderer/skill-invocation-feedback.ts
+++ b/apps/desktop/src/renderer/skill-invocation-feedback.ts
@@ -20,6 +20,8 @@
 import type { AttachmentIngestBlockedCode } from '@maka/core/attachments';
 import type { UiLocale } from '@maka/core/ui-locale';
 import type { SkillInvocationResult } from '@maka/runtime/skill-invocation';
+import { preparedSkillInvocation } from '@maka/core/skill-invocation';
+import type { InputReceipt } from '@maka/runtime-host/protocol';
 import { getShellCopy } from './locales/shell-copy.js';
 
 type FeedbackToastApi = {
@@ -33,7 +35,7 @@ type FeedbackToastApi = {
 };
 
 type SubmissionFeedback =
-  | { skillInvocation: SkillInvocationResult }
+  | { preparation: InputReceipt[]; message?: string }
   | { reason: 'attachment_blocked'; code: AttachmentIngestBlockedCode };
 
 export function showSubmissionFeedback(
@@ -46,7 +48,11 @@ export function showSubmissionFeedback(
     showAttachmentIngestBlockedFeedback(uiLocale, toastApi, outcome.code, sessionId);
     return;
   }
-  showSkillInvocationFeedback(uiLocale, toastApi, outcome.skillInvocation, sessionId);
+  if (outcome.message) {
+    toastApi.error(getShellCopy(uiLocale).chatActions.sendFailedTitle, outcome.message, undefined, { sessionId });
+    return;
+  }
+  showSkillInvocationFeedback(uiLocale, toastApi, preparedSkillInvocation(outcome.preparation), sessionId);
 }
 
 function showAttachmentIngestBlockedFeedback(
@@ -67,10 +73,10 @@ function showAttachmentIngestBlockedFeedback(
 /** Match main-process persistence for a chip-only optimistic user message. */
 export function skillInvocationDisplayText(
   text: string,
-  skillInvocation: SkillInvocationResult,
+  preparation: InputReceipt[],
 ): string {
   if (text.trim().length > 0) return text;
-  return skillInvocation.loaded.map((skill) => `/skill:${skill.id}`).join(' ');
+  return preparedSkillInvocation(preparation).loaded.map((skill) => `/skill:${skill.id}`).join(' ');
 }
 
 /** The Composer is the only Desktop surface that invokes Skills (#1433). */
diff --git a/crates/agent/src/executor.rs b/crates/agent/src/executor.rs
index d8beb617bc..16386b87c9 100644
--- a/crates/agent/src/executor.rs
+++ b/crates/agent/src/executor.rs
@@ -48,12 +48,8 @@ impl Engine {
                 "executor cannot carry a model binding or mismatched workspace".into(),
             ));
         }
-        maka_runtime::message::validate_opening(
-            &input.request.content,
-            &input.source_messages,
-            None,
-        )
-        .map_err(|reason| RunError::InvalidInput(reason.into()))?;
+        maka_runtime::message::validate_sources(&input.request.content, &input.source_messages)
+            .map_err(|reason| RunError::InvalidInput(reason.into()))?;
         let identity = input
             .binding
             .identity()
@@ -82,7 +78,6 @@ impl Engine {
                             content: input.request.content,
                             request_fingerprint: input.request_fingerprint,
                             source_messages: input.source_messages,
-                            skill_invocation: None,
                         },
                     },
                     Fact::ExecutorStarted { binding: identity },
diff --git a/crates/agent/src/history.rs b/crates/agent/src/history.rs
index 84d16cafb0..4cfe885ada 100644
--- a/crates/agent/src/history.rs
+++ b/crates/agent/src/history.rs
@@ -111,7 +111,6 @@ mod tests {
             Fact::InvocationOpened {
                 configuration: None,
                 input: InvocationInput::Message {
-                    skill_invocation: Default::default(),
                     source_messages: Vec::new(),
                     content: "hello".into(),
                     request_fingerprint: None,
@@ -119,7 +118,7 @@ mod tests {
             },
             Fact::ModelRequested {
                 effective_source_digest: None,
-                purpose: None,
+                purpose: maka_runtime::context::ModelPurpose::Main,
                 context: None,
                 checkpoint_event_id: None,
                 step_id: "step".into(),
diff --git a/crates/agent/src/history/compatible.rs b/crates/agent/src/history/compatible.rs
index 6412df71ed..cb1c172cea 100644
--- a/crates/agent/src/history/compatible.rs
+++ b/crates/agent/src/history/compatible.rs
@@ -65,13 +65,7 @@ pub(crate) fn project(mut messages: Vec<Message>, provider: &ProviderKind) -> Ve
 }
 
 fn reasoning_field(options: Option<&Value>) -> Option<&'static str> {
-    let maka = &options?["maka"];
-    let current = &maka["openAiChatReasoningField"];
-    let field = if current.is_null() {
-        &maka["kimiReasoningField"]
-    } else {
-        current
-    };
+    let field = &options?["maka"]["openAiChatReasoningField"];
     match field.as_str()? {
         "reasoning" => Some("reasoning"),
         "reasoning_content" => Some("reasoning_content"),
@@ -120,10 +114,9 @@ mod tests {
     }
 
     #[test]
-    fn first_tagged_block_wins_and_legacy_field_is_supported() {
+    fn first_tagged_block_owns_the_reasoning_field() {
         let messages = vec![json!({"role":"assistant","content":[
-            {"type":"reasoning","text":"","providerOptions":{"maka":{
-                "openAiChatReasoningField":null,"kimiReasoningField":"reasoning"}}},
+            thinking("reasoning", ""),
             thinking("reasoning_content", "later"), thinking("reasoning", "last")
         ]})];
         let projected = serde_json::to_value(project(
diff --git a/crates/agent/src/history/replay.rs b/crates/agent/src/history/replay.rs
index 7b130d8d17..9c7654a585 100644
--- a/crates/agent/src/history/replay.rs
+++ b/crates/agent/src/history/replay.rs
@@ -174,10 +174,9 @@ mod tests {
         };
         let events: Vec<_> = [
             Fact::InvocationOpened { configuration: None, input: InvocationInput::Message {
-                content: "question".into(), request_fingerprint: None, source_messages: Vec::new(), skill_invocation: None,
-            }},
+                content: "question".into(), request_fingerprint: None, source_messages: Vec::new(),             }},
             Fact::ModelRequested { step_id: "step".into(), model_id: "old".into(), route_identity: "old".into(),
-                purpose: None, context: None, source_scope: maka_runtime::event::LogScope::Root, source_high_water: 1,
+                purpose: maka_runtime::context::ModelPurpose::Main, context: None, source_scope: maka_runtime::event::LogScope::Root, source_high_water: 1,
                 source_digest: "fixture".into(), input_digest: "fixture".into(), checkpoint_event_id: None, effective_source_digest: None },
             Fact::ModelCompleted { step_id: "step".into(), output: serde_json::from_value(json!({
                 "parts":[
diff --git a/crates/agent/src/lib.rs b/crates/agent/src/lib.rs
index ebd82c7edd..d5bf0dc524 100644
--- a/crates/agent/src/lib.rs
+++ b/crates/agent/src/lib.rs
@@ -106,7 +106,6 @@ pub enum RunWork {
     Message {
         message: MessageInput,
         source_messages: Vec<maka_runtime::message::RootSourceMessage>,
-        skill_invocation: Option<Box<maka_runtime::skills::SkillInvocationResult>>,
         tools: ToolCatalog,
         max_steps: usize,
     },
@@ -245,15 +244,10 @@ impl Engine {
             RunWork::Message {
                 message,
                 source_messages,
-                skill_invocation,
                 ..
             } => {
-                maka_runtime::message::validate_opening(
-                    message,
-                    source_messages,
-                    skill_invocation.as_deref(),
-                )
-                .map_err(|reason| RunError::InvalidInput(reason.into()))?;
+                maka_runtime::message::validate_sources(message, source_messages)
+                    .map_err(|reason| RunError::InvalidInput(reason.into()))?;
             }
             _ => {}
         }
diff --git a/crates/agent/src/model_attempt.rs b/crates/agent/src/model_attempt.rs
index 65702a78f6..e81e53430d 100644
--- a/crates/agent/src/model_attempt.rs
+++ b/crates/agent/src/model_attempt.rs
@@ -220,7 +220,7 @@ async fn execute_once(
                     maka_runtime::event::LogScope::Lineage { .. }
                 ))
             .then(|| source.effective_source_digest.clone()),
-            purpose: Some(purpose),
+            purpose,
             context: input.context.clone(),
             step_id: step_id.clone(),
             model_id: input.provider.model.clone(),
diff --git a/crates/agent/src/runner.rs b/crates/agent/src/runner.rs
index 50b1d4bbd0..9206429194 100644
--- a/crates/agent/src/runner.rs
+++ b/crates/agent/src/runner.rs
@@ -74,13 +74,11 @@ pub async fn run(
         RunWork::Message {
             message,
             source_messages,
-            skill_invocation,
             ..
         } => InvocationInput::Message {
             content: message.clone(),
             request_fingerprint: input.request_fingerprint.clone(),
             source_messages: source_messages.clone(),
-            skill_invocation: skill_invocation.clone(),
         },
         RunWork::ContextCompact => InvocationInput::ContextCompact {
             request_fingerprint: input
diff --git a/crates/agent/tests/integration/attachment_projection.rs b/crates/agent/tests/integration/attachment_projection.rs
index 5f009ab4bf..135263b64c 100644
--- a/crates/agent/tests/integration/attachment_projection.rs
+++ b/crates/agent/tests/integration/attachment_projection.rs
@@ -71,7 +71,6 @@ async fn attachment_references_remain_structured_and_match_the_original_model_fo
                 Fact::InvocationOpened {
                     configuration: None,
                     input: InvocationInput::Message {
-                        skill_invocation: Default::default(),
                         source_messages: Vec::new(),
                         content: input.clone(),
                         request_fingerprint: None,
@@ -90,7 +89,6 @@ async fn attachment_references_remain_structured_and_match_the_original_model_fo
                 invocation_id: format!("invocation{index}"),
             },
             Fact::MessageSteered {
-                skill_invocation: Default::default(),
                 message: Box::new(DeliveredMessage {
                     message_id: "same-client-id".into(),
                     content: input.clone(),
diff --git a/crates/agent/tests/integration/auto_context.rs b/crates/agent/tests/integration/auto_context.rs
index 7b3fee859f..90eb50dcab 100644
--- a/crates/agent/tests/integration/auto_context.rs
+++ b/crates/agent/tests/integration/auto_context.rs
@@ -208,7 +208,7 @@ async fn midturn_waits_for_all_code_mode_results_and_replays_exact_image_anchor_
             .filter(|event| matches!(
                 event.event.fact,
                 Fact::ModelRequested {
-                    purpose: Some(ModelPurpose::Summary),
+                    purpose: ModelPurpose::Summary,
                     ..
                 }
             ))
diff --git a/crates/agent/tests/integration/code_mode_lifetime.rs b/crates/agent/tests/integration/code_mode_lifetime.rs
index 45320cb87a..8693603434 100644
--- a/crates/agent/tests/integration/code_mode_lifetime.rs
+++ b/crates/agent/tests/integration/code_mode_lifetime.rs
@@ -65,7 +65,6 @@ fn input(base: &str, suffix: &str, effect: Arc<SlowEffect>) -> RunInput {
         supports_vision: false,
         configuration: invocation::configuration(ToolMode::CodeMode),
         work: maka_agent::RunWork::Message {
-            skill_invocation: Default::default(),
             source_messages: Vec::new(),
             message: format!("question {suffix}").into(),
             tools: ToolCatalog::new([ToolRegistration {
diff --git a/crates/agent/tests/integration/context_overflow.rs b/crates/agent/tests/integration/context_overflow.rs
index 8b25ba8367..0f5e579c9e 100644
--- a/crates/agent/tests/integration/context_overflow.rs
+++ b/crates/agent/tests/integration/context_overflow.rs
@@ -116,7 +116,7 @@ async fn overflow_recovers_once_only_without_output_and_with_remaining_main_budg
                 .filter(|event| matches!(
                     event.event.fact,
                     Fact::ModelRequested {
-                        purpose: Some(ModelPurpose::Main),
+                        purpose: ModelPurpose::Main,
                         ..
                     }
                 ))
@@ -129,7 +129,7 @@ async fn overflow_recovers_once_only_without_output_and_with_remaining_main_budg
                 .filter(|event| matches!(
                     event.event.fact,
                     Fact::ModelRequested {
-                        purpose: Some(ModelPurpose::Summary),
+                        purpose: ModelPurpose::Summary,
                         ..
                     }
                 ))
@@ -219,7 +219,7 @@ async fn proactive_failure_spends_the_same_budget_as_overflow_recovery() {
             .filter(|event| matches!(
                 event.event.fact,
                 Fact::ModelRequested {
-                    purpose: Some(ModelPurpose::Summary),
+                    purpose: ModelPurpose::Summary,
                     ..
                 }
             ))
@@ -232,7 +232,7 @@ async fn proactive_failure_spends_the_same_budget_as_overflow_recovery() {
             .filter(|event| matches!(
                 event.event.fact,
                 Fact::ModelRequested {
-                    purpose: Some(ModelPurpose::Main),
+                    purpose: ModelPurpose::Main,
                     ..
                 }
             ))
diff --git a/crates/agent/tests/integration/dynamic_tools.rs b/crates/agent/tests/integration/dynamic_tools.rs
index 3dfbaac78e..624ec8ef70 100644
--- a/crates/agent/tests/integration/dynamic_tools.rs
+++ b/crates/agent/tests/integration/dynamic_tools.rs
@@ -147,7 +147,7 @@ async fn search_activates_next_step_and_only_committed_compaction_unloads_direct
                 let mut input = fixture::input(&base, "discovery", false);
                 input.configuration.tool_mode = mode;
                 input.context = Some(ModelRequestContext { provider_id:"openai".into(), context_window:Some(220), declared_window:Some(220) });
-                input.work = RunWork::Message { skill_invocation: Default::default(), source_messages:Vec::new(), message:"Use echo".into(), tools:catalog, max_steps:3 };
+                input.work = RunWork::Message { source_messages:Vec::new(), message:"Use echo".into(), tools:catalog, max_steps:3 };
                 let engine = fixture::engine(log.clone());
                 engine.run(input, CancellationToken::new()).await.unwrap();
                 engine.drain().await;
diff --git a/crates/agent/tests/integration/frozen_projection.rs b/crates/agent/tests/integration/frozen_projection.rs
index d6e24d7d54..f142674b5a 100644
--- a/crates/agent/tests/integration/frozen_projection.rs
+++ b/crates/agent/tests/integration/frozen_projection.rs
@@ -40,9 +40,9 @@ async fn settled(log: &EventLog, output: &ToolOutput) {
     for fact in [
         Fact::InvocationOpened {
             configuration: None,
-            input: InvocationInput::Message { skill_invocation: Default::default(), source_messages: Vec::new(), content: "prior".into(), request_fingerprint: None },
+            input: InvocationInput::Message { source_messages: Vec::new(), content: "prior".into(), request_fingerprint: None },
         },
-        Fact::ModelRequested { effective_source_digest: None, purpose: None, context: None, checkpoint_event_id: None, step_id: "step".into(), model_id: "test".into(),
+        Fact::ModelRequested { effective_source_digest: None, purpose: maka_runtime::context::ModelPurpose::Main, context: None, checkpoint_event_id: None, step_id: "step".into(), model_id: "test".into(),
             source_scope: maka_runtime::event::LogScope::Session { id: "session".into() },
             source_high_water: 1, source_digest: "fixture".into(), input_digest: "fixture".into(),
             route_identity: "fixture".into() },
diff --git a/crates/agent/tests/integration/handoff.rs b/crates/agent/tests/integration/handoff.rs
index 1f2d8ebb28..9d2fc616fc 100644
--- a/crates/agent/tests/integration/handoff.rs
+++ b/crates/agent/tests/integration/handoff.rs
@@ -105,7 +105,7 @@ async fn handoff_waits_for_settlement_rollback_keeps_run_and_seal_survives_reope
                 for fact in [
                     Fact::InvocationOpened { configuration: Some(Box::new(run.configuration.clone())),
                         input: InvocationInput::Message { content: text.into(), request_fingerprint: None,
-                            source_messages: Vec::new(), skill_invocation: None } },
+                            source_messages: Vec::new(), } },
                     Fact::InvocationEnded { outcome: InvocationOutcome::Cancelled { source: "user".into() } },
                 ] {
                     log.append(&EventWrite::plain(RuntimeEvent::new(invocation.clone(), fact)).unwrap()).await.unwrap();
diff --git a/crates/agent/tests/integration/image_projection.rs b/crates/agent/tests/integration/image_projection.rs
index b63ed49210..0de0738a86 100644
--- a/crates/agent/tests/integration/image_projection.rs
+++ b/crates/agent/tests/integration/image_projection.rs
@@ -49,8 +49,8 @@ async fn reopened_user_and_typed_tool_images_share_actual_byte_budget_with_curre
         let prior = identity("historical");
         let message = serde_json::from_value(json!({"text":"historical", "attachments":[attachment("historical")]})).unwrap();
         let facts = [
-            Fact::InvocationOpened { input: InvocationInput::Message { skill_invocation: Default::default(), source_messages: Vec::new(), content: message, request_fingerprint: None }, configuration: None },
-            Fact::ModelRequested { effective_source_digest: None, purpose: None, context: None, checkpoint_event_id: None, step_id: "prior-step".into(), model_id: "test".into(),
+            Fact::InvocationOpened { input: InvocationInput::Message { source_messages: Vec::new(), content: message, request_fingerprint: None }, configuration: None },
+            Fact::ModelRequested { effective_source_digest: None, purpose: maka_runtime::context::ModelPurpose::Main, context: None, checkpoint_event_id: None, step_id: "prior-step".into(), model_id: "test".into(),
                 source_scope: maka_runtime::event::LogScope::Session { id: "session".into() },
                 source_high_water: 1, source_digest: "fixture".into(), input_digest: "fixture".into(), route_identity: "fixture".into() },
             Fact::ModelCompleted { step_id: "prior-step".into(), output: serde_json::from_value(json!({
diff --git a/crates/agent/tests/integration/lifetime.rs b/crates/agent/tests/integration/lifetime.rs
index c19b8e314d..963c0eb94a 100644
--- a/crates/agent/tests/integration/lifetime.rs
+++ b/crates/agent/tests/integration/lifetime.rs
@@ -92,7 +92,6 @@ fn input(base: &str, suffix: &str, effect: Arc<Effect>) -> RunInput {
         supports_vision: false,
         configuration: invocation::configuration(ToolMode::Direct),
         work: maka_agent::RunWork::Message {
-            skill_invocation: Default::default(),
             source_messages: Vec::new(),
             message: format!("question {suffix}").into(),
             tools: ToolCatalog::new([ToolRegistration {
diff --git a/crates/agent/tests/integration/model_interruption.rs b/crates/agent/tests/integration/model_interruption.rs
index 9a114c52bc..1cf24b952a 100644
--- a/crates/agent/tests/integration/model_interruption.rs
+++ b/crates/agent/tests/integration/model_interruption.rs
@@ -61,7 +61,6 @@ fn input(base: &str, suffix: &str) -> RunInput {
         supports_vision: false,
         configuration: invocation::configuration(ToolMode::Direct),
         work: maka_agent::RunWork::Message {
-            skill_invocation: Default::default(),
             source_messages: Vec::new(),
             message: format!("question {suffix}").into(),
             tools: Default::default(),
@@ -202,12 +201,12 @@ async fn continuation_claim_replays_only_its_lineage_and_retries_at_fresh_bounda
         let opening = |text: &str| Fact::InvocationOpened {
             configuration: Some(Box::new(source.configuration.clone())),
             input: InvocationInput::Message { content: text.into(), request_fingerprint: None,
-                source_messages: Vec::new(), skill_invocation: None },
+                source_messages: Vec::new(), },
         };
         let route = format!("sha256:{:x}", Sha256::digest(serde_json::to_vec(&source.provider).unwrap()));
         for fact in [
             opening("question source"),
-            Fact::ModelRequested { step_id: "old".into(), model_id: "test".into(), purpose: None, context: None,
+            Fact::ModelRequested { step_id: "old".into(), model_id: "test".into(), purpose: maka_runtime::context::ModelPurpose::Main, context: None,
                 source_scope: LogScope::Session { id: "session".into() }, source_high_water: 1,
                 source_digest: "fixture".into(), input_digest: "fixture".into(), route_identity: route,
                 checkpoint_event_id: None, effective_source_digest: None },
diff --git a/crates/agent/tests/integration/plugin_composition.rs b/crates/agent/tests/integration/plugin_composition.rs
index 4c11518d54..197f114031 100644
--- a/crates/agent/tests/integration/plugin_composition.rs
+++ b/crates/agent/tests/integration/plugin_composition.rs
@@ -156,7 +156,7 @@ async fn plugin_step_surface_survives_retry_changes_only_between_steps_and_reope
         let mut input = fixture::input(&base, "composition", false);
         let session = input.invocation.session_id.clone();
         input.work = RunWork::Message {
-            skill_invocation: None, source_messages: Vec::new(), message: "Use echo".into(),
+            source_messages: Vec::new(), message: "Use echo".into(),
             tools: ToolCatalog::default().with_plugins(catalog, Scope::Session(session.clone()), None).unwrap(),
             max_steps: 3,
         };
diff --git a/crates/agent/tests/integration/pruning.rs b/crates/agent/tests/integration/pruning.rs
index b7ff3f4685..1c6e5f8971 100644
--- a/crates/agent/tests/integration/pruning.rs
+++ b/crates/agent/tests/integration/pruning.rs
@@ -168,7 +168,6 @@ async fn settled_results_are_pruned_before_context_limits_and_survive_failed_sum
             declared_window: spent.then_some(220),
         });
         input.work = RunWork::Message {
-            skill_invocation: Default::default(),
             source_messages: Vec::new(),
             message: "read".into(),
             tools: catalog(count.clone(), repeats),
@@ -239,7 +238,7 @@ async fn settled_results_are_pruned_before_context_limits_and_survive_failed_sum
                     .filter(|stored| matches!(
                         stored.event.fact,
                         Fact::ModelRequested {
-                            purpose: Some(maka_runtime::context::ModelPurpose::Summary),
+                            purpose: maka_runtime::context::ModelPurpose::Summary,
                             ..
                         }
                     ))
@@ -256,7 +255,7 @@ async fn settled_results_are_pruned_before_context_limits_and_survive_failed_sum
             {
                 assert_eq!(
                     effective_source_digest.is_some(),
-                    purpose == Some(maka_runtime::context::ModelPurpose::Summary)
+                    purpose == maka_runtime::context::ModelPurpose::Summary
                 );
             }
         }
diff --git a/crates/agent/tests/integration/stale_pruning.rs b/crates/agent/tests/integration/stale_pruning.rs
index 4a82d35a4d..eecf6b2bca 100644
--- a/crates/agent/tests/integration/stale_pruning.rs
+++ b/crates/agent/tests/integration/stale_pruning.rs
@@ -38,8 +38,8 @@ async fn legacy(log: &EventLog, id: &str, repeats: usize) {
     let operation = format!("{step}:call-{id}");
     for fact in [
         Fact::InvocationOpened { configuration: None,
-            input: InvocationInput::Message { skill_invocation: Default::default(), source_messages: Vec::new(), content: format!("question-{id}").into(), request_fingerprint: None } },
-        Fact::ModelRequested { purpose: None, context: None, checkpoint_event_id: None, effective_source_digest: None,
+            input: InvocationInput::Message { source_messages: Vec::new(), content: format!("question-{id}").into(), request_fingerprint: None } },
+        Fact::ModelRequested { purpose: maka_runtime::context::ModelPurpose::Main, context: None, checkpoint_event_id: None, effective_source_digest: None,
             step_id: step.clone(), model_id: "test".into(), source_scope: LogScope::Session { id: "session".into() },
             source_high_water: 1, source_digest: "legacy".into(), input_digest: "legacy".into(), route_identity: "legacy".into() },
         Fact::ModelCompleted { step_id: step.clone(), output: serde_json::from_value(json!({
@@ -146,7 +146,7 @@ async fn history_pruning_binds_post_coverage_first_pages_into_summary_after_reop
         .iter()
         .find_map(|stored| match &stored.event.fact {
             Fact::ModelRequested {
-                purpose: Some(ModelPurpose::Summary),
+                purpose: ModelPurpose::Summary,
                 source_high_water,
                 effective_source_digest: Some(digest),
                 ..
diff --git a/crates/agent/tests/integration/startup_recovery.rs b/crates/agent/tests/integration/startup_recovery.rs
index 128f87495f..ae9ff6a183 100644
--- a/crates/agent/tests/integration/startup_recovery.rs
+++ b/crates/agent/tests/integration/startup_recovery.rs
@@ -307,7 +307,6 @@ async fn unknown_dispatch_stays_unknown_and_blocks_next_admission_after_reopen()
                 supports_vision: false,
                 configuration: invocation::configuration(ToolMode::Direct),
                 work: maka_agent::RunWork::Message {
-                    skill_invocation: Default::default(),
                     source_messages: Vec::new(),
                     message: "continue".into(),
                     tools: Default::default(),
diff --git a/crates/agent/tests/integration/support/agent_loop.rs b/crates/agent/tests/integration/support/agent_loop.rs
index 6ba15ef783..0b530a0579 100644
--- a/crates/agent/tests/integration/support/agent_loop.rs
+++ b/crates/agent/tests/integration/support/agent_loop.rs
@@ -96,7 +96,6 @@ pub(crate) fn input(base: &str, suffix: &str, effect: Arc<Effect>) -> RunInput {
         supports_vision: false,
         configuration: invocation::configuration(ToolMode::Direct),
         work: maka_agent::RunWork::Message {
-            skill_invocation: Default::default(),
             source_messages: Vec::new(),
             message: format!("question {suffix}").into(),
             tools: ToolCatalog::new([ToolRegistration {
@@ -128,7 +127,6 @@ fn source(
         },
         submitted_placement: maka_runtime::message::Placement::CurrentTurn,
         disposition,
-        skill_invocation: Default::default(),
         submitted_intent: None,
     }
 }
diff --git a/crates/agent/tests/integration/support/code_mode.rs b/crates/agent/tests/integration/support/code_mode.rs
index ed4bf1afbc..7345f7b92e 100644
--- a/crates/agent/tests/integration/support/code_mode.rs
+++ b/crates/agent/tests/integration/support/code_mode.rs
@@ -125,7 +125,6 @@ pub fn input(base: &str, suffix: &str, effects: Arc<Effects>) -> RunInput {
         supports_vision: false,
         configuration: invocation::configuration(ToolMode::CodeMode),
         work: maka_agent::RunWork::Message {
-            skill_invocation: Default::default(),
             source_messages: Vec::new(),
             message: format!("question {suffix}").into(),
             tools,
diff --git a/crates/agent/tests/integration/support/context.rs b/crates/agent/tests/integration/support/context.rs
index 0074372910..86beb8ddf0 100644
--- a/crates/agent/tests/integration/support/context.rs
+++ b/crates/agent/tests/integration/support/context.rs
@@ -80,7 +80,6 @@ pub fn input(base: &str, id: &str, compact: bool) -> RunInput {
             RunWork::ContextCompact
         } else {
             RunWork::Message {
-                skill_invocation: Default::default(),
                 source_messages: Vec::new(),
                 message: format!("question-{id}").into(),
                 tools: Default::default(),
diff --git a/crates/agent/tests/integration/support/image_projection.rs b/crates/agent/tests/integration/support/image_projection.rs
index 932ad50a71..82f6a585fc 100644
--- a/crates/agent/tests/integration/support/image_projection.rs
+++ b/crates/agent/tests/integration/support/image_projection.rs
@@ -71,7 +71,6 @@ pub fn input(base: &str, attachments: Value) -> RunInput {
         supports_vision: true,
         configuration: invocation::configuration(maka_tools::ToolMode::Direct),
         work: maka_agent::RunWork::Message {
-            skill_invocation: Default::default(),
             source_messages: Vec::new(),
             message: serde_json::from_value(json!({"text":"current", "attachments":attachments}))
                 .unwrap(),
diff --git a/crates/agent/tests/integration/support/recovery.rs b/crates/agent/tests/integration/support/recovery.rs
index cf8a9dd6da..b6dc5f2dc4 100644
--- a/crates/agent/tests/integration/support/recovery.rs
+++ b/crates/agent/tests/integration/support/recovery.rs
@@ -43,7 +43,6 @@ pub async fn opening(log: &EventLog) -> RuntimeEvent {
         Fact::InvocationOpened {
             configuration: None,
             input: maka_runtime::input::InvocationInput::Message {
-                skill_invocation: Default::default(),
                 source_messages: Vec::new(),
                 content: "perform the operation".into(),
                 request_fingerprint: Some("stable".into()),
@@ -68,7 +67,7 @@ pub async fn request(log: &EventLog) {
         log,
         Fact::ModelRequested {
             effective_source_digest: None,
-            purpose: None,
+            purpose: maka_runtime::context::ModelPurpose::Main,
             context: None,
             checkpoint_event_id: None,
             step_id: "step".into(),
diff --git a/crates/cli/migrations/deployment/0001_deployment.sql b/crates/cli/migrations/deployment/0001_deployment.sql
index 0444da03cd..b926a765cb 100644
--- a/crates/cli/migrations/deployment/0001_deployment.sql
+++ b/crates/cli/migrations/deployment/0001_deployment.sql
@@ -21,4 +21,16 @@ CREATE TABLE deployment (
     singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
     configuration TEXT NOT NULL CHECK (json_valid(configuration) AND length(CAST(configuration AS BLOB)) <= 65536)
 ) STRICT;
+
+-- Pending intent does not authorize startup; only the active deployment does.
+CREATE TABLE deployment_update (
+    singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
+    target TEXT NOT NULL CHECK (json_valid(target) AND length(CAST(target AS BLOB)) <= 65536)
+) STRICT;
+
+CREATE TABLE deployment_update_policy (
+    singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
+    configuration TEXT NOT NULL CHECK (json_valid(configuration) AND length(CAST(configuration AS BLOB)) <= 16384)
+) STRICT;
+
 PRAGMA user_version = 1;
diff --git a/crates/cli/migrations/deployment/0002_update.sql b/crates/cli/migrations/deployment/0002_update.sql
deleted file mode 100644
index c23b42555f..0000000000
--- a/crates/cli/migrations/deployment/0002_update.sql
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
--- The Active reader remains version 1. Pending intent does not authorize startup.
-CREATE TABLE deployment_update (
-    singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
-    target TEXT NOT NULL CHECK (json_valid(target) AND length(CAST(target AS BLOB)) <= 65536)
-) STRICT;
diff --git a/crates/cli/migrations/deployment/0003_update_policy.sql b/crates/cli/migrations/deployment/0003_update_policy.sql
deleted file mode 100644
index 80b943adeb..0000000000
--- a/crates/cli/migrations/deployment/0003_update_policy.sql
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
--- Optional authority; old read-only launchers still use the stable Active row.
-CREATE TABLE deployment_update_policy (
-    singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
-    configuration TEXT NOT NULL CHECK (json_valid(configuration) AND length(CAST(configuration AS BLOB)) <= 16384)
-) STRICT;
diff --git a/crates/cli/src/deployment/policy/storage.rs b/crates/cli/src/deployment/policy/storage.rs
index a3b8afac20..f18be562da 100644
--- a/crates/cli/src/deployment/policy/storage.rs
+++ b/crates/cli/src/deployment/policy/storage.rs
@@ -31,12 +31,7 @@ pub(in crate::deployment) async fn read(directory: &Path) -> Result<Record, Host
             .read_only(true),
     )
     .await?;
-    let result = async {
-        let exists: bool = sqlx::query_scalar(
-            "SELECT EXISTS(SELECT 1 FROM sqlite_schema WHERE type='table' AND name='deployment_update_policy')"
-        ).fetch_one(&mut connection).await?;
-        if exists { Ok::<_, HostError>(load(&mut connection).await?) } else { Ok(Record::default()) }
-    }.await;
+    let result = load(&mut connection).await;
     let closed = connection.close().await;
     let record = result?;
     closed?;
diff --git a/crates/cli/src/deployment/query.rs b/crates/cli/src/deployment/query.rs
index 1154f1c096..6c40b0bd7b 100644
--- a/crates/cli/src/deployment/query.rs
+++ b/crates/cli/src/deployment/query.rs
@@ -171,8 +171,6 @@ pub(super) async fn pending(
     directory: &Path,
     current: &Deployment,
 ) -> Result<Option<Deployment>, HostError> {
-    // Earlier installed operators have only the Active table. Observation must
-    // not run migrations or create a pending table on their behalf.
     let mut connection = SqliteConnection::connect_with(
         &SqliteConnectOptions::new()
             .filename(directory.join("deployment.sqlite"))
@@ -180,10 +178,6 @@ pub(super) async fn pending(
     )
     .await?;
     let result = async {
-        let exists: bool = sqlx::query_scalar(
-            "SELECT EXISTS(SELECT 1 FROM sqlite_schema WHERE type='table' AND name='deployment_update')",
-        ).fetch_one(&mut connection).await?;
-        if !exists { return Ok(None) }
         let value: Option<String> = sqlx::query_scalar(
             "SELECT CASE WHEN length(CAST(target AS BLOB)) <= 65536 THEN target ELSE '' END FROM deployment_update WHERE singleton = 1",
         ).fetch_optional(&mut connection).await?;
diff --git a/crates/cli/src/deployment/store.rs b/crates/cli/src/deployment/store.rs
index aca1b9b0bd..5c053792a1 100644
--- a/crates/cli/src/deployment/store.rs
+++ b/crates/cli/src/deployment/store.rs
@@ -30,7 +30,6 @@ use std::{fs::File, path::Path, sync::Arc};
 const DATABASE: &str = "deployment.sqlite";
 const APPLICATION_ID: i64 = 0x4d414b44;
 static MIGRATIONS: sqlx::migrate::Migrator = sqlx::migrate!("./migrations/deployment");
-// user_version describes the stable Active reader, not the SQLx migration count.
 
 pub(super) enum Installation {
     Missing,
@@ -54,6 +53,7 @@ pub(super) async fn read(directory: &Path) -> Result<Installation, HostError> {
         SqliteConnection::connect_with(&SqliteConnectOptions::new().filename(path).read_only(true))
             .await?;
     let result = async {
+        validate_schema(&mut connection).await?;
         let application: i64 = sqlx::query_scalar("PRAGMA application_id").fetch_one(&mut connection).await?;
         let version: i64 = sqlx::query_scalar("PRAGMA user_version").fetch_one(&mut connection).await?;
         if version == 0 && application == APPLICATION_ID {
@@ -274,6 +274,18 @@ fn private_file(path: &Path) -> Result<File, HostError> {
 }
 
 async fn initialize(connection: &mut SqliteConnection) -> Result<(), StoreError> {
+    validate_schema(connection).await?;
+    sqlx::raw_sql(
+        "PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL; PRAGMA application_id=1296124740;",
+    )
+    .execute(&mut *connection)
+    .await?;
+    Ok(())
+}
+
+/// Reject unsupported stores before either a reader serves them or a writer
+/// changes journaling or creates SQLx metadata. Version zero is initial setup only.
+async fn validate_schema(connection: &mut SqliteConnection) -> Result<(), StoreError> {
     let application: i64 = sqlx::query_scalar("PRAGMA application_id")
         .fetch_one(&mut *connection)
         .await?;
@@ -292,11 +304,35 @@ async fn initialize(connection: &mut SqliteConnection) -> Result<(), StoreError>
             "unsupported deployment database".into(),
         ));
     }
-    sqlx::raw_sql(
-        "PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL; PRAGMA application_id=1296124740;",
+    let ledger: bool = sqlx::query_scalar(
+        "SELECT EXISTS(SELECT 1 FROM sqlite_schema WHERE type='table' AND name='_sqlx_migrations')",
     )
-    .execute(&mut *connection)
+    .fetch_one(&mut *connection)
     .await?;
+    let applied: Vec<(i64, bool, Vec<u8>)> = if ledger {
+        sqlx::query_as("SELECT version, success, checksum FROM _sqlx_migrations ORDER BY version")
+            .fetch_all(&mut *connection)
+            .await?
+    } else {
+        Vec::new()
+    };
+    let valid = if version == 0 {
+        tables == i64::from(ledger) && applied.is_empty()
+    } else {
+        applied.len() == MIGRATIONS.iter().len()
+            && applied.iter().zip(MIGRATIONS.iter()).all(
+                |((version, success, checksum), expected)| {
+                    *version == expected.version
+                        && *success
+                        && checksum.as_slice() == expected.checksum.as_ref()
+                },
+            )
+    };
+    if !valid {
+        return Err(StoreError::InvalidTransition(
+            "unsupported deployment schema".into(),
+        ));
+    }
     Ok(())
 }
 
diff --git a/crates/cli/src/deployment/updates.rs b/crates/cli/src/deployment/updates.rs
index f723c6cfb4..0689b2395c 100644
--- a/crates/cli/src/deployment/updates.rs
+++ b/crates/cli/src/deployment/updates.rs
@@ -194,8 +194,7 @@ mod tests {
             .await
             .unwrap();
         writer.run(|connection| Box::pin(async move {
-            // Model the published predecessor and a failure after migration.
-            sqlx::raw_sql("DROP TABLE deployment_update_policy; DELETE FROM _sqlx_migrations WHERE version = 3; CREATE TRIGGER reject_prepare BEFORE INSERT ON deployment_update BEGIN SELECT RAISE(ABORT, 'prepare failed'); END;")
+            sqlx::raw_sql("CREATE TRIGGER reject_prepare BEFORE INSERT ON deployment_update BEGIN SELECT RAISE(ABORT, 'prepare failed'); END;")
                 .execute(connection).await?;
             Ok(())
         })).await.unwrap();
@@ -208,15 +207,17 @@ mod tests {
         let writer = store::open_update_writer(&directory, lease.clone())
             .await
             .unwrap();
-        writer.run(|connection| Box::pin(async move {
-            let migrated: bool = sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM _sqlx_migrations WHERE version = 3)")
-                .fetch_one(&mut *connection).await?;
-            let table: bool = sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM sqlite_schema WHERE name = 'deployment_update_policy')")
-                .fetch_one(&mut *connection).await?;
-            assert!(!migrated && !table, "failed preparation must also roll back migrations");
-            sqlx::query("DROP TRIGGER reject_prepare").execute(connection).await?;
-            Ok(())
-        })).await.unwrap();
+        writer
+            .run(|connection| {
+                Box::pin(async move {
+                    sqlx::query("DROP TRIGGER reject_prepare")
+                        .execute(connection)
+                        .await?;
+                    Ok(())
+                })
+            })
+            .await
+            .unwrap();
         writer.close().await.unwrap();
         assert_eq!(
             read(&directory, lease.clone()).await.unwrap(),
@@ -248,7 +249,7 @@ mod tests {
         prepare(&directory, lease.clone(), current.clone(), target.clone())
             .await
             .unwrap();
-        // Old startup readers still see the sole authorized version while an
+        // Startup readers still see the sole authorized version while an
         // updater is absent. A different target cannot overwrite its intent.
         let store::Installation::Installed(active) = store::read(&directory).await.unwrap() else {
             panic!("pending intent must not change the startup reader contract");
@@ -372,7 +373,36 @@ mod tests {
         )
         .await
         .unwrap();
-        assert_eq!(read(&directory, lease).await.unwrap(), (active, None));
+        assert_eq!(
+            read(&directory, lease.clone()).await.unwrap(),
+            (active, None)
+        );
+        let writer = store::open_writer(&directory, lease.clone(), None)
+            .await
+            .unwrap();
+        writer
+            .run(|connection| {
+                Box::pin(async move {
+                    sqlx::raw_sql(
+                        "PRAGMA journal_mode=DELETE; UPDATE _sqlx_migrations SET checksum = X'00';",
+                    )
+                    .execute(connection)
+                    .await?;
+                    Ok(())
+                })
+            })
+            .await
+            .unwrap();
+        writer.close().await.unwrap();
+        let path = directory.join("deployment.sqlite");
+        let before = std::fs::read(&path).unwrap();
+        assert!(store::read(&directory).await.is_err());
+        assert!(store::open_writer(&directory, lease, None).await.is_err());
+        assert_eq!(
+            std::fs::read(path).unwrap(),
+            before,
+            "unsupported stores must not be rewritten"
+        );
         drop(owner);
         drop(RootOwner::open(&root_path, &namespaces).unwrap());
     }
diff --git a/crates/cli/src/host_client.rs b/crates/cli/src/host_client.rs
index 808d52f919..73d66e7b08 100644
--- a/crates/cli/src/host_client.rs
+++ b/crates/cli/src/host_client.rs
@@ -90,7 +90,6 @@ impl HostClient {
             writer
                 .write(&ClientHello {
                     client_instance_id: format!("maka-operator-{}", uuid::Uuid::new_v4()),
-                    activity_snapshot_version: Some(2),
                     protocol_min: 0,
                     protocol_max: 0,
                     compatibility_epoch: COMPATIBILITY_EPOCH,
diff --git a/crates/cli/tests/integration/candidate.rs b/crates/cli/tests/integration/candidate.rs
index 468bbd64ee..d0f5497ba3 100644
--- a/crates/cli/tests/integration/candidate.rs
+++ b/crates/cli/tests/integration/candidate.rs
@@ -321,7 +321,6 @@ impl Drop for CandidateFixture {
                 let _ = fs::remove_dir_all(deployment);
             }
         }
-        let _ = fs::remove_file(control.join("owner.lock"));
         let _ = fs::remove_dir(control);
         let _ = fs::remove_file(&self.lock);
     }
diff --git a/crates/client-capability/src/broker.rs b/crates/client-capability/src/broker.rs
index 5ed6de2879..5e9d3c0982 100644
--- a/crates/client-capability/src/broker.rs
+++ b/crates/client-capability/src/broker.rs
@@ -136,8 +136,11 @@ impl Broker {
         let frame = frame(invocation_id.clone());
         let tool = match &frame {
             HostFrame::Call {
-                session_id,
-                turn_id,
+                source:
+                    maka_runtime::capability::CallSource::Agent {
+                        session_id,
+                        turn_id,
+                    },
                 tool_call_id,
                 server_id,
                 tool_name,
diff --git a/crates/client-capability/src/broker/dispatch.rs b/crates/client-capability/src/broker/dispatch.rs
index 43cc581103..cc1bf815b9 100644
--- a/crates/client-capability/src/broker/dispatch.rs
+++ b/crates/client-capability/src/broker/dispatch.rs
@@ -19,7 +19,7 @@
 
 use super::{Broker, CallError, PendingCall};
 use crate::Registration;
-use maka_runtime::capability::{HostFrame, HostPathAccess};
+use maka_runtime::capability::{CallSource, HostFrame, HostPathAccess};
 use serde_json::{Map, Value};
 use std::{sync::Arc, time::Duration};
 use tokio_util::sync::CancellationToken;
@@ -38,8 +38,7 @@ pub struct ToolCall {
     pub server_id: String,
     pub tool_name: String,
     pub arguments: Map<String, Value>,
-    pub session_id: String,
-    pub turn_id: String,
+    pub source: CallSource,
     pub tool_call_id: String,
     pub cwd: String,
 }
@@ -111,8 +110,7 @@ impl Broker {
                 server_id: call.server_id,
                 tool_name: call.tool_name,
                 arguments: call.arguments,
-                session_id: call.session_id,
-                turn_id: call.turn_id,
+                source: call.source,
                 tool_call_id: call.tool_call_id,
                 cwd,
             }
diff --git a/crates/client-capability/tests/client_service.rs b/crates/client-capability/tests/client_service.rs
index 6cd656be13..a2efac67ce 100644
--- a/crates/client-capability/tests/client_service.rs
+++ b/crates/client-capability/tests/client_service.rs
@@ -193,8 +193,17 @@ async fn service_roundtrip() {
                         server_id: format!("test-server-{access}"),
                         tool_name: "write".into(),
                         arguments: serde_json::from_value(json!({"text": access})).unwrap(),
-                        session_id: "interop-session".into(),
-                        turn_id: "interop-turn".into(),
+                        source: if index == 0 {
+                            maka_runtime::capability::CallSource::Agent {
+                                session_id: "interop-session".into(),
+                                turn_id: "interop-turn".into(),
+                            }
+                        } else {
+                            maka_runtime::capability::CallSource::Background {
+                                session_id: None,
+                                grant_id: "authorized-grant".into(),
+                            }
+                        },
                         tool_call_id: format!("interop-call-{access}"),
                         cwd: workspace.path().to_str().unwrap().into(),
                     },
diff --git a/crates/client-capability/tests/journal_admission.rs b/crates/client-capability/tests/journal_admission.rs
index 1d7a0bcd4a..8cf6d24d0f 100644
--- a/crates/client-capability/tests/journal_admission.rs
+++ b/crates/client-capability/tests/journal_admission.rs
@@ -70,7 +70,6 @@ async fn run(boundary: Boundary) {
             Fact::InvocationOpened {
                 configuration: None,
                 input: InvocationInput::Message {
-                    skill_invocation: Default::default(),
                     source_messages: Vec::new(),
                     content: "invoke".into(),
                     request_fingerprint: None,
@@ -118,8 +117,10 @@ async fn run(boundary: Boundary) {
                 server_id: "server".into(),
                 tool_name: "effect".into(),
                 arguments: serde_json::Map::new(),
-                session_id: invocation.session_id.clone(),
-                turn_id: invocation.turn_id.clone(),
+                source: maka_runtime::capability::CallSource::Agent {
+                    session_id: invocation.session_id.clone(),
+                    turn_id: invocation.turn_id.clone(),
+                },
                 tool_call_id: "call".into(),
                 cwd: "/host/private/workspace".into(),
             },
diff --git a/crates/config/migrations/0001_configuration.sql b/crates/config/migrations/0001_configuration.sql
index 6f9e1521d6..a0646847bc 100644
--- a/crates/config/migrations/0001_configuration.sql
+++ b/crates/config/migrations/0001_configuration.sql
@@ -17,28 +17,70 @@
  * under the License.
  */
 
-CREATE TABLE IF NOT EXISTS connection_catalog (
+CREATE TABLE connection_catalog (
     singleton INTEGER PRIMARY KEY CHECK(singleton = 1),
     revision INTEGER NOT NULL CHECK(revision >= 0),
     default_target TEXT
 );
-INSERT OR IGNORE INTO connection_catalog VALUES(1, 0, NULL);
-CREATE TABLE IF NOT EXISTS connections (
+INSERT INTO connection_catalog VALUES(1, 0, NULL);
+CREATE TABLE connections (
     connection_id TEXT PRIMARY KEY,
     slug TEXT NOT NULL UNIQUE,
     revision INTEGER NOT NULL CHECK(revision > 0),
     document TEXT NOT NULL
 );
-CREATE TABLE IF NOT EXISTS credential_vault (
+CREATE TABLE credential_vault (
     singleton INTEGER PRIMARY KEY CHECK(singleton = 1),
     revision INTEGER NOT NULL CHECK(revision >= 0)
 );
-INSERT OR IGNORE INTO credential_vault VALUES(1, 0);
-CREATE TABLE IF NOT EXISTS credentials (
+INSERT INTO credential_vault VALUES(1, 0);
+CREATE TABLE credentials (
     locator TEXT PRIMARY KEY,
     credential_id TEXT NOT NULL UNIQUE,
     revision INTEGER NOT NULL CHECK(revision > 0),
     secret TEXT NOT NULL,
     updated_at INTEGER NOT NULL
 );
+
+CREATE TABLE access_credentials (
+    document TEXT NOT NULL CHECK(json_valid(document) AND length(CAST(document AS BLOB)) <= 524288),
+    credential_id TEXT GENERATED ALWAYS AS (json_extract(document, '$.credentialId')) STORED NOT NULL UNIQUE,
+    credential_hash TEXT GENERATED ALWAYS AS (json_extract(document, '$.credentialHash')) STORED NOT NULL UNIQUE
+        CHECK(length(credential_hash) = 64)
+);
+
+CREATE TABLE runtime_policy (
+    singleton INTEGER PRIMARY KEY CHECK(singleton = 1),
+    document TEXT NOT NULL CHECK(
+        json_valid(document) AND length(CAST(document AS BLOB)) <= 49152
+    )
+);
+
+-- Receipts survive logout/removal: they prove completion, not current authorization.
+CREATE TABLE oauth_login_receipts (
+    completion_order INTEGER PRIMARY KEY AUTOINCREMENT,
+    attempt_id TEXT NOT NULL UNIQUE CHECK(length(attempt_id) BETWEEN 1 AND 128),
+    target TEXT NOT NULL,
+    connection TEXT NOT NULL
+);
+
+CREATE TABLE plugin_credentials (
+    package_id TEXT NOT NULL,
+    scope_id TEXT NOT NULL,
+    key TEXT NOT NULL,
+    revision INTEGER NOT NULL CHECK(revision BETWEEN 1 AND 9007199254740991),
+    secret TEXT CHECK(secret IS NULL OR length(CAST(secret AS BLOB)) <= 65536),
+    PRIMARY KEY (package_id, scope_id, key)
+) STRICT;
+
+CREATE TABLE plugin_authorizations (
+    id TEXT PRIMARY KEY NOT NULL,
+    package_id TEXT NOT NULL,
+    scope_id TEXT NOT NULL,
+    operation_id TEXT NOT NULL,
+    document TEXT NOT NULL CHECK(length(CAST(document AS BLOB)) <= 65536),
+    revoked INTEGER NOT NULL DEFAULT 0 CHECK(revoked IN (0, 1)),
+    UNIQUE(package_id, scope_id, operation_id)
+) STRICT;
+
 PRAGMA user_version = 1;
diff --git a/crates/config/migrations/0002_access_credentials.sql b/crates/config/migrations/0002_access_credentials.sql
deleted file mode 100644
index 5d5245cef8..0000000000
--- a/crates/config/migrations/0002_access_credentials.sql
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-CREATE TABLE access_credentials (
-    document TEXT NOT NULL CHECK(json_valid(document) AND length(CAST(document AS BLOB)) <= 524288),
-    credential_id TEXT GENERATED ALWAYS AS (json_extract(document, '$.credentialId')) STORED NOT NULL UNIQUE,
-    credential_hash TEXT GENERATED ALWAYS AS (json_extract(document, '$.credentialHash')) STORED NOT NULL UNIQUE
-        CHECK(length(credential_hash) = 64)
-);
-PRAGMA user_version = 2;
diff --git a/crates/config/migrations/0003_runtime_policy.sql b/crates/config/migrations/0003_runtime_policy.sql
deleted file mode 100644
index ede6a6a68d..0000000000
--- a/crates/config/migrations/0003_runtime_policy.sql
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-
-CREATE TABLE runtime_policy (
-    singleton INTEGER PRIMARY KEY CHECK(singleton = 1),
-    document TEXT NOT NULL CHECK(
-        json_valid(document) AND length(CAST(document AS BLOB)) <= 49152
-    )
-);
-
-PRAGMA user_version = 3;
diff --git a/crates/config/migrations/0004_model_facts.sql b/crates/config/migrations/0004_model_facts.sql
deleted file mode 100644
index e0c95b389a..0000000000
--- a/crates/config/migrations/0004_model_facts.sql
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-
--- This is the catalog's observed projection basis, not another revision.
-ALTER TABLE connection_catalog ADD COLUMN model_facts_fingerprint TEXT
-    CHECK(model_facts_fingerprint IS NULL OR length(model_facts_fingerprint) = 71);
-PRAGMA user_version = 4;
diff --git a/crates/config/migrations/0005_oauth_login.sql b/crates/config/migrations/0005_oauth_login.sql
deleted file mode 100644
index 2b72345e83..0000000000
--- a/crates/config/migrations/0005_oauth_login.sql
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
--- Receipts survive logout/removal: they prove completion, not current authorization.
-CREATE TABLE oauth_login_receipts (
-    completion_order INTEGER PRIMARY KEY AUTOINCREMENT,
-    attempt_id TEXT NOT NULL UNIQUE CHECK(length(attempt_id) BETWEEN 1 AND 128),
-    target TEXT NOT NULL,
-    connection TEXT NOT NULL
-);
-PRAGMA user_version = 5;
diff --git a/crates/config/migrations/0006_external_agents.sql b/crates/config/migrations/0006_external_agents.sql
deleted file mode 100644
index bf9723de06..0000000000
--- a/crates/config/migrations/0006_external_agents.sql
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
--- Preserve full old documents when the new field exceeds the wire budget.
--- Normal writes still enforce 48 KiB; recovery must not prevent shrinking them.
-CREATE TABLE runtime_policy_next (
-    singleton INTEGER PRIMARY KEY CHECK(singleton = 1),
-    document TEXT NOT NULL CHECK(
-        json_valid(document) AND length(CAST(document AS BLOB)) <= 65536
-    )
-);
-INSERT INTO runtime_policy_next
-SELECT singleton, json_insert(document, '$.policy.externalAgents',
-    json('{"antigravity":{"executable":""}}')) FROM runtime_policy;
-DROP TABLE runtime_policy;
-ALTER TABLE runtime_policy_next RENAME TO runtime_policy;
-
-PRAGMA user_version = 6;
diff --git a/crates/config/migrations/0007_model_overrides.sql b/crates/config/migrations/0007_model_overrides.sql
deleted file mode 100644
index 4f7accd4fa..0000000000
--- a/crates/config/migrations/0007_model_overrides.sql
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
--- A legacy relay window also declared proactive compaction. Preserve both
--- meanings while new writes keep capacity and compaction independent.
-UPDATE connections
-SET document = json_remove(
-    json_insert(document, '$.modelOverrides', json((
-        SELECT json_group_object(key, json(
-            CASE WHEN json_type(value, '$.contextWindow') = 'integer'
-                THEN json_insert(value, '$.compactionThreshold',
-                    json_extract(value, '$.contextWindow'))
-                ELSE value END))
-        FROM json_each(connections.document, '$.relayModelProfiles')
-    ))),
-    '$.relayModelProfiles'
-)
-WHERE json_type(document, '$.relayModelProfiles') = 'object';
-
-PRAGMA user_version = 7;
diff --git a/crates/config/migrations/0008_connection_model_authority.sql b/crates/config/migrations/0008_connection_model_authority.sql
deleted file mode 100644
index 0f11c59525..0000000000
--- a/crates/config/migrations/0008_connection_model_authority.sql
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
--- Verification tied to an external facts file cannot establish the new
--- connection-owned wire. Leave external files untouched; never import TS state.
-UPDATE connections
-SET document = json_remove(document, '$.lastTest', '$.lastTestModelFactsFingerprint')
-WHERE json_type(document, '$.lastTestModelFactsFingerprint') IS NOT NULL;
-
-ALTER TABLE connection_catalog DROP COLUMN model_facts_fingerprint;
-PRAGMA user_version = 8;
diff --git a/crates/config/migrations/0009_skill_preferences.sql b/crates/config/migrations/0009_skill_preferences.sql
deleted file mode 100644
index 776f59ca19..0000000000
--- a/crates/config/migrations/0009_skill_preferences.sql
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
--- Root-owned preferences are control state, not model history or imported TS data.
-CREATE TABLE skill_preferences (
-    ref TEXT PRIMARY KEY NOT NULL CHECK (length(CAST(ref AS BLOB)) BETWEEN 1 AND 512),
-    enabled INTEGER NOT NULL CHECK (enabled IN (0, 1)),
-    pinned INTEGER NOT NULL CHECK (pinned IN (0, 1))
-) STRICT;
-CREATE TABLE skill_preferences_revision (
-    singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
-    revision INTEGER NOT NULL CHECK (revision BETWEEN 0 AND 9007199254740991)
-) STRICT;
-INSERT INTO skill_preferences_revision VALUES (1, 0);
-PRAGMA user_version = 9;
-
diff --git a/crates/config/migrations/0010_plugin_credentials.sql b/crates/config/migrations/0010_plugin_credentials.sql
deleted file mode 100644
index cc0ad074d2..0000000000
--- a/crates/config/migrations/0010_plugin_credentials.sql
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-CREATE TABLE plugin_credentials (
-    package_id TEXT NOT NULL,
-    scope_id TEXT NOT NULL,
-    key TEXT NOT NULL,
-    revision INTEGER NOT NULL CHECK(revision BETWEEN 1 AND 9007199254740991),
-    secret TEXT CHECK(secret IS NULL OR length(CAST(secret AS BLOB)) <= 65536),
-    PRIMARY KEY (package_id, scope_id, key)
-) STRICT;
-PRAGMA user_version = 10;
diff --git a/crates/config/src/database.rs b/crates/config/src/database.rs
index 5988e65f68..c97d0b406c 100644
--- a/crates/config/src/database.rs
+++ b/crates/config/src/database.rs
@@ -107,21 +107,37 @@ async fn initialize(connection: &mut SqliteConnection) -> std::result::Result<()
     .fetch_one(&mut *connection)
     .await?;
     if !((application_id == 0 && version == 0 && tables == 0)
-        || (application_id == 0x4d414b43 && matches!(version, 0..=10)))
+        || (application_id == 0x4d414b43 && matches!(version, 0..=1)))
     {
         return Err(StoreError::UnsupportedDatabase);
     }
-    // Stamp identity before SQLx creates its ledger so interrupted setup can retry.
-    sqlx::raw_sql(
-        "PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL; PRAGMA secure_delete = ON;",
+    let ledger: bool = sqlx::query_scalar(
+        "SELECT EXISTS(SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name = '_sqlx_migrations')",
     )
-    .execute(&mut *connection)
+    .fetch_one(&mut *connection)
     .await?;
+    if (version == 0 && tables != i64::from(ledger)) || (version == 1 && !ledger) {
+        return Err(StoreError::UnsupportedDatabase);
+    }
+    if ledger {
+        let applied: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM _sqlx_migrations")
+            .fetch_one(&mut *connection)
+            .await?;
+        if (version == 0) != (applied == 0) {
+            return Err(StoreError::UnsupportedDatabase);
+        }
+    }
+    // Stamp identity before SQLx creates its ledger so interrupted setup can retry.
     if application_id == 0 {
         sqlx::query("PRAGMA application_id = 1296124739")
             .execute(&mut *connection)
             .await?;
     }
     MIGRATIONS.run_direct(None, &mut *connection, false).await?;
+    sqlx::raw_sql(
+        "PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL; PRAGMA secure_delete = ON;",
+    )
+    .execute(&mut *connection)
+    .await?;
     Ok(())
 }
diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs
index 8f2a99cee8..ff6bb02863 100644
--- a/crates/config/src/lib.rs
+++ b/crates/config/src/lib.rs
@@ -30,10 +30,10 @@ pub mod model_fetch;
 pub mod network;
 pub mod oauth;
 pub mod onboarding;
+pub mod plugin_authorization;
 mod plugin_credentials;
 mod policy;
 pub mod projection;
-pub mod skills;
 mod vault;
 
 pub use maka_event_log::connection::BoxFuture;
diff --git a/crates/config/src/plugin_authorization.rs b/crates/config/src/plugin_authorization.rs
new file mode 100644
index 0000000000..1eca7dd33d
--- /dev/null
+++ b/crates/config/src/plugin_authorization.rs
@@ -0,0 +1,187 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+//! Host consent authority. Plugin data may refer to these records but cannot
+//! create, edit or reactivate them through the namespaced data API.
+
+use crate::{ConfigError, ConfigurationStore, Result, TransactionMode};
+pub use maka_plugins::authorization::Boundary;
+use maka_plugins::{
+    authorization::{Grant, Id, Request},
+    storage::Namespace,
+};
+use serde::{Deserialize, Serialize};
+use sqlx::{Row, sqlite::SqliteRow};
+use uuid::Uuid;
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+#[serde(
+    tag = "kind",
+    rename_all = "snake_case",
+    rename_all_fields = "camelCase",
+    deny_unknown_fields
+)]
+pub enum Principal {
+    LocalUser {
+        client_instance_id: String,
+    },
+    Credential {
+        credential_id: String,
+        client_instance_id: String,
+    },
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+struct Document {
+    request: Request,
+    principal: Principal,
+    boundary: Boundary,
+}
+pub struct Record {
+    pub grant: Grant,
+    pub principal: Principal,
+    pub boundary: Boundary,
+}
+pub enum Approval {
+    Granted(Box<Record>),
+    Conflict,
+}
+
+impl ConfigurationStore {
+    /// Exact retry returns the original boundary, including its revocation.
+    /// Re-approval requires a new operation identity and explicit consent.
+    pub async fn approve_plugin_authorization(
+        &self,
+        namespace: Namespace,
+        principal: Principal,
+        request: Request,
+        boundary: Boundary,
+    ) -> Result<Approval> {
+        request
+            .validate()
+            .map_err(|error| ConfigError::Invalid(error.to_string()))?;
+        self.transaction(TransactionMode::Immediate, move |connection| {
+            Box::pin(async move {
+            let scope = String::from(namespace.scope().clone());
+            let previous = sqlx::query("SELECT id, document, revoked FROM plugin_authorizations WHERE package_id = ? AND scope_id = ? AND operation_id = ?")
+                .bind(namespace.package()).bind(&scope).bind(request.operation_id.to_string())
+                .fetch_optional(&mut *connection).await?;
+            if let Some(row) = previous {
+                let record = decode(row)?;
+                return Ok(if record.grant.request == request && record.principal == principal {
+                    Approval::Granted(Box::new(record))
+                } else {
+                    Approval::Conflict
+                });
+            }
+            boundary.validate(&request)
+                .map_err(|error| ConfigError::Invalid(error.to_string()))?;
+            let id = Id(Uuid::new_v4());
+            let document = Document {
+                request,
+                principal,
+                boundary,
+            };
+            let bytes = serde_json::to_string(&document)?;
+            if bytes.len() > 64 * 1024 {
+                return Err(ConfigError::Invalid("authorization exceeds 64 KiB".into()));
+            }
+            sqlx::query("INSERT INTO plugin_authorizations (id, package_id, scope_id, operation_id, document) VALUES (?, ?, ?, ?, ?)")
+                .bind(id.0.to_string()).bind(namespace.package()).bind(scope).bind(document.request.operation_id.to_string()).bind(bytes)
+                .execute(&mut *connection).await?;
+            Ok(Approval::Granted(Box::new(Record {
+                grant: Grant {
+                    id,
+                    request: document.request,
+                    revoked: false,
+                },
+                principal: document.principal,
+                boundary: document.boundary,
+            })))
+            })
+        }).await
+    }
+
+    /// Read does not confer authority. Host must also check the current principal,
+    /// workspace identity, Session boundary and live plugin ownership before use.
+    pub async fn plugin_authorization(
+        &self,
+        namespace: Namespace,
+        id: Id,
+    ) -> Result<Option<Record>> {
+        self.transaction(TransactionMode::Deferred, move |connection| {
+            Box::pin(async move {
+            sqlx::query("SELECT id, document, revoked FROM plugin_authorizations WHERE id = ? AND package_id = ? AND scope_id = ?")
+                .bind(id.0.to_string()).bind(namespace.package()).bind(String::from(namespace.scope().clone()))
+                .fetch_optional(&mut *connection).await?.map(decode).transpose()
+            })
+        }).await
+    }
+
+    /// Recover an approval receipt without recapturing a potentially changed
+    /// target. The caller must compare both proposal and issuing principal.
+    pub async fn plugin_authorization_operation(
+        &self,
+        namespace: Namespace,
+        operation_id: Uuid,
+    ) -> Result<Option<Record>> {
+        self.transaction(TransactionMode::Deferred, move |connection| {
+            Box::pin(async move {
+                sqlx::query("SELECT id, document, revoked FROM plugin_authorizations WHERE package_id = ? AND scope_id = ? AND operation_id = ?")
+                    .bind(namespace.package()).bind(String::from(namespace.scope().clone())).bind(operation_id.to_string())
+                    .fetch_optional(&mut *connection).await?.map(decode).transpose()
+            })
+        }).await
+    }
+
+    /// Idempotent and monotonic; never delete the idempotency tombstone.
+    pub async fn revoke_plugin_authorization(&self, namespace: Namespace, id: Id) -> Result<()> {
+        self.transaction(TransactionMode::Immediate, move |connection| {
+            Box::pin(async move {
+            sqlx::query("UPDATE plugin_authorizations SET revoked = 1 WHERE id = ? AND package_id = ? AND scope_id = ?")
+                .bind(id.0.to_string()).bind(namespace.package()).bind(String::from(namespace.scope().clone()))
+                .execute(&mut *connection).await?;
+            Ok(())
+            })
+        }).await
+    }
+}
+
+fn decode(row: SqliteRow) -> Result<Record> {
+    let id: String = row.try_get("id")?;
+    let document: Document = serde_json::from_str(row.try_get("document")?)?;
+    document
+        .request
+        .validate()
+        .map_err(|error| ConfigError::Invalid(error.to_string()))?;
+    document
+        .boundary
+        .validate(&document.request)
+        .map_err(|error| ConfigError::Invalid(error.to_string()))?;
+    Ok(Record {
+        grant: Grant {
+            id: Id(Uuid::parse_str(&id).map_err(|error| ConfigError::Invalid(error.to_string()))?),
+            request: document.request,
+            revoked: row.try_get::<i64, _>("revoked")? != 0,
+        },
+        principal: document.principal,
+        boundary: document.boundary,
+    })
+}
diff --git a/crates/config/src/policy.rs b/crates/config/src/policy.rs
index be8c15dba1..9f5ab6837f 100644
--- a/crates/config/src/policy.rs
+++ b/crates/config/src/policy.rs
@@ -21,16 +21,12 @@ use crate::{ConfigError, ConfigurationStore, Result, TransactionMode};
 use maka_runtime::configuration::{
     policy::{
         ChatDefaults, EnabledPolicy, MAX_POLICY_SNAPSHOT_BYTES, Personalization, RuntimePolicy,
-        RuntimePolicyMutationResult, RuntimePolicySnapshot, decode_canonical_document,
+        RuntimePolicyMutationResult, RuntimePolicySnapshot, decode_canonical_snapshot,
     },
     validation::{MAX_SAFE_INTEGER, revision},
 };
 use sqlx::SqliteConnection;
 
-// Matches migration 0006. Recovery has headroom for schema growth; ordinary
-// writes still require a policy that fits the unchanged 48 KiB wire budget.
-const MAX_POLICY_DOCUMENT_BYTES: usize = 64 * 1024;
-
 impl ConfigurationStore {
     pub async fn set_subagents(
         &self,
@@ -162,7 +158,7 @@ pub(crate) async fn read(connection: &mut SqliteConnection) -> Result<RuntimePol
             policy: RuntimePolicy::default(),
         });
     };
-    if size < 0 || size > MAX_POLICY_DOCUMENT_BYTES as i64 {
+    if size < 0 || size > MAX_POLICY_SNAPSHOT_BYTES as i64 {
         return Err(ConfigError::UnsupportedDatabase);
     }
     let document: String =
@@ -170,5 +166,5 @@ pub(crate) async fn read(connection: &mut SqliteConnection) -> Result<RuntimePol
             .fetch_one(connection)
             .await?;
     let value = serde_json::from_str(&document)?;
-    decode_canonical_document(value).map_err(|_| ConfigError::UnsupportedDatabase)
+    decode_canonical_snapshot(value).map_err(|_| ConfigError::UnsupportedDatabase)
 }
diff --git a/crates/config/src/skills.rs b/crates/config/src/skills.rs
deleted file mode 100644
index 0fef735424..0000000000
--- a/crates/config/src/skills.rs
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-use crate::{ConfigError, ConfigurationStore, Result, TransactionMode};
-use maka_runtime::{configuration::validation::MAX_SAFE_INTEGER, skills::SkillPreference};
-use sqlx::SqliteConnection;
-use std::collections::BTreeMap;
-
-const MAX_PREFERENCES: usize = 16_384;
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct SkillPreferences {
-    pub revision: u64,
-    pub entries: BTreeMap<String, SkillPreference>,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum PreferenceUpdate {
-    Committed { revision: u64 },
-    Conflict { actual_revision: u64 },
-}
-
-impl ConfigurationStore {
-    pub async fn skill_preferences(&self) -> Result<SkillPreferences> {
-        self.transaction(TransactionMode::Deferred, |connection| {
-            Box::pin(read(connection))
-        })
-        .await
-    }
-
-    /// The catalog's later filesystem revision check must include this control
-    /// revision; a stale preference writer cannot overwrite a newer decision.
-    pub async fn set_skill_preference(
-        &self,
-        expected_revision: u64,
-        reference: String,
-        preference: SkillPreference,
-    ) -> Result<PreferenceUpdate> {
-        if expected_revision > MAX_SAFE_INTEGER || !valid_reference(&reference) {
-            return Err(ConfigError::Invalid(
-                "invalid skill preference target or revision".into(),
-            ));
-        }
-        self.transaction(TransactionMode::Immediate, move |connection| Box::pin(async move {
-            let current = read(connection).await?;
-            if current.revision != expected_revision {
-                return Ok(PreferenceUpdate::Conflict { actual_revision: current.revision });
-            }
-            if current.entries.get(&reference).copied().unwrap_or_default() == preference {
-                return Ok(PreferenceUpdate::Committed { revision: current.revision });
-            }
-            if current.revision == MAX_SAFE_INTEGER
-                || (current.entries.len() == MAX_PREFERENCES && !current.entries.contains_key(&reference))
-            {
-                return Err(ConfigError::Invalid("skill preference capacity exhausted".into()));
-            }
-            if preference == SkillPreference::default() {
-                sqlx::query("DELETE FROM skill_preferences WHERE ref = ?")
-                    .bind(reference).execute(&mut *connection).await?;
-            } else {
-                sqlx::query("INSERT INTO skill_preferences(ref, enabled, pinned) VALUES (?, ?, ?) ON CONFLICT(ref) DO UPDATE SET enabled = excluded.enabled, pinned = excluded.pinned")
-                    .bind(reference).bind(preference.enabled).bind(preference.pinned)
-                    .execute(&mut *connection).await?;
-            }
-            let revision = current.revision + 1;
-            sqlx::query("UPDATE skill_preferences_revision SET revision = ? WHERE singleton = 1")
-                .bind(revision as i64).execute(connection).await?;
-            Ok(PreferenceUpdate::Committed { revision })
-        })).await
-    }
-}
-
-async fn read(connection: &mut SqliteConnection) -> Result<SkillPreferences> {
-    let revisions: Vec<(i64, i64)> =
-        sqlx::query_as("SELECT singleton, revision FROM skill_preferences_revision LIMIT 2")
-            .fetch_all(&mut *connection)
-            .await?;
-    let [(1, revision)] = revisions.as_slice() else {
-        return Err(ConfigError::UnsupportedDatabase);
-    };
-    let revision = u64::try_from(*revision)
-        .ok()
-        .filter(|r| *r <= MAX_SAFE_INTEGER)
-        .ok_or(ConfigError::UnsupportedDatabase)?;
-    let invalid: i64 = sqlx::query_scalar(
-        "SELECT COUNT(*) FROM skill_preferences WHERE length(CAST(ref AS BLOB)) NOT BETWEEN 1 AND 512 OR enabled NOT IN (0, 1) OR pinned NOT IN (0, 1)"
-    ).fetch_one(&mut *connection).await?;
-    if invalid != 0 {
-        return Err(ConfigError::UnsupportedDatabase);
-    }
-    let rows: Vec<(String, bool, bool)> =
-        sqlx::query_as("SELECT ref, enabled, pinned FROM skill_preferences ORDER BY ref LIMIT ?")
-            .bind((MAX_PREFERENCES + 1) as i64)
-            .fetch_all(connection)
-            .await?;
-    if rows.len() > MAX_PREFERENCES
-        || rows
-            .iter()
-            .any(|(reference, _, _)| !valid_reference(reference))
-    {
-        return Err(ConfigError::UnsupportedDatabase);
-    }
-    Ok(SkillPreferences {
-        revision,
-        entries: rows
-            .into_iter()
-            .map(|(reference, enabled, pinned)| (reference, SkillPreference { enabled, pinned }))
-            .collect(),
-    })
-}
-
-fn valid_reference(reference: &str) -> bool {
-    !reference.is_empty()
-        && reference.len() <= 512
-        && !reference
-            .chars()
-            .any(|c| matches!(c, '\u{0000}'..='\u{001f}' | '\u{007f}'))
-}
diff --git a/crates/config/tests/migrations.rs b/crates/config/tests/migrations.rs
index db0dbd20a1..06f62ea2d8 100644
--- a/crates/config/tests/migrations.rs
+++ b/crates/config/tests/migrations.rs
@@ -19,9 +19,7 @@
 
 use maka_config::ConfigurationStore;
 use maka_event_log::root::{RootNamespaces, RootOwner};
-use maka_runtime::configuration::*;
-use rusqlite::{Connection, params};
-use serde_json::json;
+use rusqlite::Connection;
 use std::{path::Path, sync::Arc};
 
 fn private_database(root: &Path) -> Connection {
@@ -55,220 +53,6 @@ fn root(temp: &Path) -> Arc<RootOwner> {
     )
 }
 
-#[tokio::test]
-async fn legacy_catalog_and_vault_migrate_without_losing_revisions_or_secrets() {
-    let temp = tempfile::tempdir().unwrap();
-    let owner = root(temp.path());
-    let database = private_database(owner.canonical_path());
-    // This is the previous Rust schema, deliberately independent of the new migration.
-    database
-        .execute_batch(
-            "
-        PRAGMA application_id = 1296124739;
-        PRAGMA user_version = 1;
-        CREATE TABLE connection_catalog (
-            singleton INTEGER PRIMARY KEY CHECK(singleton = 1),
-            revision INTEGER NOT NULL CHECK(revision >= 0), default_target TEXT);
-        CREATE TABLE connections (
-            connection_id TEXT PRIMARY KEY, slug TEXT NOT NULL UNIQUE,
-            revision INTEGER NOT NULL CHECK(revision > 0), document TEXT NOT NULL);
-        CREATE TABLE credential_vault (
-            singleton INTEGER PRIMARY KEY CHECK(singleton = 1),
-            revision INTEGER NOT NULL CHECK(revision >= 0));
-        CREATE TABLE credentials (
-            locator TEXT PRIMARY KEY, credential_id TEXT NOT NULL UNIQUE,
-            revision INTEGER NOT NULL CHECK(revision > 0),
-            secret TEXT NOT NULL, updated_at INTEGER NOT NULL);
-        INSERT INTO credential_vault VALUES(1, 9);
-    ",
-        )
-        .unwrap();
-    let connection_id = "11111111-1111-4111-8111-111111111111".to_owned();
-    let target = ConnectionTarget {
-        connection_id: connection_id.clone(),
-        model_id: "fixture".into(),
-    };
-    let mut entry: ConnectionCatalogEntry = serde_json::from_value(json!({
-        "connectionId":connection_id, "revision":3, "slug":"legacy", "name":"Legacy",
-        "providerType":"openai-compatible", "baseUrl":"http://127.0.0.1:18080/v1",
-        "enabled":true, "enabledModelIds":["fixture"], "models":[]
-    }))
-    .unwrap();
-    let mut legacy_document = serde_json::to_value(&entry).unwrap();
-    legacy_document["relayModelProfiles"] = json!({
-        "fixture":{"contextWindow":8192,"vision":true}
-    });
-    legacy_document["lastTest"] = json!({"status":"verified","checkedAt":"legacy"});
-    legacy_document["lastTestModelFactsFingerprint"] = json!("sha256:old-file-basis");
-    let external = owner.canonical_path().join("model-facts.json");
-    std::fs::write(&external, b"not a Rust configuration source").unwrap();
-    entry.model_overrides = Some(
-        serde_json::from_value(json!({
-            "fixture":{"contextWindow":8192,"compactionThreshold":8192,"vision":true}
-        }))
-        .unwrap(),
-    );
-    database
-        .execute(
-            "INSERT INTO connection_catalog VALUES(1, 7, ?)",
-            [serde_json::to_string(&target).unwrap()],
-        )
-        .unwrap();
-    database
-        .execute(
-            "INSERT INTO connections VALUES(?, 'legacy', 3, ?)",
-            params![
-                connection_id,
-                serde_json::to_string(&legacy_document).unwrap()
-            ],
-        )
-        .unwrap();
-    let key = CredentialLocator::Connection {
-        connection_id: connection_id.clone(),
-        kind: ConnectionCredentialKind::ApiKey,
-    };
-    let unrelated = CredentialLocator::NetworkProxy {
-        kind: PasswordKind::Password,
-    };
-    for (locator, id, secret) in [
-        (
-            &key,
-            "22222222-2222-4222-8222-222222222222",
-            "legacy-private-key",
-        ),
-        (
-            &unrelated,
-            "33333333-3333-4333-8333-333333333333",
-            "legacy-proxy-password",
-        ),
-    ] {
-        database
-            .execute(
-                "INSERT INTO credentials VALUES(?, ?, 4, ?, 42)",
-                params![serde_json::to_string(locator).unwrap(), id, secret],
-            )
-            .unwrap();
-    }
-    drop(database);
-
-    let store = ConfigurationStore::for_root(owner.clone()).await.unwrap();
-    let snapshot = store.catalog().await.unwrap();
-    assert_eq!(
-        std::fs::read(&external).unwrap(),
-        b"not a Rust configuration source"
-    );
-    assert_eq!(snapshot.revision, 7);
-    assert_eq!(snapshot.connections, vec![entry]);
-    assert_eq!(snapshot.default_target, Some(target));
-    assert!(
-        !serde_json::to_string(&snapshot)
-            .unwrap()
-            .contains("legacy-private-key")
-    );
-    assert_eq!(
-        store
-            .credential_secret(&key, None)
-            .await
-            .unwrap()
-            .as_deref(),
-        Some("legacy-private-key")
-    );
-    let status = store.credential_status(key.clone()).await.unwrap();
-    assert!(matches!(
-        &status,
-        CredentialVaultQueryResult::Status {
-            status: CredentialStatus {
-                state: CredentialState::Configured {
-                    revision: 4,
-                    updated_at: 42,
-                    ..
-                },
-                ..
-            }
-        }
-    ));
-    let basis = ConnectionVersionBasis {
-        connection_id,
-        revision: 3,
-    };
-    let mut stale = basis.clone();
-    stale.revision = 2;
-    assert_eq!(
-        store
-            .remove_connection(RemoveCatalogConnectionInput {
-                expected: stale.clone()
-            })
-            .await
-            .unwrap(),
-        CatalogMutationResult::ConnectionStale {
-            expected: stale,
-            actual: Some(basis.clone())
-        }
-    );
-    assert!(matches!(
-        store
-            .delete_credential(DeleteCredentialInput {
-                expected: CredentialVersionBasis {
-                    locator: key.clone(),
-                    credential_id: "22222222-2222-4222-8222-222222222222".into(),
-                    revision: 3
-                }
-            })
-            .await
-            .unwrap(),
-        CredentialMutationResult::CredentialStale { .. }
-    ));
-    assert_eq!(store.catalog().await.unwrap(), snapshot);
-    assert_eq!(store.credential_status(key.clone()).await.unwrap(), status);
-    store.close().await.unwrap();
-
-    let store = ConfigurationStore::for_root(owner.clone()).await.unwrap();
-    assert_eq!(store.catalog().await.unwrap(), snapshot);
-    assert_eq!(store.credential_status(key.clone()).await.unwrap(), status);
-    assert_eq!(
-        store
-            .remove_connection(RemoveCatalogConnectionInput { expected: basis })
-            .await
-            .unwrap(),
-        CatalogMutationResult::Committed {
-            catalog_revision: 8,
-            connection: None
-        }
-    );
-    assert_eq!(store.catalog().await.unwrap().default_target, None);
-    assert_eq!(
-        store
-            .credential_secret(&unrelated, None)
-            .await
-            .unwrap()
-            .as_deref(),
-        Some("legacy-proxy-password")
-    );
-    store.close().await.unwrap();
-    let database =
-        Connection::open(owner.canonical_path().join("configuration-rust.sqlite")).unwrap();
-    assert_eq!(
-        database
-            .query_row(
-                "SELECT revision, (SELECT COUNT(*) FROM credentials) FROM credential_vault",
-                [],
-                |row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?))
-            )
-            .unwrap(),
-        (10, 1)
-    );
-    assert_eq!(
-        database
-            .query_row(
-                "SELECT COUNT(*) FROM _sqlx_migrations WHERE success = 1",
-                [],
-                |row| row.get::<_, i64>(0)
-            )
-            .unwrap(),
-        10
-    );
-}
-
 #[tokio::test]
 async fn invalid_databases_are_rejected_without_mutation_and_interrupted_initialization_resumes() {
     for corruption in [
@@ -276,6 +60,9 @@ async fn invalid_databases_are_rejected_without_mutation_and_interrupted_initial
         "UPDATE _sqlx_migrations SET version = version + 999",
         "PRAGMA application_id = 123",
         "PRAGMA user_version = 999",
+        "DROP TABLE _sqlx_migrations",
+        "DELETE FROM _sqlx_migrations",
+        "PRAGMA user_version = 0",
     ] {
         let temp = tempfile::tempdir().unwrap();
         let owner = root(temp.path());
@@ -284,6 +71,9 @@ async fn invalid_databases_are_rejected_without_mutation_and_interrupted_initial
         let path = owner.canonical_path().join("configuration-rust.sqlite");
         let database = Connection::open(&path).unwrap();
         database.execute_batch(corruption).unwrap();
+        database
+            .execute_batch("PRAGMA journal_mode = DELETE")
+            .unwrap();
         drop(database);
         let before = std::fs::read(&path).unwrap();
         assert!(
diff --git a/crates/config/tests/plugin_authorization.rs b/crates/config/tests/plugin_authorization.rs
new file mode 100644
index 0000000000..aa21806ee7
--- /dev/null
+++ b/crates/config/tests/plugin_authorization.rs
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use maka_config::{
+    ConfigurationStore,
+    plugin_authorization::{Approval, Boundary, Principal},
+};
+use maka_event_log::root::{RootNamespaces, RootOwner};
+use maka_plugins::{
+    authorization::{Capability, Request, Target},
+    composition::Scope,
+    execution::SessionBoundary,
+    storage::Namespace,
+};
+use maka_runtime::execution::{PermissionMode, WorkspaceIdentity};
+use std::sync::Arc;
+use uuid::Uuid;
+
+#[tokio::test]
+async fn consent_retries_preserve_original_boundary_and_revocation_across_restart() {
+    let temp = tempfile::tempdir().unwrap();
+    let owner = Arc::new(
+        RootOwner::create(
+            &temp.path().join("root"),
+            &RootNamespaces {
+                ownership: temp.path().join("owners"),
+                control: temp.path().join("control"),
+            },
+        )
+        .unwrap(),
+    );
+    let store = ConfigurationStore::for_root(owner.clone()).await.unwrap();
+    let namespace = Namespace::new("example.background", Scope::Profile).unwrap();
+    let request = Request {
+        operation_id: Uuid::new_v4(),
+        title: "Review changes".into(),
+        target: Target::Session {
+            session_id: "session".into(),
+        },
+        capabilities: [Capability::Executions].into(),
+    };
+    let boundary = |revision, permission_mode| Boundary::Session {
+        boundary: SessionBoundary {
+            session_id: "session".into(),
+            boundary_revision: revision,
+            permission_mode,
+            cwd: temp.path().to_str().unwrap().into(),
+        },
+        workspace_identity: WorkspaceIdentity::from_marker_id(
+            "00000000-0000-4000-8000-000000000001",
+        )
+        .unwrap(),
+    };
+    let approve = |principal, request, boundary| {
+        store.approve_plugin_authorization(namespace.clone(), principal, request, boundary)
+    };
+    let first = boundary(1, PermissionMode::Ask);
+    let (left, right) = tokio::join!(
+        approve(
+            Principal::LocalUser {
+                client_instance_id: "client".into()
+            },
+            request.clone(),
+            first.clone()
+        ),
+        approve(
+            Principal::LocalUser {
+                client_instance_id: "client".into()
+            },
+            request.clone(),
+            first.clone()
+        ),
+    );
+    let (Approval::Granted(left), Approval::Granted(right)) = (left.unwrap(), right.unwrap())
+    else {
+        panic!("identical approval conflicted")
+    };
+    assert_eq!(left.grant, right.grant);
+    let id = left.grant.id;
+    // Lost reply retries cannot recapture a newer/wider Session configuration.
+    let Approval::Granted(retry) = approve(
+        Principal::LocalUser {
+            client_instance_id: "client".into(),
+        },
+        request.clone(),
+        boundary(2, PermissionMode::Bypass),
+    )
+    .await
+    .unwrap() else {
+        panic!("retry conflicted")
+    };
+    assert_eq!(retry.boundary, first);
+    let mut changed = request.clone();
+    changed.capabilities.insert(Capability::Notifications);
+    assert!(matches!(
+        approve(
+            Principal::LocalUser {
+                client_instance_id: "client".into()
+            },
+            changed,
+            first.clone()
+        )
+        .await
+        .unwrap(),
+        Approval::Conflict
+    ));
+    assert!(matches!(
+        approve(
+            Principal::Credential {
+                credential_id: "another-user".into(),
+                client_instance_id: "desktop".into()
+            },
+            request.clone(),
+            first.clone()
+        )
+        .await
+        .unwrap(),
+        Approval::Conflict
+    ));
+    for other in [
+        Namespace::new("other", Scope::Profile).unwrap(),
+        Namespace::new("example.background", Scope::Session("session".into())).unwrap(),
+    ] {
+        assert!(
+            store
+                .plugin_authorization(other.clone(), id)
+                .await
+                .unwrap()
+                .is_none()
+        );
+        store.revoke_plugin_authorization(other, id).await.unwrap();
+    }
+    assert!(
+        !store
+            .plugin_authorization(namespace.clone(), id)
+            .await
+            .unwrap()
+            .unwrap()
+            .grant
+            .revoked
+    );
+    store
+        .revoke_plugin_authorization(namespace.clone(), id)
+        .await
+        .unwrap();
+    store.close().await.unwrap();
+    let store = ConfigurationStore::for_root(owner).await.unwrap();
+    let Approval::Granted(retry) = store
+        .approve_plugin_authorization(
+            namespace.clone(),
+            Principal::LocalUser {
+                client_instance_id: "client".into(),
+            },
+            request,
+            first.clone(),
+        )
+        .await
+        .unwrap()
+    else {
+        panic!("revoked retry conflicted")
+    };
+    assert_eq!(retry.grant.id, id);
+    assert!(retry.grant.revoked);
+    assert_eq!(retry.boundary, first);
+    assert!(
+        store
+            .plugin_authorization(namespace, id)
+            .await
+            .unwrap()
+            .unwrap()
+            .grant
+            .revoked
+    );
+    store.close().await.unwrap();
+}
diff --git a/crates/config/tests/runtime_policy.rs b/crates/config/tests/runtime_policy.rs
index 4b5424fd03..50815fb22a 100644
--- a/crates/config/tests/runtime_policy.rs
+++ b/crates/config/tests/runtime_policy.rs
@@ -233,67 +233,11 @@ async fn defaults_are_read_only_and_cas_is_durable_even_for_same_value() {
         RuntimePolicyMutationResult::Committed { revision: 3 }
     );
     store.close().await.unwrap();
-    // Reconstruct the previous Rust policy document and migration ledger. The
-    // upgrade must preserve user values/revisions without weakening wire decode.
-    db.execute_batch(
-        "UPDATE runtime_policy SET document = json_remove(document, '$.policy.externalAgents');
-        DELETE FROM _sqlx_migrations WHERE version = 6; PRAGMA user_version = 5;",
-    )
-    .unwrap();
-    let document: String = db
-        .query_row("SELECT document FROM runtime_policy", [], |row| row.get(0))
-        .unwrap();
-    let mut legacy: serde_json::Value = serde_json::from_str(&document).unwrap();
-    // A valid legacy document at the old byte limit must survive schema growth.
-    while legacy.to_string().len() + 515 < MAX_POLICY_SNAPSHOT_BYTES {
-        let domains = legacy["policy"]["networkProxy"]["bypassList"]
-            .as_array_mut()
-            .unwrap();
-        domains.push(serde_json::json!(format!(
-            "{:03}{}",
-            domains.len(),
-            "x".repeat(509)
-        )));
-    }
-    let remaining = MAX_POLICY_SNAPSHOT_BYTES - legacy.to_string().len();
-    if remaining > 3 {
-        legacy["policy"]["networkProxy"]["bypassList"]
-            .as_array_mut()
-            .unwrap()
-            .push(serde_json::json!("y".repeat(remaining - 3)));
-    }
-    let document = legacy.to_string();
-    assert!(document.len() <= MAX_POLICY_SNAPSHOT_BYTES);
-    assert!(document.len() > MAX_POLICY_SNAPSHOT_BYTES - 40);
-    db.execute("UPDATE runtime_policy SET document = ?", [&document])
-        .unwrap();
-    let reopened = ConfigurationStore::for_root(owner.clone()).await.unwrap();
+    let reopened = ConfigurationStore::for_root(owner).await.unwrap();
     let snapshot = reopened.runtime_policy().await.unwrap();
     assert_eq!(snapshot.revision, 3);
     assert_eq!(snapshot.policy.chat_defaults, clear);
-    assert_eq!(snapshot.policy.external_agents, ExternalAgents::default());
-    assert_eq!(
-        serde_json::to_value(&snapshot.policy.network_proxy).unwrap(),
-        legacy["policy"]["networkProxy"]
-    );
-    assert!(snapshot.validate().is_err()); // Still too large for the wire.
-    assert_eq!(
-        reopened.network_configuration().await.unwrap().proxy,
-        snapshot.policy.network_proxy
-    );
-    assert!(matches!(
-        reopened.set_chat_defaults(3, clear).await,
-        Err(ConfigError::Invalid(_))
-    ));
-    assert_eq!(reopened.runtime_policy().await.unwrap(), snapshot);
-    assert!(matches!(
-        reopened
-            .set_network_proxy(3, initial.policy.network_proxy.clone())
-            .await
-            .unwrap(),
-        RuntimePolicyMutationResult::Committed { revision: 4 }
-    ));
-    reopened.runtime_policy().await.unwrap().validate().unwrap();
+    snapshot.validate().unwrap();
     assert_eq!(count(&db), 1);
     reopened.close().await.unwrap();
 }
diff --git a/crates/config/tests/skills.rs b/crates/config/tests/skills.rs
deleted file mode 100644
index 19b242d0c4..0000000000
--- a/crates/config/tests/skills.rs
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-use maka_config::{ConfigurationStore, skills::PreferenceUpdate};
-use maka_event_log::root::{RootNamespaces, RootOwner};
-use maka_runtime::skills::SkillPreference;
-use std::sync::Arc;
-
-#[tokio::test]
-async fn root_preferences_are_atomic_revisioned_and_corruption_is_not_default_enablement() {
-    let temporary = tempfile::tempdir().unwrap();
-    let owner = Arc::new(
-        RootOwner::create(
-            &temporary.path().join("root"),
-            &RootNamespaces {
-                ownership: temporary.path().join("owners"),
-                control: temporary.path().join("control"),
-            },
-        )
-        .unwrap(),
-    );
-    let legacy = owner.canonical_path().join(".maka");
-    std::fs::create_dir(&legacy).unwrap();
-    let legacy = legacy.join("skills-state.json");
-    std::fs::write(&legacy, "original TS state is not an input").unwrap();
-    let store = ConfigurationStore::for_root(owner.clone()).await.unwrap();
-    let initial = store.skill_preferences().await.unwrap();
-    assert_eq!(initial.revision, 0);
-    assert!(initial.entries.is_empty());
-    let reference = "project:maka:review".to_owned();
-    let disabled = SkillPreference {
-        enabled: false,
-        pinned: true,
-    };
-    assert_eq!(
-        store
-            .set_skill_preference(0, reference.clone(), disabled)
-            .await
-            .unwrap(),
-        PreferenceUpdate::Committed { revision: 1 }
-    );
-    assert_eq!(
-        store
-            .set_skill_preference(0, reference.clone(), SkillPreference::default())
-            .await
-            .unwrap(),
-        PreferenceUpdate::Conflict { actual_revision: 1 }
-    );
-    assert_eq!(
-        store
-            .set_skill_preference(1, reference.clone(), disabled)
-            .await
-            .unwrap(),
-        PreferenceUpdate::Committed { revision: 1 }
-    );
-    store.close().await.unwrap();
-    let store = ConfigurationStore::for_root(owner.clone()).await.unwrap();
-    assert_eq!(
-        store.skill_preferences().await.unwrap().entries[&reference],
-        disabled
-    );
-    assert_eq!(
-        store
-            .set_skill_preference(1, reference.clone(), SkillPreference::default())
-            .await
-            .unwrap(),
-        PreferenceUpdate::Committed { revision: 2 }
-    );
-    let empty = store.skill_preferences().await.unwrap();
-    assert_eq!(empty.revision, 2);
-    assert!(empty.entries.is_empty());
-
-    let db = rusqlite::Connection::open(owner.canonical_path().join("configuration-rust.sqlite"))
-        .unwrap();
-    db.execute_batch(
-        "PRAGMA ignore_check_constraints = ON;
-        INSERT INTO skill_preferences VALUES ('project:maka:review', 7, 0);",
-    )
-    .unwrap();
-    assert!(store.skill_preferences().await.is_err());
-    assert!(
-        store
-            .set_skill_preference(2, reference, disabled)
-            .await
-            .is_err()
-    );
-    assert_eq!(
-        db.query_row(
-            "SELECT revision FROM skill_preferences_revision",
-            [],
-            |row| row.get::<_, i64>(0)
-        )
-        .unwrap(),
-        2
-    );
-    assert_eq!(
-        std::fs::read_to_string(legacy).unwrap(),
-        "original TS state is not an input"
-    );
-    store.close().await.unwrap();
-}
diff --git a/crates/event-log/migrations/0001_runtime.sql b/crates/event-log/migrations/0001_runtime.sql
index 6301f9d642..68e461f82d 100644
--- a/crates/event-log/migrations/0001_runtime.sql
+++ b/crates/event-log/migrations/0001_runtime.sql
@@ -17,34 +17,19 @@
  * under the License.
  */
 
+-- Canonical facts and durable control state. Disposable projections are built at startup.
 
--- Canonical execution facts and independent Session control state.
--- Disposable transcript/catalog projections are rebuilt from these facts.
-PRAGMA application_id = 1296124754;
-PRAGMA user_version = 1;
-
-CREATE TABLE IF NOT EXISTS runtime_events (
+CREATE TABLE event_log (
     sequence INTEGER PRIMARY KEY AUTOINCREMENT CHECK(sequence > 0),
     event_id TEXT NOT NULL UNIQUE,
-    invocation_id TEXT NOT NULL,
+    invocation_id TEXT,
     kind TEXT NOT NULL,
     operation_id TEXT,
-    event_json TEXT NOT NULL
-);
-CREATE INDEX IF NOT EXISTS invocation_sequence ON runtime_events(invocation_id, sequence);
-CREATE INDEX IF NOT EXISTS session_event_sequence ON runtime_events(
-    json_extract(event_json, '$.invocation.session_id'), sequence
+    event_json TEXT NOT NULL,
+    CHECK(invocation_id IS NOT NULL OR operation_id IS NULL)
 );
-CREATE INDEX IF NOT EXISTS turn_opening_lookup ON runtime_events(
-    json_extract(event_json, '$.invocation.session_id'),
-    json_extract(event_json, '$.invocation.turn_id'), sequence DESC
-) WHERE kind = 'invocation_opened';
-CREATE UNIQUE INDEX IF NOT EXISTS operation_fact ON runtime_events(operation_id, kind)
-    WHERE operation_id IS NOT NULL;
-CREATE UNIQUE INDEX IF NOT EXISTS invocation_boundary ON runtime_events(invocation_id, kind)
-    WHERE kind IN ('invocation_opened', 'invocation_ended');
 
-CREATE TABLE IF NOT EXISTS session_control (
+CREATE TABLE session_control (
     id TEXT PRIMARY KEY,
     fingerprint TEXT NOT NULL,
     revision INTEGER NOT NULL CHECK(revision > 0),
@@ -53,8 +38,388 @@ CREATE TABLE IF NOT EXISTS session_control (
     archived INTEGER NOT NULL CHECK(archived IN (0, 1)),
     configuration TEXT NOT NULL
 );
-CREATE TABLE IF NOT EXISTS session_catalog_revision (
+
+CREATE TABLE session_catalog_revision (
     singleton INTEGER PRIMARY KEY CHECK(singleton = 1),
     revision INTEGER NOT NULL CHECK(revision >= 0)
 );
-INSERT OR IGNORE INTO session_catalog_revision VALUES (1, 0);
+
+CREATE TABLE session_read_state (
+    session_id TEXT PRIMARY KEY REFERENCES session_control(id),
+    has_unread INTEGER NOT NULL CHECK(has_unread IN (0, 1)),
+    last_read_message_id TEXT
+);
+
+CREATE TABLE interaction_requests (
+    request_id TEXT PRIMARY KEY,
+    session_id TEXT NOT NULL,
+    created_at INTEGER NOT NULL,
+    record_json TEXT NOT NULL CHECK(length(CAST(record_json AS BLOB)) <= 20480)
+);
+
+CREATE INDEX interaction_session_pending ON interaction_requests(session_id, created_at, request_id);
+
+CREATE TABLE interaction_outcomes (
+    request_id TEXT PRIMARY KEY REFERENCES interaction_requests(request_id),
+    outcome_json TEXT NOT NULL CHECK(length(CAST(outcome_json AS BLOB)) <= 8192)
+);
+
+CREATE TABLE client_capability_session_grants (
+    authority_key TEXT PRIMARY KEY,
+    record_json TEXT NOT NULL CHECK(length(CAST(record_json AS BLOB)) <= 12288)
+);
+
+CREATE TABLE artifacts (
+    session_id TEXT NOT NULL REFERENCES session_control(id),
+    id TEXT NOT NULL,
+    created_at INTEGER NOT NULL CHECK(created_at >= 0),
+    record_json TEXT NOT NULL CHECK(length(CAST(record_json AS BLOB)) <= 16384),
+    content_sha256 TEXT NOT NULL,
+    payload BLOB NOT NULL CHECK(length(payload) <= 52428800),
+    PRIMARY KEY (session_id, id),
+    CHECK(json_extract(record_json, '$.sessionId') = session_id),
+    CHECK(json_extract(record_json, '$.id') = id),
+    CHECK(json_extract(record_json, '$.createdAt') = created_at),
+    CHECK(json_extract(record_json, '$.sizeBytes') = length(payload))
+);
+
+CREATE INDEX artifacts_session_order ON artifacts(session_id, created_at DESC, id);
+
+CREATE TABLE artifact_catalog (
+    session_id TEXT PRIMARY KEY REFERENCES session_control(id),
+    revision INTEGER NOT NULL CHECK(revision > 0 AND revision <= 9007199254740991)
+);
+
+CREATE TABLE shell_runs (
+    session_id TEXT NOT NULL REFERENCES session_control(id),
+    id TEXT NOT NULL,
+    started_at INTEGER NOT NULL CHECK(started_at >= 0),
+    active INTEGER NOT NULL CHECK(active IN (0, 1)),
+    record_json TEXT NOT NULL CHECK(length(CAST(record_json AS BLOB)) <= 4194304),
+    PRIMARY KEY (session_id, id),
+    CHECK(json_extract(record_json, '$.sessionId') IS session_id),
+    CHECK(json_extract(record_json, '$.id') IS id),
+    CHECK(json_extract(record_json, '$.startedAt') IS started_at),
+    CHECK(active IS (json_extract(record_json, '$.state.kind') IN ('starting', 'running')))
+);
+
+CREATE INDEX shell_runs_session_order ON shell_runs(session_id, started_at, id);
+
+CREATE INDEX shell_runs_active ON shell_runs(active) WHERE active = 1;
+
+CREATE TABLE message_admissions (
+    session_id TEXT NOT NULL,
+    message_id TEXT NOT NULL,
+    position INTEGER NOT NULL,
+    record_json TEXT NOT NULL,
+    PRIMARY KEY(session_id, message_id)
+);
+
+CREATE INDEX message_admission_order ON message_admissions(session_id, position, message_id);
+
+CREATE TABLE message_queue_state (
+    session_id TEXT PRIMARY KEY NOT NULL,
+    revision INTEGER NOT NULL CHECK(revision >= 0)
+);
+
+CREATE TABLE message_cancellations (
+    session_id TEXT NOT NULL,
+    message_id TEXT NOT NULL,
+    cancellation_id TEXT NOT NULL,
+    PRIMARY KEY(session_id, message_id)
+);
+
+CREATE TABLE queue_command_receipts (
+    host_epoch TEXT NOT NULL,
+    session_id TEXT NOT NULL,
+    operation TEXT NOT NULL,
+    command_id TEXT NOT NULL,
+    fingerprint TEXT NOT NULL,
+    receipt_json TEXT NOT NULL,
+    PRIMARY KEY(host_epoch, session_id, operation, command_id)
+);
+
+CREATE TABLE message_submit_receipts (
+    host_epoch TEXT NOT NULL,
+    session_id TEXT NOT NULL,
+    message_id TEXT NOT NULL,
+    receipt_json TEXT NOT NULL,
+    PRIMARY KEY(host_epoch, session_id, message_id)
+);
+
+CREATE TABLE message_interrupt_receipts (
+    host_epoch TEXT NOT NULL,
+    session_id TEXT NOT NULL,
+    interrupt_id TEXT NOT NULL,
+    turn_id TEXT NOT NULL,
+    run_id TEXT NOT NULL,
+    fence_json TEXT,
+    PRIMARY KEY(host_epoch, session_id, interrupt_id)
+);
+
+CREATE INDEX message_interrupt_run ON message_interrupt_receipts(session_id, run_id)
+    WHERE fence_json IS NOT NULL;
+
+CREATE TABLE projects (
+    id TEXT PRIMARY KEY NOT NULL,
+    identity TEXT NOT NULL UNIQUE,
+    name TEXT NOT NULL CHECK(length(CAST(name AS BLOB)) BETWEEN 1 AND 16384),
+    last_used_at INTEGER NOT NULL CHECK(last_used_at BETWEEN 0 AND 9007199254740991),
+    archived_at INTEGER CHECK(archived_at BETWEEN 0 AND 9007199254740991)
+) STRICT;
+
+CREATE TABLE project_identities (
+    id TEXT PRIMARY KEY NOT NULL,
+    project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE
+) STRICT;
+
+CREATE INDEX project_identity_owner ON project_identities(project_id, id);
+
+CREATE TABLE project_locations (
+    project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
+    path TEXT NOT NULL CHECK(length(CAST(path AS BLOB)) BETWEEN 1 AND 4096),
+    is_worktree INTEGER NOT NULL CHECK(is_worktree IN (0, 1)),
+    last_used_at INTEGER NOT NULL CHECK(last_used_at BETWEEN 0 AND 9007199254740991),
+    PRIMARY KEY(project_id, path)
+) STRICT;
+
+CREATE UNIQUE INDEX pending_root_invocation ON message_admissions (
+    json_extract(record_json, '$.invocation.invocation_id')
+) WHERE json_extract(record_json, '$.source.disposition') = 'turn_started';
+
+CREATE TABLE "tool_result_payloads" (
+    event_id TEXT PRIMARY KEY NOT NULL REFERENCES event_log(event_id),
+    payload BLOB NOT NULL CHECK(length(payload) <= 67108864)
+);
+
+CREATE VIEW runtime_events AS
+    SELECT * FROM event_log WHERE invocation_id IS NOT NULL;
+
+CREATE VIEW session_events AS
+    SELECT sequence, event_id, kind, event_json FROM event_log WHERE invocation_id IS NULL;
+
+CREATE INDEX invocation_sequence ON event_log(invocation_id, sequence);
+
+CREATE INDEX session_event_sequence ON event_log(
+    json_extract(event_json, '$.invocation.session_id'), sequence
+);
+
+CREATE INDEX session_control_sequence ON event_log(
+    json_extract(event_json, '$.session_id'), sequence
+) WHERE invocation_id IS NULL;
+
+CREATE INDEX turn_opening_lookup ON event_log(
+    json_extract(event_json, '$.invocation.session_id'),
+    json_extract(event_json, '$.invocation.turn_id'), sequence DESC
+) WHERE kind = 'invocation_opened';
+
+CREATE UNIQUE INDEX operation_fact ON event_log(operation_id, kind)
+    WHERE operation_id IS NOT NULL;
+
+CREATE UNIQUE INDEX invocation_boundary ON event_log(invocation_id, kind)
+    WHERE kind IN ('invocation_opened', 'invocation_ended');
+
+CREATE UNIQUE INDEX message_steering_identity ON event_log(
+    json_extract(event_json, '$.invocation.session_id'),
+    json_extract(event_json, '$.fact.message.message_id')
+) WHERE kind = 'message_steered';
+
+CREATE UNIQUE INDEX workhub_stop_resolution ON event_log(
+    json_extract(event_json, '$.fact.action_id')
+) WHERE kind = 'workhub_stop_resolved';
+
+CREATE INDEX workhub_stop_request_subject ON event_log(
+    json_extract(event_json, '$.fact.intent.delegation_action_id')
+) WHERE kind = 'workhub_stop_requested';
+
+CREATE VIEW workhub_stops AS
+    SELECT json_extract(q.event_json, '$.fact.intent.request.action_id') AS action_id,
+           json_extract(q.event_json, '$.fact.intent.delegation_action_id') AS delegation_action_id,
+           json_extract(q.event_json, '$.fact.intent') AS record_json,
+           json_extract(r.event_json, '$.fact.resolution') AS resolution_json
+    FROM session_events q LEFT JOIN session_events r
+      ON r.kind = 'workhub_stop_resolved'
+     AND json_extract(r.event_json, '$.fact.action_id') =
+         json_extract(q.event_json, '$.fact.intent.request.action_id')
+    WHERE q.kind = 'workhub_stop_requested';
+
+CREATE INDEX workhub_assignment_target ON event_log(
+    json_extract(event_json, '$.fact.delegation.target.session_id'), sequence DESC
+) WHERE kind = 'workhub_delegated';
+
+CREATE UNIQUE INDEX workhub_action_identity ON event_log(
+    CASE
+        WHEN kind IN ('workhub_stop_requested', 'workhub_correction_requested')
+            THEN json_extract(event_json, '$.fact.intent.request.action_id')
+        WHEN kind = 'workhub_delegated' AND invocation_id IS NOT NULL
+            THEN json_extract(event_json, '$.fact.delegation.action_id')
+        WHEN kind = 'workhub_resume_observed'
+            THEN json_extract(event_json, '$.fact.resume.action_id')
+        WHEN kind = 'invocation_opened'
+            AND json_extract(event_json, '$.fact.input.kind') = 'continuation'
+            THEN json_extract(event_json, '$.fact.input.workhub_resume.action_id')
+    END
+) WHERE kind IN ('workhub_delegated', 'workhub_resume_observed', 'invocation_opened', 'workhub_stop_requested', 'workhub_correction_requested');
+
+CREATE UNIQUE INDEX workhub_assignment_action ON event_log(
+    json_extract(event_json, '$.fact.delegation.action_id')
+) WHERE kind = 'workhub_delegated';
+
+CREATE UNIQUE INDEX workhub_correction_subject ON event_log(
+    json_extract(event_json, '$.fact.intent.request.replaces_action_id')
+) WHERE kind = 'workhub_correction_requested';
+
+CREATE UNIQUE INDEX workhub_correction_resolution ON event_log(
+    json_extract(event_json, '$.fact.action_id')
+) WHERE kind IN ('workhub_superseded', 'workhub_correction_aborted');
+
+CREATE VIEW workhub_assignments AS
+    SELECT sequence, event_id, event_json,
+           COALESCE(json_extract(event_json, '$.invocation'),
+                    json_extract(event_json, '$.fact.coordinator')) AS coordinator_json
+    FROM event_log WHERE kind = 'workhub_delegated';
+
+CREATE VIEW workhub_corrections AS
+    SELECT q.sequence,
+           json_extract(q.event_json, '$.fact.intent.request.action_id') AS action_id,
+           json_extract(q.event_json, '$.fact.intent.request.replaces_action_id') AS replaces_action_id,
+           json_extract(q.event_json, '$.fact.intent') AS intent_json,
+           r.kind AS resolution_kind,
+           json_extract(r.event_json, '$.fact.reason') AS abort_reason
+    FROM session_events q LEFT JOIN session_events r
+      ON r.kind IN ('workhub_superseded', 'workhub_correction_aborted')
+     AND json_extract(r.event_json, '$.fact.action_id') =
+         json_extract(q.event_json, '$.fact.intent.request.action_id')
+    WHERE q.kind = 'workhub_correction_requested';
+
+CREATE UNIQUE INDEX continuation_claim_id ON event_log(
+    json_extract(event_json, '$.fact.input.claim.id')
+) WHERE kind = 'invocation_opened' AND json_extract(event_json, '$.fact.input.kind') IN ('continuation','handoff');
+
+CREATE UNIQUE INDEX continuation_source_boundary ON event_log(
+    json_extract(event_json, '$.invocation.session_id'),
+    json_extract(event_json, '$.fact.input.claim.source.invocation.run_id'),
+    json_extract(event_json, '$.fact.input.claim.source.high_water')
+) WHERE kind = 'invocation_opened' AND json_extract(event_json, '$.fact.input.kind') IN ('continuation','handoff');
+
+CREATE UNIQUE INDEX handoff_successor_run ON event_log(
+    json_extract(event_json, '$.fact.outcome.pause.intent.successor_run_id')
+) WHERE kind = 'invocation_ended' AND json_extract(event_json, '$.fact.outcome.kind') = 'handoff_paused';
+
+CREATE UNIQUE INDEX handoff_successor_invocation ON event_log(
+    json_extract(event_json, '$.fact.outcome.pause.intent.successor_invocation_id')
+) WHERE kind = 'invocation_ended' AND json_extract(event_json, '$.fact.outcome.kind') = 'handoff_paused';
+
+CREATE UNIQUE INDEX handoff_claim ON event_log(
+    json_extract(event_json, '$.fact.outcome.pause.intent.claim_id')
+) WHERE kind = 'invocation_ended' AND json_extract(event_json, '$.fact.outcome.kind') = 'handoff_paused';
+
+CREATE TABLE plugin_composition (
+    singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
+    ledger_json TEXT NOT NULL CHECK (json_valid(ledger_json))
+);
+
+CREATE TABLE plugin_package_blobs (
+    digest TEXT PRIMARY KEY
+);
+
+CREATE TABLE plugin_package_files (
+    digest TEXT NOT NULL REFERENCES plugin_package_blobs(digest) ON DELETE CASCADE,
+    path TEXT NOT NULL,
+    payload BLOB NOT NULL,
+    PRIMARY KEY (digest, path)
+);
+
+CREATE TABLE plugin_packages (
+    id TEXT PRIMARY KEY,
+    digest TEXT NOT NULL REFERENCES plugin_package_blobs(digest)
+);
+
+CREATE TABLE plugin_data (
+    package_id TEXT NOT NULL,
+    scope_id TEXT NOT NULL,
+    key TEXT NOT NULL,
+    revision INTEGER NOT NULL CHECK (revision > 0),
+    value_json TEXT CHECK (value_json IS NULL OR json_valid(value_json)),
+    PRIMARY KEY (package_id, scope_id, key)
+);
+
+CREATE TABLE plugin_execution_receipts (
+    package_id TEXT NOT NULL,
+    scope_id TEXT NOT NULL,
+    operation_id TEXT NOT NULL,
+    receipt_json TEXT NOT NULL CHECK (json_valid(receipt_json)),
+    PRIMARY KEY (package_id, scope_id, operation_id)
+);
+
+CREATE TABLE graph_epochs (
+    root_session_id TEXT NOT NULL,
+    epoch INTEGER NOT NULL CHECK(epoch > 0),
+    graph_id TEXT NOT NULL UNIQUE,
+    mode TEXT NOT NULL CHECK(mode IN ('graph', 'swarm')),
+    created_at INTEGER NOT NULL CHECK(created_at >= 0),
+    stop_requested INTEGER NOT NULL DEFAULT 0 CHECK(stop_requested IN (0, 1)),
+    PRIMARY KEY(root_session_id, epoch)
+);
+
+CREATE TABLE graph_updates (
+    graph_id TEXT NOT NULL REFERENCES graph_epochs(graph_id),
+    update_id TEXT NOT NULL,
+    revision INTEGER NOT NULL CHECK(revision > 0),
+    fingerprint TEXT NOT NULL,
+    update_json TEXT NOT NULL CHECK(json_valid(update_json)),
+    byte_count INTEGER GENERATED ALWAYS AS (length(CAST(update_json AS BLOB))) STORED,
+    committed_at INTEGER NOT NULL CHECK(committed_at >= 0),
+    PRIMARY KEY(graph_id, revision),
+    UNIQUE(graph_id, update_id)
+);
+
+CREATE TABLE graph_intents (
+    graph_id TEXT NOT NULL REFERENCES graph_epochs(graph_id),
+    work_id TEXT NOT NULL,
+    intent_json TEXT NOT NULL CHECK(json_valid(intent_json)),
+    PRIMARY KEY(graph_id, work_id)
+);
+
+CREATE TABLE graph_wakes (
+    graph_id TEXT NOT NULL REFERENCES graph_epochs(graph_id),
+    snapshot_key TEXT NOT NULL,
+    request_json TEXT NOT NULL CHECK(json_valid(request_json)),
+    PRIMARY KEY(graph_id, snapshot_key)
+);
+
+CREATE TABLE request_compositions (
+    digest TEXT PRIMARY KEY NOT NULL,
+    surface BLOB NOT NULL CHECK(length(surface) <= 4194304)
+);
+
+CREATE TABLE model_request_compositions (
+    event_id TEXT PRIMARY KEY NOT NULL REFERENCES event_log(event_id),
+    digest TEXT NOT NULL REFERENCES request_compositions(digest)
+);
+
+CREATE INDEX model_request_composition_digest ON model_request_compositions(digest);
+
+CREATE TABLE session_managers (
+    session_id TEXT PRIMARY KEY NOT NULL,
+    package_id TEXT NOT NULL,
+    scope_id TEXT NOT NULL,
+    fingerprint TEXT NOT NULL
+);
+
+CREATE TABLE host_effects (
+    id TEXT PRIMARY KEY,
+    package_id TEXT NOT NULL,
+    scope_id TEXT NOT NULL,
+    request TEXT NOT NULL CHECK(json_valid(request) AND length(CAST(request AS BLOB)) <= 8388608),
+    outcome TEXT CHECK(outcome IS NULL OR (json_valid(outcome) AND length(CAST(outcome AS BLOB)) <= 8388608)),
+    payload BLOB CHECK(payload IS NULL OR length(payload) <= 5242880),
+    CHECK(outcome IS NOT NULL OR payload IS NULL)
+) STRICT;
+
+CREATE INDEX host_effects_namespace ON host_effects(package_id, scope_id);
+
+INSERT INTO session_catalog_revision VALUES (1, 0);
+INSERT INTO plugin_composition VALUES (1, '{"generation":0,"packageLayers":[],"overlays":[]}');
+
+PRAGMA user_version = 1;
diff --git a/crates/event-log/migrations/0002_session_read_state.sql b/crates/event-log/migrations/0002_session_read_state.sql
deleted file mode 100644
index be0cfa8e6f..0000000000
--- a/crates/event-log/migrations/0002_session_read_state.sql
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-PRAGMA user_version = 2;
-
--- Read acknowledgement is durable control state, never a rebuilt event cache.
-CREATE TABLE IF NOT EXISTS session_read_state (
-    session_id TEXT PRIMARY KEY REFERENCES session_control(id),
-    has_unread INTEGER NOT NULL CHECK(has_unread IN (0, 1)),
-    last_read_message_id TEXT
-);
-
--- Existing Rust sessions predate acknowledgement support. Terminal history
--- initializes unread once; subsequent cache reconstruction cannot change it.
-INSERT OR IGNORE INTO session_read_state (session_id, has_unread, last_read_message_id)
-SELECT id, EXISTS(
-    SELECT 1 FROM runtime_events
-    WHERE json_extract(event_json, '$.invocation.session_id') = session_control.id
-      AND kind = 'invocation_ended'
-), NULL FROM session_control;
diff --git a/crates/event-log/migrations/0003_interactions.sql b/crates/event-log/migrations/0003_interactions.sql
deleted file mode 100644
index 6434129521..0000000000
--- a/crates/event-log/migrations/0003_interactions.sql
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
--- Independent canonical interaction facts; grants never derive from UI history.
-PRAGMA user_version = 3;
-CREATE TABLE IF NOT EXISTS interaction_requests (
-    request_id TEXT PRIMARY KEY,
-    session_id TEXT NOT NULL,
-    created_at INTEGER NOT NULL,
-    record_json TEXT NOT NULL CHECK(length(CAST(record_json AS BLOB)) <= 20480)
-);
-CREATE INDEX IF NOT EXISTS interaction_session_pending ON interaction_requests(session_id, created_at, request_id);
-CREATE TABLE IF NOT EXISTS interaction_outcomes (
-    request_id TEXT PRIMARY KEY REFERENCES interaction_requests(request_id),
-    outcome_json TEXT NOT NULL CHECK(length(CAST(outcome_json AS BLOB)) <= 8192)
-);
-CREATE TABLE IF NOT EXISTS client_capability_session_grants (
-    authority_key TEXT PRIMARY KEY,
-    record_json TEXT NOT NULL CHECK(length(CAST(record_json AS BLOB)) <= 12288)
-);
diff --git a/crates/event-log/migrations/0004_artifacts.sql b/crates/event-log/migrations/0004_artifacts.sql
deleted file mode 100644
index 62642c537c..0000000000
--- a/crates/event-log/migrations/0004_artifacts.sql
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-
--- Artifact control authority is independent of invocation history. Metadata and
--- immutable payload become visible together; query projections never scan BLOBs.
-PRAGMA user_version = 4;
-CREATE TABLE IF NOT EXISTS artifacts (
-    session_id TEXT NOT NULL REFERENCES session_control(id),
-    id TEXT NOT NULL,
-    created_at INTEGER NOT NULL CHECK(created_at >= 0),
-    record_json TEXT NOT NULL CHECK(length(CAST(record_json AS BLOB)) <= 16384),
-    content_sha256 TEXT NOT NULL,
-    payload BLOB NOT NULL CHECK(length(payload) <= 52428800),
-    PRIMARY KEY (session_id, id),
-    CHECK(json_extract(record_json, '$.sessionId') = session_id),
-    CHECK(json_extract(record_json, '$.id') = id),
-    CHECK(json_extract(record_json, '$.createdAt') = created_at),
-    CHECK(json_extract(record_json, '$.sizeBytes') = length(payload))
-);
-CREATE INDEX IF NOT EXISTS artifacts_session_order ON artifacts(session_id, created_at DESC, id);
-CREATE TABLE IF NOT EXISTS artifact_catalog (
-    session_id TEXT PRIMARY KEY REFERENCES session_control(id),
-    revision INTEGER NOT NULL CHECK(revision > 0 AND revision <= 9007199254740991)
-);
diff --git a/crates/event-log/migrations/0005_tool_result_payloads.sql b/crates/event-log/migrations/0005_tool_result_payloads.sql
deleted file mode 100644
index cd9819f4ac..0000000000
--- a/crates/event-log/migrations/0005_tool_result_payloads.sql
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
--- Raw outcomes are part of the execution log; identity and digest live in T2.
-PRAGMA user_version = 5;
-CREATE TABLE IF NOT EXISTS tool_result_payloads (
-    event_id TEXT PRIMARY KEY NOT NULL REFERENCES runtime_events(event_id),
-    payload BLOB NOT NULL CHECK(length(payload) <= 67108864)
-);
diff --git a/crates/event-log/migrations/0006_shell_runs.sql b/crates/event-log/migrations/0006_shell_runs.sql
deleted file mode 100644
index 2c1ff39f78..0000000000
--- a/crates/event-log/migrations/0006_shell_runs.sql
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-
--- Operational shell authority belongs to the existing RootOwner/connection.
--- Invocation history remains canonical for model/tool interactions, not for
--- live process attachment. No operating-system PID is persisted for recovery.
-PRAGMA user_version = 6;
-CREATE TABLE IF NOT EXISTS shell_runs (
-    session_id TEXT NOT NULL REFERENCES session_control(id),
-    id TEXT NOT NULL,
-    started_at INTEGER NOT NULL CHECK(started_at >= 0),
-    active INTEGER NOT NULL CHECK(active IN (0, 1)),
-    record_json TEXT NOT NULL CHECK(length(CAST(record_json AS BLOB)) <= 4194304),
-    PRIMARY KEY (session_id, id),
-    CHECK(json_extract(record_json, '$.sessionId') IS session_id),
-    CHECK(json_extract(record_json, '$.id') IS id),
-    CHECK(json_extract(record_json, '$.startedAt') IS started_at),
-    CHECK(active IS (json_extract(record_json, '$.state.kind') IN ('starting', 'running')))
-);
-CREATE INDEX IF NOT EXISTS shell_runs_session_order ON shell_runs(session_id, started_at, id);
-CREATE INDEX IF NOT EXISTS shell_runs_active ON shell_runs(active) WHERE active = 1;
diff --git a/crates/event-log/migrations/0007_steering.sql b/crates/event-log/migrations/0007_steering.sql
deleted file mode 100644
index 059b92ab63..0000000000
--- a/crates/event-log/migrations/0007_steering.sql
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-PRAGMA user_version = 7;
-CREATE UNIQUE INDEX IF NOT EXISTS message_steering_identity ON runtime_events(
-    json_extract(event_json, '$.invocation.session_id'),
-    json_extract(event_json, '$.fact.message.message_id')
-) WHERE kind = 'message_steered';
-
--- Client message identities are Session-scoped. Only disposable projections
--- change; all existing canonical execution facts remain byte-for-byte intact.
-DROP TABLE IF EXISTS transcript_rows;
-DROP TABLE IF EXISTS transcript_progress;
diff --git a/crates/event-log/migrations/0008_message_sources.sql b/crates/event-log/migrations/0008_message_sources.sql
deleted file mode 100644
index 28258e1fda..0000000000
--- a/crates/event-log/migrations/0008_message_sources.sql
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-PRAGMA user_version = 8;
-DROP INDEX IF EXISTS message_steering_identity;
--- Source identity lookup is rebuilt from the immutable log on initialization.
--- Only disposable UI projections need invalidation for source-owned user IDs.
-DROP TABLE IF EXISTS transcript_rows;
-DROP TABLE IF EXISTS transcript_progress;
diff --git a/crates/event-log/migrations/0009_message_admissions.sql b/crates/event-log/migrations/0009_message_admissions.sql
deleted file mode 100644
index bc362f584c..0000000000
--- a/crates/event-log/migrations/0009_message_admissions.sql
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-PRAGMA user_version = 9;
-CREATE TABLE message_admissions (
-    session_id TEXT NOT NULL,
-    message_id TEXT NOT NULL,
-    position INTEGER NOT NULL,
-    record_json TEXT NOT NULL,
-    PRIMARY KEY(session_id, message_id)
-);
-CREATE INDEX message_admission_order ON message_admissions(session_id, position, message_id);
diff --git a/crates/event-log/migrations/0010_message_queue.sql b/crates/event-log/migrations/0010_message_queue.sql
deleted file mode 100644
index 0557d101dc..0000000000
--- a/crates/event-log/migrations/0010_message_queue.sql
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-PRAGMA user_version = 10;
-CREATE TABLE message_queue_state (
-    session_id TEXT PRIMARY KEY NOT NULL,
-    revision INTEGER NOT NULL CHECK(revision >= 0)
-);
-CREATE TABLE message_cancellations (
-    session_id TEXT NOT NULL,
-    message_id TEXT NOT NULL,
-    cancellation_id TEXT NOT NULL,
-    PRIMARY KEY(session_id, message_id)
-);
diff --git a/crates/event-log/migrations/0011_queue_command_receipts.sql b/crates/event-log/migrations/0011_queue_command_receipts.sql
deleted file mode 100644
index cc3ba16057..0000000000
--- a/crates/event-log/migrations/0011_queue_command_receipts.sql
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-PRAGMA user_version = 11;
-
-CREATE TABLE queue_command_receipts (
-    host_epoch TEXT NOT NULL,
-    session_id TEXT NOT NULL,
-    operation TEXT NOT NULL,
-    command_id TEXT NOT NULL,
-    fingerprint TEXT NOT NULL,
-    receipt_json TEXT NOT NULL,
-    PRIMARY KEY(host_epoch, session_id, operation, command_id)
-);
diff --git a/crates/event-log/migrations/0012_message_submit_receipts.sql b/crates/event-log/migrations/0012_message_submit_receipts.sql
deleted file mode 100644
index dfd72a56cd..0000000000
--- a/crates/event-log/migrations/0012_message_submit_receipts.sql
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-PRAGMA user_version = 12;
-
-CREATE TABLE message_submit_receipts (
-    host_epoch TEXT NOT NULL,
-    session_id TEXT NOT NULL,
-    message_id TEXT NOT NULL,
-    receipt_json TEXT NOT NULL,
-    PRIMARY KEY(host_epoch, session_id, message_id)
-);
diff --git a/crates/event-log/migrations/0013_message_interrupts.sql b/crates/event-log/migrations/0013_message_interrupts.sql
deleted file mode 100644
index f187b6876e..0000000000
--- a/crates/event-log/migrations/0013_message_interrupts.sql
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-PRAGMA user_version = 13;
-
--- A NULL fence records a rejected exact command; accepted fences revoke pending
--- delivery atomically. Terminal results remain canonical invocation facts.
-CREATE TABLE message_interrupt_receipts (
-    host_epoch TEXT NOT NULL,
-    session_id TEXT NOT NULL,
-    interrupt_id TEXT NOT NULL,
-    turn_id TEXT NOT NULL,
-    run_id TEXT NOT NULL,
-    fence_json TEXT,
-    PRIMARY KEY(host_epoch, session_id, interrupt_id)
-);
-CREATE INDEX message_interrupt_run ON message_interrupt_receipts(session_id, run_id)
-    WHERE fence_json IS NOT NULL;
diff --git a/crates/event-log/migrations/0014_continuation_claims.sql b/crates/event-log/migrations/0014_continuation_claims.sql
deleted file mode 100644
index f50894cade..0000000000
--- a/crates/event-log/migrations/0014_continuation_claims.sql
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
--- The canonical opening is the claim. These indexes add no independent state.
-CREATE UNIQUE INDEX continuation_claim_id ON runtime_events (
-    json_extract(event_json, '$.fact.input.claim.id')
-) WHERE kind = 'invocation_opened' AND json_extract(event_json, '$.fact.input.kind') = 'continuation';
-
-CREATE UNIQUE INDEX continuation_source_boundary ON runtime_events (
-    json_extract(event_json, '$.invocation.session_id'),
-    json_extract(event_json, '$.fact.input.claim.source.invocation.run_id'),
-    json_extract(event_json, '$.fact.input.claim.source.high_water')
-) WHERE kind = 'invocation_opened' AND json_extract(event_json, '$.fact.input.kind') = 'continuation';
-
-PRAGMA user_version = 14;
diff --git a/crates/event-log/migrations/0015_projects.sql b/crates/event-log/migrations/0015_projects.sql
deleted file mode 100644
index 1a37ca85ac..0000000000
--- a/crates/event-log/migrations/0015_projects.sql
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-
--- Project control state shares the Session transaction authority, not runtime history.
-CREATE TABLE projects (
-    id TEXT PRIMARY KEY NOT NULL,
-    identity TEXT NOT NULL UNIQUE,
-    name TEXT NOT NULL CHECK(length(CAST(name AS BLOB)) BETWEEN 1 AND 16384),
-    last_used_at INTEGER NOT NULL CHECK(last_used_at BETWEEN 0 AND 9007199254740991),
-    archived_at INTEGER CHECK(archived_at BETWEEN 0 AND 9007199254740991)
-) STRICT;
-
--- Canonical IDs and absorbed aliases occupy one namespace.
-CREATE TABLE project_identities (
-    id TEXT PRIMARY KEY NOT NULL,
-    project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE
-) STRICT;
-CREATE INDEX project_identity_owner ON project_identities(project_id, id);
-
-CREATE TABLE project_locations (
-    project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
-    path TEXT NOT NULL CHECK(length(CAST(path AS BLOB)) BETWEEN 1 AND 4096),
-    is_worktree INTEGER NOT NULL CHECK(is_worktree IN (0, 1)),
-    last_used_at INTEGER NOT NULL CHECK(last_used_at BETWEEN 0 AND 9007199254740991),
-    PRIMARY KEY(project_id, path)
-) STRICT;
-
-PRAGMA user_version = 15;
-
diff --git a/crates/event-log/migrations/0016_interrupted_transcript.sql b/crates/event-log/migrations/0016_interrupted_transcript.sql
deleted file mode 100644
index 806e2160f0..0000000000
--- a/crates/event-log/migrations/0016_interrupted_transcript.sql
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-
--- Rebuild only the disposable transcript projection with epoch 152 markers.
--- Canonical runtime events and execution state remain unchanged.
-DROP TABLE IF EXISTS transcript_rows;
-DROP TABLE IF EXISTS transcript_progress;
-PRAGMA user_version = 16;
diff --git a/crates/event-log/migrations/0018_workhub_stops.sql b/crates/event-log/migrations/0018_workhub_stops.sql
deleted file mode 100644
index 136d09397a..0000000000
--- a/crates/event-log/migrations/0018_workhub_stops.sql
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-
-CREATE TABLE workhub_stops (
-    action_id TEXT PRIMARY KEY NOT NULL,
-    delegation_action_id TEXT NOT NULL,
-    record_json TEXT NOT NULL CHECK(json_valid(record_json)),
-    resolution_json TEXT CHECK(resolution_json IS NULL OR json_valid(resolution_json))
-);
-CREATE INDEX workhub_stop_subject ON workhub_stops(delegation_action_id);
-CREATE UNIQUE INDEX workhub_stop_claim ON workhub_stops(delegation_action_id)
-    WHERE resolution_json IS NULL OR json_extract(resolution_json, '$.outcome') != 'not_owned';
-PRAGMA user_version = 18;
diff --git a/crates/event-log/migrations/0019_workhub_resume.sql b/crates/event-log/migrations/0019_workhub_resume.sql
deleted file mode 100644
index b0a58ef5e4..0000000000
--- a/crates/event-log/migrations/0019_workhub_resume.sql
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-
-DROP INDEX workhub_action_identity;
-CREATE UNIQUE INDEX workhub_action_identity ON runtime_events (
-    CASE
-        WHEN kind = 'workhub_delegated'
-            THEN json_extract(event_json, '$.fact.delegation.action_id')
-        WHEN kind = 'workhub_resume_observed'
-            THEN json_extract(event_json, '$.fact.resume.action_id')
-        WHEN kind = 'invocation_opened'
-            AND json_extract(event_json, '$.fact.input.kind') = 'continuation'
-            THEN json_extract(event_json, '$.fact.input.workhub_resume.action_id')
-    END
-) WHERE kind IN ('workhub_delegated', 'workhub_resume_observed', 'invocation_opened');
-
-PRAGMA user_version = 19;
diff --git a/crates/event-log/migrations/0020_session_events.sql b/crates/event-log/migrations/0020_session_events.sql
deleted file mode 100644
index dcbe7b16bb..0000000000
--- a/crates/event-log/migrations/0020_session_events.sql
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
--- One ordered ledger, with owner-specific read views. Existing execution bytes
--- and positions remain unchanged; Session facts never enter a Run prefix.
-CREATE TABLE event_log (
-    sequence INTEGER PRIMARY KEY AUTOINCREMENT CHECK(sequence > 0),
-    event_id TEXT NOT NULL UNIQUE,
-    invocation_id TEXT,
-    kind TEXT NOT NULL,
-    operation_id TEXT,
-    event_json TEXT NOT NULL,
-    CHECK(invocation_id IS NOT NULL OR operation_id IS NULL)
-);
-INSERT INTO event_log SELECT * FROM runtime_events ORDER BY sequence;
-UPDATE sqlite_sequence SET seq = MAX(seq, COALESCE(
-    (SELECT seq FROM sqlite_sequence WHERE name = 'runtime_events'), 0
-)) WHERE name = 'event_log';
-
-CREATE TABLE tool_result_payloads_next (
-    event_id TEXT PRIMARY KEY NOT NULL REFERENCES event_log(event_id),
-    payload BLOB NOT NULL CHECK(length(payload) <= 67108864)
-);
-INSERT INTO tool_result_payloads_next SELECT * FROM tool_result_payloads;
-DROP TABLE tool_result_payloads;
-DROP TABLE runtime_events;
-ALTER TABLE tool_result_payloads_next RENAME TO tool_result_payloads;
-
-CREATE VIEW runtime_events AS
-    SELECT * FROM event_log WHERE invocation_id IS NOT NULL;
-CREATE VIEW session_events AS
-    SELECT sequence, event_id, kind, event_json FROM event_log WHERE invocation_id IS NULL;
-
-CREATE INDEX invocation_sequence ON event_log(invocation_id, sequence);
-CREATE INDEX session_event_sequence ON event_log(
-    json_extract(event_json, '$.invocation.session_id'), sequence
-);
-CREATE INDEX session_control_sequence ON event_log(
-    json_extract(event_json, '$.session_id'), sequence
-) WHERE invocation_id IS NULL;
-CREATE INDEX turn_opening_lookup ON event_log(
-    json_extract(event_json, '$.invocation.session_id'),
-    json_extract(event_json, '$.invocation.turn_id'), sequence DESC
-) WHERE kind = 'invocation_opened';
-CREATE UNIQUE INDEX operation_fact ON event_log(operation_id, kind)
-    WHERE operation_id IS NOT NULL;
-CREATE UNIQUE INDEX invocation_boundary ON event_log(invocation_id, kind)
-    WHERE kind IN ('invocation_opened', 'invocation_ended');
-CREATE UNIQUE INDEX message_steering_identity ON event_log(
-    json_extract(event_json, '$.invocation.session_id'),
-    json_extract(event_json, '$.fact.message.message_id')
-) WHERE kind = 'message_steered';
-CREATE UNIQUE INDEX continuation_claim_id ON event_log(
-    json_extract(event_json, '$.fact.input.claim.id')
-) WHERE kind = 'invocation_opened' AND json_extract(event_json, '$.fact.input.kind') = 'continuation';
-CREATE UNIQUE INDEX continuation_source_boundary ON event_log(
-    json_extract(event_json, '$.invocation.session_id'),
-    json_extract(event_json, '$.fact.input.claim.source.invocation.run_id'),
-    json_extract(event_json, '$.fact.input.claim.source.high_water')
-) WHERE kind = 'invocation_opened' AND json_extract(event_json, '$.fact.input.kind') = 'continuation';
-CREATE UNIQUE INDEX workhub_action_identity ON event_log(
-    CASE
-        WHEN kind = 'workhub_stop_requested'
-            THEN json_extract(event_json, '$.fact.intent.request.action_id')
-        WHEN kind = 'workhub_delegated'
-            THEN json_extract(event_json, '$.fact.delegation.action_id')
-        WHEN kind = 'workhub_resume_observed'
-            THEN json_extract(event_json, '$.fact.resume.action_id')
-        WHEN kind = 'invocation_opened'
-            AND json_extract(event_json, '$.fact.input.kind') = 'continuation'
-            THEN json_extract(event_json, '$.fact.input.workhub_resume.action_id')
-    END
-) WHERE kind IN ('workhub_delegated', 'workhub_resume_observed', 'invocation_opened', 'workhub_stop_requested');
-
-CREATE UNIQUE INDEX workhub_stop_resolution ON event_log(
-    json_extract(event_json, '$.fact.action_id')
-) WHERE kind = 'workhub_stop_resolved';
-CREATE INDEX workhub_stop_request_subject ON event_log(
-    json_extract(event_json, '$.fact.intent.delegation_action_id')
-) WHERE kind = 'workhub_stop_requested';
-
--- Pre-cutover receipts have no historical sequence or timestamp. Keep them
--- readable, never invent a timeline for them, and never write new receipts here.
-ALTER TABLE workhub_stops RENAME TO legacy_workhub_stops;
-CREATE VIEW workhub_stops AS
-    SELECT json_extract(q.event_json, '$.fact.intent.request.action_id') AS action_id,
-           json_extract(q.event_json, '$.fact.intent.delegation_action_id') AS delegation_action_id,
-           json_extract(q.event_json, '$.fact.intent') AS record_json,
-           json_extract(r.event_json, '$.fact.resolution') AS resolution_json
-    FROM session_events q LEFT JOIN session_events r
-      ON r.kind = 'workhub_stop_resolved'
-     AND json_extract(r.event_json, '$.fact.action_id') =
-         json_extract(q.event_json, '$.fact.intent.request.action_id')
-    WHERE q.kind = 'workhub_stop_requested'
-    UNION ALL
-    SELECT q.action_id, q.delegation_action_id, q.record_json,
-           COALESCE(json_extract(r.event_json, '$.fact.resolution'), q.resolution_json)
-    FROM legacy_workhub_stops q LEFT JOIN session_events r
-      ON r.kind = 'workhub_stop_resolved'
-     AND json_extract(r.event_json, '$.fact.action_id') = q.action_id;
-
-PRAGMA user_version = 20;
diff --git a/crates/event-log/migrations/0021_workhub_assignment_target.sql b/crates/event-log/migrations/0021_workhub_assignment_target.sql
deleted file mode 100644
index 688732401a..0000000000
--- a/crates/event-log/migrations/0021_workhub_assignment_target.sql
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-
-CREATE INDEX workhub_assignment_target ON event_log(
-    json_extract(event_json, '$.fact.delegation.target.session_id'), sequence DESC
-) WHERE kind = 'workhub_delegated';
-
-PRAGMA user_version = 21;
diff --git a/crates/event-log/migrations/0022_workhub_correction.sql b/crates/event-log/migrations/0022_workhub_correction.sql
deleted file mode 100644
index 53d7eaaf51..0000000000
--- a/crates/event-log/migrations/0022_workhub_correction.sql
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
--- Assignments keep their original owner and bytes. Replacement commits have
--- Session ownership; the intent, not the later assignment, owns the action ID.
-DROP INDEX workhub_action_identity;
-CREATE UNIQUE INDEX workhub_action_identity ON event_log(
-    CASE
-        WHEN kind IN ('workhub_stop_requested', 'workhub_correction_requested')
-            THEN json_extract(event_json, '$.fact.intent.request.action_id')
-        WHEN kind = 'workhub_delegated' AND invocation_id IS NOT NULL
-            THEN json_extract(event_json, '$.fact.delegation.action_id')
-        WHEN kind = 'workhub_resume_observed'
-            THEN json_extract(event_json, '$.fact.resume.action_id')
-        WHEN kind = 'invocation_opened'
-            AND json_extract(event_json, '$.fact.input.kind') = 'continuation'
-            THEN json_extract(event_json, '$.fact.input.workhub_resume.action_id')
-    END
-) WHERE kind IN ('workhub_delegated', 'workhub_resume_observed', 'invocation_opened', 'workhub_stop_requested', 'workhub_correction_requested');
-
-CREATE UNIQUE INDEX workhub_assignment_action ON event_log(
-    json_extract(event_json, '$.fact.delegation.action_id')
-) WHERE kind = 'workhub_delegated';
-CREATE UNIQUE INDEX workhub_correction_subject ON event_log(
-    json_extract(event_json, '$.fact.intent.request.replaces_action_id')
-) WHERE kind = 'workhub_correction_requested';
-CREATE UNIQUE INDEX workhub_correction_resolution ON event_log(
-    json_extract(event_json, '$.fact.action_id')
-) WHERE kind IN ('workhub_superseded', 'workhub_correction_aborted');
-
-CREATE VIEW workhub_assignments AS
-    SELECT sequence, event_id, event_json,
-           COALESCE(json_extract(event_json, '$.invocation'),
-                    json_extract(event_json, '$.fact.coordinator')) AS coordinator_json
-    FROM event_log WHERE kind = 'workhub_delegated';
-
-CREATE VIEW workhub_corrections AS
-    SELECT q.sequence,
-           json_extract(q.event_json, '$.fact.intent.request.action_id') AS action_id,
-           json_extract(q.event_json, '$.fact.intent.request.replaces_action_id') AS replaces_action_id,
-           json_extract(q.event_json, '$.fact.intent') AS intent_json,
-           r.kind AS resolution_kind,
-           json_extract(r.event_json, '$.fact.reason') AS abort_reason
-    FROM session_events q LEFT JOIN session_events r
-      ON r.kind IN ('workhub_superseded', 'workhub_correction_aborted')
-     AND json_extract(r.event_json, '$.fact.action_id') =
-         json_extract(q.event_json, '$.fact.intent.request.action_id')
-    WHERE q.kind = 'workhub_correction_requested';
-
-PRAGMA user_version = 22;
-
diff --git a/crates/event-log/migrations/0023_handoff_claims.sql b/crates/event-log/migrations/0023_handoff_claims.sql
deleted file mode 100644
index 9098e88b4a..0000000000
--- a/crates/event-log/migrations/0023_handoff_claims.sql
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
--- Both continuations acquire the source through their canonical opening.
-DROP INDEX continuation_claim_id;
-DROP INDEX continuation_source_boundary;
-CREATE UNIQUE INDEX continuation_claim_id ON event_log(
-    json_extract(event_json, '$.fact.input.claim.id')
-) WHERE kind = 'invocation_opened' AND json_extract(event_json, '$.fact.input.kind') IN ('continuation','handoff');
-CREATE UNIQUE INDEX continuation_source_boundary ON event_log(
-    json_extract(event_json, '$.invocation.session_id'),
-    json_extract(event_json, '$.fact.input.claim.source.invocation.run_id'),
-    json_extract(event_json, '$.fact.input.claim.source.high_water')
-) WHERE kind = 'invocation_opened' AND json_extract(event_json, '$.fact.input.kind') IN ('continuation','handoff');
-
--- No two paused owners can reserve the same future physical execution.
-CREATE UNIQUE INDEX handoff_successor_run ON event_log(
-    json_extract(event_json, '$.fact.outcome.pause.intent.successor_run_id')
-) WHERE kind = 'invocation_ended' AND json_extract(event_json, '$.fact.outcome.kind') = 'handoff_paused';
-CREATE UNIQUE INDEX handoff_successor_invocation ON event_log(
-    json_extract(event_json, '$.fact.outcome.pause.intent.successor_invocation_id')
-) WHERE kind = 'invocation_ended' AND json_extract(event_json, '$.fact.outcome.kind') = 'handoff_paused';
-CREATE UNIQUE INDEX handoff_claim ON event_log(
-    json_extract(event_json, '$.fact.outcome.pause.intent.claim_id')
-) WHERE kind = 'invocation_ended' AND json_extract(event_json, '$.fact.outcome.kind') = 'handoff_paused';
-
-PRAGMA user_version = 23;
diff --git a/crates/event-log/migrations/0024_plugins.sql b/crates/event-log/migrations/0024_plugins.sql
deleted file mode 100644
index 0359080892..0000000000
--- a/crates/event-log/migrations/0024_plugins.sql
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-
-CREATE TABLE plugin_composition (
-    singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
-    ledger_json TEXT NOT NULL CHECK (json_valid(ledger_json))
-);
-INSERT INTO plugin_composition VALUES (1, '{"generation":0,"packageLayers":[],"overlays":[]}');
-
-CREATE TABLE plugin_package_blobs (
-    digest TEXT PRIMARY KEY
-);
-CREATE TABLE plugin_package_files (
-    digest TEXT NOT NULL REFERENCES plugin_package_blobs(digest) ON DELETE CASCADE,
-    path TEXT NOT NULL,
-    payload BLOB NOT NULL,
-    PRIMARY KEY (digest, path)
-);
-CREATE TABLE plugin_packages (
-    id TEXT PRIMARY KEY,
-    digest TEXT NOT NULL REFERENCES plugin_package_blobs(digest)
-);
-
-CREATE TABLE plugin_data (
-    package_id TEXT NOT NULL,
-    scope_id TEXT NOT NULL,
-    key TEXT NOT NULL,
-    revision INTEGER NOT NULL CHECK (revision > 0),
-    value_json TEXT CHECK (value_json IS NULL OR json_valid(value_json)),
-    PRIMARY KEY (package_id, scope_id, key)
-);
-
-CREATE TABLE plugin_execution_receipts (
-    package_id TEXT NOT NULL,
-    scope_id TEXT NOT NULL,
-    operation_id TEXT NOT NULL,
-    receipt_json TEXT NOT NULL CHECK (json_valid(receipt_json)),
-    PRIMARY KEY (package_id, scope_id, operation_id)
-);
-
-PRAGMA user_version = 24;
diff --git a/crates/event-log/migrations/0025_graph.sql b/crates/event-log/migrations/0025_graph.sql
deleted file mode 100644
index 1db96c82ce..0000000000
--- a/crates/event-log/migrations/0025_graph.sql
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-CREATE TABLE graph_epochs (
-    root_session_id TEXT NOT NULL,
-    epoch INTEGER NOT NULL CHECK(epoch > 0),
-    graph_id TEXT NOT NULL UNIQUE,
-    mode TEXT NOT NULL CHECK(mode IN ('graph', 'swarm')),
-    created_at INTEGER NOT NULL CHECK(created_at >= 0),
-    stop_requested INTEGER NOT NULL DEFAULT 0 CHECK(stop_requested IN (0, 1)),
-    PRIMARY KEY(root_session_id, epoch)
-);
-CREATE TABLE graph_updates (
-    graph_id TEXT NOT NULL REFERENCES graph_epochs(graph_id),
-    update_id TEXT NOT NULL,
-    revision INTEGER NOT NULL CHECK(revision > 0),
-    fingerprint TEXT NOT NULL,
-    update_json TEXT NOT NULL CHECK(json_valid(update_json)),
-    byte_count INTEGER GENERATED ALWAYS AS (length(CAST(update_json AS BLOB))) STORED,
-    committed_at INTEGER NOT NULL CHECK(committed_at >= 0),
-    PRIMARY KEY(graph_id, revision),
-    UNIQUE(graph_id, update_id)
-);
-CREATE TABLE graph_intents (
-    graph_id TEXT NOT NULL REFERENCES graph_epochs(graph_id),
-    work_id TEXT NOT NULL,
-    intent_json TEXT NOT NULL CHECK(json_valid(intent_json)),
-    PRIMARY KEY(graph_id, work_id)
-);
-CREATE TABLE graph_wakes (
-    graph_id TEXT NOT NULL REFERENCES graph_epochs(graph_id),
-    snapshot_key TEXT NOT NULL,
-    request_json TEXT NOT NULL CHECK(json_valid(request_json)),
-    PRIMARY KEY(graph_id, snapshot_key)
-);
-PRAGMA user_version = 25;
diff --git a/crates/event-log/migrations/0026_request_composition.sql b/crates/event-log/migrations/0026_request_composition.sql
deleted file mode 100644
index adf12a38c8..0000000000
--- a/crates/event-log/migrations/0026_request_composition.sql
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-CREATE TABLE request_compositions (
-    digest TEXT PRIMARY KEY NOT NULL,
-    surface BLOB NOT NULL CHECK(length(surface) <= 4194304)
-);
-CREATE TABLE model_request_compositions (
-    event_id TEXT PRIMARY KEY NOT NULL REFERENCES event_log(event_id),
-    digest TEXT NOT NULL REFERENCES request_compositions(digest)
-);
-CREATE INDEX model_request_composition_digest ON model_request_compositions(digest);
-PRAGMA user_version = 26;
diff --git a/crates/event-log/migrations/0027_session_managers.sql b/crates/event-log/migrations/0027_session_managers.sql
deleted file mode 100644
index fe1dd4f909..0000000000
--- a/crates/event-log/migrations/0027_session_managers.sql
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-CREATE TABLE session_managers (
-    session_id TEXT PRIMARY KEY NOT NULL,
-    package_id TEXT NOT NULL,
-    scope_id TEXT NOT NULL,
-    fingerprint TEXT NOT NULL
-);
-PRAGMA user_version = 27;
diff --git a/crates/event-log/src/archive/effective.rs b/crates/event-log/src/archive/effective.rs
index a34bb16538..8cbd4a2147 100644
--- a/crates/event-log/src/archive/effective.rs
+++ b/crates/event-log/src/archive/effective.rs
@@ -94,7 +94,7 @@ pub(crate) async fn affected_after(
 pub(crate) async fn validate_summary(
     connection: &mut SqliteConnection,
     source: &SourceEvidence,
-    expected: Option<&str>,
+    expected: &str,
     requested: u64,
 ) -> Result<(), StoreError> {
     let selection = Selection::resolve(connection, &source.scope).await?;
@@ -106,31 +106,11 @@ pub(crate) async fn validate_summary(
             "summary effective source changed after its request".into(),
         ));
     }
-    match expected {
-        Some(expected)
-            if digest_selected(connection, &selection, source, requested).await? == expected =>
-        {
-            Ok(())
-        }
-        Some(_) => Err(StoreError::InvalidTransition(
+    if digest_selected(connection, &selection, source, requested).await? == expected {
+        Ok(())
+    } else {
+        Err(StoreError::InvalidTransition(
             "summary effective source digest mismatch".into(),
-        )),
-        None => {
-            let archive_filter = Selection::predicate("a", "?3");
-            let target_filter = Selection::predicate("t", "?3");
-            let pruned: bool = sqlx::query_scalar(sqlx::AssertSqlSafe(format!(
-                "SELECT EXISTS(SELECT 1 FROM runtime_events a JOIN runtime_events t
-                 ON t.event_id=json_extract(a.event_json,'$.fact.placeholder.identity.runtime_event_id')
-                 WHERE a.kind='tool_result_archived' AND json_extract(a.event_json,'$.invocation.session_id')=?
-                   AND t.sequence <= ?2 AND {archive_filter} AND {target_filter})"
-            ))).bind(&selection.session).bind(source.high_water as i64).bind(&selection.lineage).fetch_one(connection).await?;
-            if pruned {
-                Err(StoreError::InvalidTransition(
-                    "legacy summary cannot cover archived projections".into(),
-                ))
-            } else {
-                Ok(())
-            }
-        }
+        ))
     }
 }
diff --git a/crates/event-log/src/context/boundary.rs b/crates/event-log/src/context/boundary.rs
index 2625396ea7..24db4571bb 100644
--- a/crates/event-log/src/context/boundary.rs
+++ b/crates/event-log/src/context/boundary.rs
@@ -105,9 +105,7 @@ pub(super) async fn summary_span(
     let work: bool = sqlx::query_scalar(
         "SELECT EXISTS(SELECT 1 FROM runtime_events WHERE invocation_id = ?1 AND sequence > ?2 AND sequence < ?3
          AND (kind IN ('message_steered','tool_dispatched','tool_rejected','tool_settled')
-           OR (kind = 'model_requested' AND COALESCE(json_extract(event_json, '$.fact.purpose'),
-             (SELECT CASE json_extract(o.event_json, '$.fact.input.kind') WHEN 'context_compact' THEN 'summary' ELSE 'main' END
-               FROM runtime_events o WHERE o.invocation_id = runtime_events.invocation_id AND o.kind = 'invocation_opened')) != 'summary')))",
+           OR (kind = 'model_requested' AND json_extract(event_json, '$.fact.purpose') != 'summary')))",
     ).bind(&event.invocation.invocation_id).bind(first as i64).bind(through as i64).fetch_one(connection).await?;
     if work {
         return Err(invalid(
@@ -131,10 +129,7 @@ pub(super) async fn summary_start(
            WHERE invocation_id = ?1 AND kind = 'context_checkpoint_recorded' AND sequence < ?2)
          SELECT (SELECT MIN(s.sequence) FROM runtime_events s
            WHERE s.invocation_id = ?1 AND s.kind = 'model_requested' AND s.sequence > cut AND s.sequence < ?2
-           AND (json_extract(s.event_json, '$.fact.purpose') = 'summary'
-             OR (json_extract(s.event_json, '$.fact.purpose') IS NULL AND EXISTS (
-               SELECT 1 FROM runtime_events o WHERE o.invocation_id = ?1 AND o.kind = 'invocation_opened'
-               AND json_extract(o.event_json, '$.fact.input.kind') = 'context_compact')))),
+           AND json_extract(s.event_json, '$.fact.purpose') = 'summary'),
            cut = 0 OR EXISTS (SELECT 1 FROM runtime_events completed
              JOIN runtime_events request ON request.invocation_id = completed.invocation_id
                AND request.operation_id = completed.operation_id AND request.kind = 'model_requested'
@@ -142,7 +137,7 @@ pub(super) async fn summary_start(
              WHERE completed.invocation_id = ?1 AND completed.kind = 'model_completed'
                AND completed.sequence > cut AND completed.sequence < ?2
                AND json_extract(opening.event_json, '$.fact.input.kind') != 'context_compact'
-               AND COALESCE(json_extract(request.event_json, '$.fact.purpose'), 'main') = 'main')
+               AND json_extract(request.event_json, '$.fact.purpose') = 'main')
          FROM boundary",
     ).bind(invocation).bind(through as i64).fetch_one(connection).await?;
     if !renewed {
diff --git a/crates/event-log/src/context/latest_main.rs b/crates/event-log/src/context/latest_main.rs
index 16512550a8..79283efa30 100644
--- a/crates/event-log/src/context/latest_main.rs
+++ b/crates/event-log/src/context/latest_main.rs
@@ -126,12 +126,12 @@ pub(super) async fn read_selected(
                 return None;
             }
             let kind: &str = row.try_get("opening_kind").ok()?;
-            let purpose: Option<&str> = row.try_get("purpose").ok()?;
+            let purpose: &str = row.try_get("purpose").ok()?;
             let purpose = match (kind, purpose) {
-                ("message" | "continuation", None | Some("main")) => ModelPurpose::Main,
-                ("handoff", Some("main")) => ModelPurpose::Main,
-                ("message" | "continuation" | "handoff" | "context_compact", Some("summary"))
-                | ("context_compact", None) => ModelPurpose::Summary,
+                ("message" | "continuation" | "handoff", "main") => ModelPurpose::Main,
+                ("message" | "continuation" | "handoff" | "context_compact", "summary") => {
+                    ModelPurpose::Summary
+                }
                 _ => return None,
             };
             if purpose == ModelPurpose::Summary {
diff --git a/crates/event-log/src/context/proof.rs b/crates/event-log/src/context/proof.rs
index 5c1f87a8c9..61dee8d32e 100644
--- a/crates/event-log/src/context/proof.rs
+++ b/crates/event-log/src/context/proof.rs
@@ -127,7 +127,9 @@ pub(super) async fn validate(
             high_water: *source_high_water,
             digest: source_digest.clone(),
         },
-        effective_source_digest.as_deref(),
+        effective_source_digest
+            .as_deref()
+            .ok_or_else(|| invalid("summary requires effective source evidence"))?,
         first_summary,
     )
     .await?;
@@ -153,14 +155,13 @@ pub(super) async fn validate(
         ));
     }
     let changed_route: bool = sqlx::query_scalar(
-        "SELECT EXISTS(SELECT 1 FROM runtime_events WHERE invocation_id = ?1 AND kind = 'model_requested' AND sequence >= ?10 AND sequence < ?2
-         AND (json_extract(event_json, '$.fact.purpose') = 'summary' OR (?3 AND json_extract(event_json, '$.fact.purpose') IS NULL))
-         AND (json_extract(event_json, '$.fact.model_id') != ?4 OR json_extract(event_json, '$.fact.route_identity') != ?5
-           OR json_extract(event_json, '$.fact.source_high_water') != ?6 OR json_extract(event_json, '$.fact.source_digest') != ?7
-           OR json_extract(event_json, '$.fact.checkpoint_event_id') IS NOT ?8
-           OR json_extract(event_json, '$.fact.effective_source_digest') IS NOT ?9))",
+        "SELECT EXISTS(SELECT 1 FROM runtime_events WHERE invocation_id = ?1 AND kind = 'model_requested' AND sequence >= ?9 AND sequence < ?2
+         AND json_extract(event_json, '$.fact.purpose') = 'summary'
+         AND (json_extract(event_json, '$.fact.model_id') != ?3 OR json_extract(event_json, '$.fact.route_identity') != ?4
+           OR json_extract(event_json, '$.fact.source_high_water') != ?5 OR json_extract(event_json, '$.fact.source_digest') != ?6
+           OR json_extract(event_json, '$.fact.checkpoint_event_id') IS NOT ?7
+           OR json_extract(event_json, '$.fact.effective_source_digest') IS NOT ?8))",
     ).bind(&event.invocation.invocation_id).bind(sequence as i64)
-        .bind(matches!(input, maka_runtime::event::InvocationInput::ContextCompact { .. }))
         .bind(model_id).bind(route_identity).bind(checkpoint.covered_through as i64).bind(&checkpoint.source_digest)
         .bind(&checkpoint.previous_checkpoint_id).bind(effective_source_digest).bind(first_summary as i64).fetch_one(&mut *connection).await?;
     if changed_route {
diff --git a/crates/event-log/src/context/request.rs b/crates/event-log/src/context/request.rs
index bdf0319236..14d0695b99 100644
--- a/crates/event-log/src/context/request.rs
+++ b/crates/event-log/src/context/request.rs
@@ -43,18 +43,6 @@ pub(super) async fn validate(
     else {
         return Ok(());
     };
-    // Legacy facts remain replayable. New explicit purposes must agree with the
-    // canonical opening; automatic summaries can never use the legacy default.
-    if purpose.is_none() {
-        let compact: bool=sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM runtime_events WHERE invocation_id=? AND kind='invocation_opened' AND json_extract(event_json,'$.fact.input.kind') IN ('context_compact','continuation','handoff'))")
-            .bind(&event.invocation.invocation_id).fetch_one(connection).await?;
-        if compact {
-            return Err(invalid(
-                "compact requires explicit summary purpose; continuation requires explicit main/summary purpose and effective source",
-            ));
-        }
-        return Ok(());
-    }
     let (opened, opening) = proof::by_kind(
         connection,
         &event.invocation.invocation_id,
@@ -125,13 +113,12 @@ pub(super) async fn validate(
         high_water: high,
         digest: source_digest.clone(),
     };
-    if effective_source_digest.is_none() {
-        return Err(invalid("model request requires effective source evidence"));
-    }
     crate::archive::validate_summary(
         connection,
         &source,
-        effective_source_digest.as_deref(),
+        effective_source_digest
+            .as_deref()
+            .ok_or_else(|| invalid("model request requires effective source evidence"))?,
         i64::MAX as u64,
     )
     .await?;
@@ -150,8 +137,15 @@ pub(super) async fn validate(
         if expected != *effective_source_digest {
             return Err(invalid("summary repair changed effective source"));
         }
-        crate::archive::validate_summary(connection, &source, expected.as_deref(), sequence as u64)
-            .await?;
+        crate::archive::validate_summary(
+            connection,
+            &source,
+            expected
+                .as_deref()
+                .ok_or_else(|| invalid("summary requires effective source evidence"))?,
+            sequence as u64,
+        )
+        .await?;
     }
     let first: Option<(String, String, i64, String, Option<String>)> = sqlx::query_as(
         "SELECT json_extract(event_json, '$.fact.model_id'), json_extract(event_json, '$.fact.route_identity'),
diff --git a/crates/event-log/src/effects.rs b/crates/event-log/src/effects.rs
new file mode 100644
index 0000000000..5f2d6096c4
--- /dev/null
+++ b/crates/event-log/src/effects.rs
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+//! One-shot Host resource facts outside an Agent invocation. Only the Host
+//! adapter writes these records; plugin business state uses separate storage.
+
+use crate::{EventLog, StoreError};
+use maka_plugins::{call::Identity, fiber, storage::Namespace};
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
+use sqlx::{Connection, Row};
+use uuid::Uuid;
+
+#[derive(Serialize, Deserialize)]
+#[serde(tag = "kind", content = "input", rename_all = "snake_case")]
+pub enum Operation {
+    File(maka_plugins::filesystem::Operation),
+    Model {
+        input: maka_plugins::llm::Generate,
+        model: maka_runtime::execution::ModelBinding,
+        thinking_level: Option<maka_runtime::execution::ThinkingLevel>,
+    },
+    Client {
+        input: maka_plugins::client_capability::Call,
+        provider: maka_runtime::capability::Identity,
+        registration_id: String,
+        offer_id: String,
+        tool_call_id: String,
+    },
+}
+#[derive(Serialize, Deserialize)]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+pub struct Request {
+    pub source: Identity,
+    pub owner: fiber::Identity,
+    pub boundary: maka_plugins::authorization::Boundary,
+    pub operation: Operation,
+}
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+#[serde(
+    tag = "kind",
+    rename_all = "snake_case",
+    rename_all_fields = "camelCase"
+)]
+pub enum Outcome {
+    Completed { value: Value },
+    Image { mime_type: String, digest: String },
+    Failed { message: String },
+    Unknown { message: String },
+}
+pub struct Record {
+    pub request: Request,
+    pub outcome: Option<Outcome>,
+    pub payload: Option<Vec<u8>>,
+}
+impl EventLog {
+    /// Caller may start the effect only after this durable admission succeeds.
+    pub async fn begin_host_effect(&self, request: Request) -> Result<Uuid, StoreError> {
+        self.validate_root()?;
+        if matches!(request.source, Identity::Agent { .. }) {
+            return Err(invalid("Agent effects belong to their invocation journal"));
+        }
+        let namespace = Namespace::new(
+            request.owner.package_id.clone(),
+            request.owner.scope.clone(),
+        )
+        .map_err(|error| invalid(error.to_string()))?;
+        let raw = serde_json::to_string(&request)?;
+        let id = Uuid::new_v4();
+        self.connection.run(move |connection| {
+            Box::pin(async move {
+            let mut tx = connection.begin_with("BEGIN IMMEDIATE").await?;
+            sqlx::query("INSERT INTO host_effects (id, package_id, scope_id, request) VALUES (?, ?, ?, ?)")
+                .bind(id.to_string()).bind(namespace.package()).bind(String::from(namespace.scope().clone())).bind(raw)
+                .execute(&mut *tx).await?;
+            tx.commit().await.map_err(StoreError::CommitUnknown)?;
+            Ok(id)
+            })
+        }).await
+    }
+
+    /// Settlement cannot overwrite a known outcome, including recovery's unknown.
+    pub async fn settle_host_effect(
+        &self,
+        id: Uuid,
+        outcome: Outcome,
+        payload: Option<Vec<u8>>,
+    ) -> Result<(), StoreError> {
+        self.validate_root()?;
+        match (&outcome, &payload) {
+            (Outcome::Image { mime_type, digest }, Some(bytes))
+                if matches!(
+                    mime_type.as_str(),
+                    "image/png" | "image/jpeg" | "image/gif" | "image/webp"
+                ) && &maka_runtime::artifact::content_digest(bytes) == digest => {}
+            (Outcome::Image { .. }, _) | (_, Some(_)) => {
+                return Err(invalid("invalid effect image payload"));
+            }
+            _ => {}
+        }
+        let raw = serde_json::to_string(&outcome)?;
+        self.connection.run(move |connection| {
+            Box::pin(async move {
+            let mut tx = connection.begin_with("BEGIN IMMEDIATE").await?;
+            let result = sqlx::query("UPDATE host_effects SET outcome = ?, payload = ? WHERE id = ? AND outcome IS NULL")
+                .bind(raw).bind(payload).bind(id.to_string()).execute(&mut *tx).await?;
+            if result.rows_affected() != 1 {
+                return Err(StoreError::EventConflict);
+            }
+            tx.commit().await.map_err(StoreError::CommitUnknown)?;
+            Ok(())
+            })
+        }).await
+    }
+
+    pub async fn host_effect(
+        &self,
+        namespace: Namespace,
+        id: Uuid,
+    ) -> Result<Option<Record>, StoreError> {
+        self.validate_root()?;
+        self.connection.run(move |connection| {
+            Box::pin(async move {
+            let row = sqlx::query("SELECT request, outcome, payload FROM host_effects WHERE id = ? AND package_id = ? AND scope_id = ?")
+                .bind(id.to_string()).bind(namespace.package()).bind(String::from(namespace.scope().clone()))
+                .fetch_optional(connection).await?;
+            row.map(|row| {
+                Ok(Record {
+                    request: serde_json::from_str(row.try_get("request")?)?,
+                    outcome: row.try_get::<Option<&str>, _>("outcome")?.map(serde_json::from_str).transpose()?,
+                    payload: row.try_get("payload")?,
+                })
+            }).transpose()
+            })
+        }).await
+    }
+
+    /// Called only at exclusive Host recovery, before accepting new resource work.
+    pub async fn recover_host_effects(&self) -> Result<(), StoreError> {
+        self.validate_root()?;
+        let outcome = serde_json::to_string(&Outcome::Unknown {
+            message:
+                "Host stopped before recording the effect outcome; do not replay automatically"
+                    .into(),
+        })?;
+        self.connection
+            .run(move |connection| {
+                Box::pin(async move {
+                    let mut tx = connection.begin_with("BEGIN IMMEDIATE").await?;
+                    sqlx::query("UPDATE host_effects SET outcome = ? WHERE outcome IS NULL")
+                        .bind(outcome)
+                        .execute(&mut *tx)
+                        .await?;
+                    tx.commit().await.map_err(StoreError::CommitUnknown)?;
+                    Ok(())
+                })
... 33401 lines suppressed ...


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org