Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
299 lines
10 KiB
Markdown
299 lines
10 KiB
Markdown
<!--
|
|
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.
|
|
-->
|
|
|
|
# Runtime Kernel Extraction
|
|
|
|
> Archived on 2026-07-13. This is a point-in-time extraction record, not current architecture authority. Start with `ARCHITECTURE.md`.
|
|
|
|
This document explains the runtime-kernel work in this change set: what changed,
|
|
why it was needed, what stayed stable, and how it was verified.
|
|
|
|
## Summary
|
|
|
|
Maka already had the pieces of a local desktop coding agent: sessions, model
|
|
streams, tool calls, permission prompts, abort handling, usage telemetry, bot and
|
|
gateway entry points, and persisted session messages. The problem was that the
|
|
core execution responsibilities were still concentrated in a few large runtime
|
|
paths, especially `AiSdkBackend` and `SessionManager.sendMessage()`.
|
|
|
|
This change set keeps the product surfaces stable but introduces clearer
|
|
internal runtime boundaries:
|
|
|
|
```text
|
|
SessionManager
|
|
-> AgentRun
|
|
-> AiSdkBackend
|
|
-> ModelAdapter
|
|
-> ToolRuntime
|
|
-> RunTrace
|
|
-> AgentRunStore
|
|
```
|
|
|
|
The intent is not to rewrite the runtime or replace the Vercel AI SDK. The goal
|
|
is to make the existing runtime easier to reason about, easier to recover after
|
|
interruption, and easier to extend with future backends or workflow integrations.
|
|
|
|
## What Changed
|
|
|
|
### ToolRuntime
|
|
|
|
`ToolRuntime` is now the internal boundary for the lifecycle of model-requested
|
|
tools. The extracted runtime owns the work that used to be interleaved inside the
|
|
AI SDK backend:
|
|
|
|
- validate tool input before execution
|
|
- evaluate permission policy
|
|
- wait for parked permission decisions with timeout behavior
|
|
- pause and resume the stream watchdog around tool execution
|
|
- propagate abort signals into tools
|
|
- classify tool failures
|
|
- record tool telemetry and artifacts
|
|
- emit tool and permission trace events
|
|
|
|
`AiSdkBackend` still bridges model stream behavior, but it no longer needs to own
|
|
every detail of tool execution.
|
|
|
|
### ModelAdapter
|
|
|
|
`ModelAdapter` is the provider-facing stream and error normalization layer. It
|
|
keeps AI SDK-specific stream chunks, provider setup, usage normalization, and
|
|
provider error mapping out of the higher-level backend orchestration shell.
|
|
|
|
This makes the boundary explicit:
|
|
|
|
```text
|
|
provider / AI SDK details
|
|
-> ModelAdapter
|
|
-> Maka runtime events and usage records
|
|
```
|
|
|
|
That separation matters because future providers should not need to duplicate
|
|
permission, tool, run, or session-state behavior.
|
|
|
|
### RunTrace
|
|
|
|
`RunTrace` is an internal best-effort trace path for runtime events. It records
|
|
milestones such as:
|
|
|
|
- turn started
|
|
- model resolved / stream started / stream completed / stream failed
|
|
- tool started / completed / failed
|
|
- permission requested / decided / failed
|
|
- usage recorded
|
|
- abort requested
|
|
|
|
Trace recorder failures are intentionally non-fatal. A failed trace write must
|
|
not alter model or tool execution.
|
|
|
|
### AgentRun Types and Store
|
|
|
|
The core package now defines internal AgentRun contracts:
|
|
|
|
- `AgentRunHeader`
|
|
- `AgentRunEvent`
|
|
- `AgentRunStatus`
|
|
- `AgentRunStore`
|
|
|
|
The storage package adds a file-backed run store:
|
|
|
|
```text
|
|
sessions/<sessionId>/runs/<runId>/run.json
|
|
sessions/<sessionId>/runs/<runId>/events.jsonl
|
|
```
|
|
|
|
The store provides:
|
|
|
|
- atomic `run.json` writes
|
|
- append-only run event JSONL
|
|
- same-run write serialization
|
|
- session run listing
|
|
- corrupt committed event-line recovery via `event_corrupt`
|
|
- malformed unterminated tail tolerance
|
|
|
|
This ledger is separate from the existing session message JSONL so runtime
|
|
diagnostics and recovery state do not pollute user-visible conversation history.
|
|
|
|
### AgentRun Execution
|
|
|
|
`SessionManager.sendMessage()` now delegates the heavy turn lifecycle to
|
|
internal `AgentRun.execute()`.
|
|
|
|
`AgentRun` owns:
|
|
|
|
- generating and recording run identity
|
|
- appending the user message
|
|
- writing initial turn state
|
|
- locking the connection snapshot for the run
|
|
- ensuring the active backend exists
|
|
- driving backend stream events
|
|
- projecting session status changes
|
|
- writing turn completion, failure, abort, or permission-wait states
|
|
- recording run started/completed/failed/cancelled status
|
|
- routing `RunTrace` events into the durable run ledger
|
|
- preserving abort source diagnostics such as `renderer.stop_button`
|
|
|
|
`SessionManager` remains the public runtime API and continues to own session
|
|
CRUD, backend registry orchestration, active run lookup, and legacy recovery
|
|
entry points.
|
|
|
|
### Active Run Registry
|
|
|
|
The old active stream counters and turn-id side maps were replaced with:
|
|
|
|
```ts
|
|
activeRuns: Map<string, AgentRun>
|
|
turnToRunId: Map<string, string>
|
|
```
|
|
|
|
This makes overlapping run behavior explicit. It also avoids masking active
|
|
stream accounting bugs with defensive counter clamping.
|
|
|
|
### Startup Recovery
|
|
|
|
`recoverInterruptedSessions()` now prefers the AgentRun ledger when run rows are
|
|
available. It scans persisted run headers and events, classifies stale
|
|
non-terminal runs, repairs them, and then converges the existing session/turn
|
|
projection.
|
|
|
|
Recovered cases include:
|
|
|
|
- stale `created` or `running` runs
|
|
- runs whose last event is `run_started` or `model_stream_started`
|
|
- stale tool tails after `tool_started`
|
|
- stale permission waits after `permission_requested`
|
|
- `model_stream_completed` without a terminal run event
|
|
- corrupt run event lines represented as `event_corrupt`
|
|
|
|
Legacy sessions without run ledger rows still use the prior message and
|
|
turn-state recovery path.
|
|
|
|
## What Stayed Stable
|
|
|
|
This work intentionally does not change:
|
|
|
|
- `window.maka.*` preload API
|
|
- Electron IPC channel names
|
|
- renderer-visible `SessionEvent` behavior
|
|
- user-visible permission modes
|
|
- session message JSONL compatibility
|
|
- builtin tool names or public tool behavior
|
|
- model provider settings UI
|
|
- bot and OpenGateway public entry points
|
|
- Rive workflow integration semantics
|
|
|
|
The runtime kernel is internal. Public behavior should remain compatible while
|
|
the internals become more explicit and recoverable.
|
|
|
|
## Why This Was Needed
|
|
|
|
Before this work, a single turn execution was spread across session management,
|
|
backend stream handling, tool wrapping, permission policy, telemetry, and abort
|
|
logic. That made several questions difficult to answer after a failure:
|
|
|
|
- Did the model stream start?
|
|
- Which backend/model/connection did the turn use?
|
|
- Was the run waiting for permission?
|
|
- Which tool was running when the app exited?
|
|
- Did the user press stop?
|
|
- Was usage recorded?
|
|
- Did a stale session status reflect a real active run or a crashed process?
|
|
|
|
The AgentRun ledger gives the runtime a durable fact record for those answers.
|
|
ToolRuntime and ModelAdapter then reduce the amount of model/tool/provider logic
|
|
that has to be understood at once.
|
|
|
|
## Recovery Model
|
|
|
|
The new recovery path is conservative. It does not replay model streams or tools.
|
|
Instead, it repairs stale state into deterministic terminal states so the app
|
|
does not reopen with sessions permanently stuck in `running` or
|
|
`waiting_for_user`.
|
|
|
|
When a stale non-terminal run is recovered, the runtime:
|
|
|
|
1. Updates the AgentRun header to terminal state.
|
|
2. Appends a durable recovery event such as `run_failed` or `run_completed`.
|
|
3. Writes the existing session `turn_state` projection.
|
|
4. Updates the session header out of active running/waiting states.
|
|
|
|
The failure class for app-restart recovery is recorded as `app_restarted`.
|
|
Diagnostics are limited to small reason-code fields such as `recovered`,
|
|
`failureClass`, `recoveryReason`, `lastEventType`, and `eventCorrupt`; raw user
|
|
text and raw event payloads are not copied into recovery diagnostics.
|
|
|
|
## Files Added
|
|
|
|
- `packages/runtime/src/tool-runtime.ts`
|
|
- `packages/runtime/src/model-adapter.ts`
|
|
- `packages/runtime/src/run-trace.ts`
|
|
- `packages/core/src/agent-run.ts`
|
|
- `packages/storage/src/agent-run-store.ts`
|
|
- `packages/runtime/src/agent-run.ts`
|
|
- `packages/runtime/src/agent-run-recovery.ts`
|
|
|
|
## Tests Added or Expanded
|
|
|
|
The runtime test suite now covers:
|
|
|
|
- ToolRuntime permission allow/block/prompt/timeout paths
|
|
- watchdog pause/resume behavior around tools
|
|
- tool abort and failure classification behavior
|
|
- tool telemetry and artifact recording behavior
|
|
- ModelAdapter stream, usage, and provider error normalization
|
|
- RunTrace recording and best-effort failure behavior
|
|
- overlapping AgentRuns
|
|
- backend build failure after user-message append
|
|
- permission handoff without stuck run state
|
|
- stop button abort source preservation
|
|
- late complete after stop not overwriting aborted state
|
|
- durable trace redaction
|
|
- AgentRunStore create/read/update/list behavior
|
|
- same-run AgentRun event append serialization
|
|
- corrupt AgentRun event JSONL recovery
|
|
- startup recovery from stale AgentRun ledger states
|
|
- legacy startup recovery fallback
|
|
- terminal AgentRun recovery idempotency
|
|
|
|
## Verification
|
|
|
|
The changes were verified with:
|
|
|
|
```sh
|
|
npm --workspace @maka/core run typecheck
|
|
npm --workspace @maka/storage run test
|
|
npm --workspace @maka/runtime run typecheck
|
|
npm --workspace @maka/runtime run test
|
|
npm --workspace @maka/desktop run build:main
|
|
git diff --check
|
|
```
|
|
|
|
The final runtime suite included 315 passing tests after the AgentRun recovery
|
|
work.
|
|
|
|
## Follow-Up Work
|
|
|
|
This PR establishes the internal runtime-kernel shape, but it does not finish
|
|
every possible cleanup. Good follow-up slices are:
|
|
|
|
- merge usage and cost accounting more tightly with the run ledger
|
|
- expose an internal run-inspection/debug read model
|
|
- reduce the remaining `SessionManager` hook surface used by `AgentRun`
|
|
- consider Gateway/Bot policy unification through the same run policy layer
|
|
- consider Rive workflow/run mapping only after there is a product need
|