Skip to content

refactor(runtime): persist durable Tool Result projections - #4287

Merged
Astro-Han merged 30 commits into
apache:mainfrom
Astro-Han:refactor/4283-durable-projection
Aug 31, 2026
Merged

refactor(runtime): persist durable Tool Result projections#4287
Astro-Han merged 30 commits into
apache:mainfrom
Astro-Han:refactor/4283-durable-projection

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Tool Results previously had no single durable, provider-neutral model-visible representation. Live continuation, later replay, and restart recovery could therefore reconstruct different output, while a transient post-T2 model-output path competed with the RuntimeEvent ledger.

This PR adds the PR 1 foundation from #4283:

  • a closed, versioned, bounded, provider-neutral projection schema with one deterministic failure sentinel;
  • content fidelity for arbitrary tool-authored text and JSON, without introducing a secret-detection or DLP promise;
  • Core as the single schema/admission and Artifact source-policy authority, Storage as the exact artifact-ref persistence authority, and Runtime as the T1/publish/T2 lifecycle owner;
  • one compatibility codec and effective-history reducer input;
  • operation admission and complete projection admission before observable publication;
  • atomic T2 commit of the completed outcome and projection;
  • the same durable projection for live continuation, next-Turn replay, and cold restart;
  • protected content-addressed image artifacts and ref remapping through the existing Runtime Host exact-copy owner; and
  • removal of the replaced transient settlement output, raw settlement lane, synthetic writer surface, retired SessionManager conversation-copy owner, and superseded seam-only tests.

The final diff is 43 files (+2779/-1578): production and other source is +1225/-463, while tests are +1554/-1115. The added volume defines and verifies the durable protocol; the deletions remove competing authorities and tests replaced by stronger production-composition coverage.

Refs #4283

Lifecycle

flowchart TD
    A[Provider tool call] --> B{RuntimeStore T1 claim}
    B -- rejected --> R[Stop before call publication or execution]
    B -- accepted --> C[Runtime publishes the admitted call and executes once]
    C --> D{Storage plans exact artifact refs without writes}
    D -- planning fails --> F[Core-defined deterministic failure projection]
    D -- planned --> E{Core admits one closed and bounded projection}
    E -- rejected --> F
    E -- admitted --> G{Storage persists protected projection artifacts}
    G -- persistence fails --> F
    G -- persisted --> H[Canonical durable projection]
    F --> I{RuntimeStore T2 atomic commit}
    H --> I
    I -- fails --> J[Fail-stop; T1 prevents repeated effects]
    I -- committed --> K[Effective-history reducer]
    K --> L[Live continuation]
    K --> M[Next-Turn replay]
    K --> N[Cold restart]
Loading

Core is the only compatibility/schema authority; Runtime does not recalculate projection semantics during replay. Session copy also does not create another projection path: the existing Runtime Host exact-copy owner only remaps persisted session_file refs.

Projection bounding is a structural and resource contract, not arbitrary-content redaction. Tool-authored text and JSON remain faithful, consistent with Discussion #4119; credential stores and omission-based credential APIs remain separate security boundaries.

Verification

  • npm --workspace @maka/core run test:dist — 739 passed
  • npm --workspace @maka/storage run test:dist — 1015 passed, 7 skipped
  • npm --workspace @maka/runtime run test:dist — 3093 passed, 13 skipped
  • npm --workspace @maka/runtime-host run test:dist — 1444 passed, 12 skipped
  • focused projection, T1/T2 rollback, user-delete protection, live continuation, next-Turn replay, real RuntimeStore/ArtifactStore close-reopen, recovery, artifact, and Runtime Host copy suites passed
  • the full repository build and test build passed, including Desktop
  • Biome, ASF license-header audit, model-metadata check, and git diff --check passed

Review focus

This remains the durable projection foundation. Budgeting/compaction policy, projection transitions/pruning/copy-artifact protocols, and overflow omission remain outside this PR.

Schema/admission failures are zero-write: all parts and exact artifact refs are planned and decoded before any artifact is published. Projection-owned artifacts use a protected lifetime, so ordinary user deletion cannot invalidate committed history. Once multi-artifact publication begins, a later artifact failure or T2 failure can still leave a hidden, content-addressed orphan without committing a dangling projection ref. Repeat settlement is rejected at T1 before tool execution, projection work, or call-side publication can run again. Making publication atomic across ArtifactStore and RuntimeStore requires a batch staging/publish or garbage-collection lifecycle protocol, so that bounded P2 remains explicit follow-up scope rather than a local PR 1 patch.

The main invariants are that admission precedes effects, projection is computed before T2, outcome and projection commit atomically, and one durable authority feeds live continuation, next-Turn replay, and restart.

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Codex implemented the schema, codec, Runtime and Host integration, tests, validation, simplification, and review-driven corrections.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

@github-actions github-actions Bot added the effort/XL Over 1000 readable lines label Aug 30, 2026
@Astro-Han
Astro-Han force-pushed the refactor/4283-durable-projection branch from 9d41dd3 to 4d7007a Compare August 30, 2026 23:57
@Astro-Han
Astro-Han marked this pull request as ready for review August 31, 2026 04:03
@Astro-Han
Astro-Han force-pushed the refactor/4283-durable-projection branch from 484f4e9 to b2b2ae8 Compare August 31, 2026 07:20

@jackwener jackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the latest head b2b2ae86d. No P0 or P1 — approving. I found nothing worth filing at P2 or P3, so there are no inline comments. audit, ubuntu-latest, macos-latest, windows-latest and windows_recovery are green; package and test were still running.

This is 43 files and 2,779 additions, so rather than claim I read all of it evenly, here is what I went after and what I found.

The allocation bound is done properly. 3d423b8c3 checks byteLength before copying, and the test is the part worth praising: it subclasses ArrayBuffer to count slice() calls and asserts data.copies === 0, plus passes a planning callback that throws if reached. That proves rejection happens before allocation rather than merely that oversized input is rejected — two independent proofs of the property that actually matters.

The same discipline holds across the module. decodeBoundedImageData bounds the encoded base64 length before decoding, so an oversized string never allocates a decoded buffer, and it rejects non-canonical base64 — which also closes off two encodings of the same bytes. Structural limits on depth, node count and part count are in place, and unsafe media types fail closed in three separate paths.

The artifact protection is the right shape. The new tool_result_projection source carries userDeletable: false while tool_result is user-deletable. That matters because durable replay depends on these projections — a user-deletable projection would mean a tool result that cannot be reconstructed. sharedReadable: true keeps shared-session reads working. I also checked whether this new enum value crosses the Runtime Host protocol wire and would need an epoch bump: it does not appear in the protocol frames, so no bump is required.

The session-manager.ts removal is safe, and I checked the call chain rather than the symbol. Removing branchFromTurn, branchBeforeTurn and reviseBeforeTurn looks alarming at first, because the Desktop renderer, preload bridge and IPC main all still reference those names, and the implementation is gone from every package. It is fine: the live path is sessions:branchFromTurndeps.client.copySession("branch", ...) → Host → conversation-copy.ts, which never went through those SessionManager methods. They were a superseded in-process path with no remaining callers, and the 995 removed test lines tested exactly them.

And the copy path is correctly updated for the new format, which is the detail that would have been easy to miss: conversation-copy.ts now rewrites artifact references inside modelProjection when copying. Without that, a branched or revised session would carry projection refs pointing at the source session's artifacts. It has 22 tests.

Scope of this review. I examined the allocation bounds, the artifact source policy and its wire exposure, the session-manager removal and its full call chain, and the conversation-copy projection rewrite. I did not line-by-line review tool-runtime.ts (+266), ai-sdk-backend.ts (+143), or all 478 lines of the new projection module — I scanned the latter for bounds and failure modes rather than reading every branch. Saying so plainly since "approved" on a change this size should not be read as "every line verified".

Automated review notice: This comment was posted by an automated review agent operated by jackwener. It is not an independent human review and does not replace one.

@hqhq1025 hqhq1025 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the current head and found one P1 blocker in the durable tool-result settlement path. The change otherwise has substantial focused coverage, but a supported asynchronous model-output projector can now prevent T2 from ever being committed after the tool effect has already completed.


Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.

Comment thread packages/runtime/src/tool-runtime.ts

@M4n5ter M4n5ter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of exact head b2b2ae86dd2a00312e73dd1fb3090ff07365a4cc: changes are required. I independently reproduced the unresolved P1 on tool-runtime.ts:1610: an allowed asynchronous projector can leave a completed tool effect permanently at T1 with no T2 outcome, even after turn abort. Because that P1 blocks approval, I am not approving this head. I left the separate artifact-lifecycle P2 inline below. The closed schema, side-effect-free projection admission, atomic SQLite outcome/projection commit, live/next-turn/restart replay, and exact Session copy paths otherwise held in focused verification; I found no separate simplification issue.


Automated review notice: This comment was posted by an automated review agent operated by M4n5ter. It is not an independent human review and does not replace one.

Comment thread packages/runtime/src/durable-tool-result-projection.ts

@zhiiw zhiiw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at exact head b2b2ae86 (verified unchanged at review time; all checks completed/success on this head, including test 25m50s and package 23m41s).

Durable Tool Result projection foundation, verified rather than assumed:

  • The schema is closed, versioned, and bounded at the Core layer: 256 KiB / 64 parts / depth 32 / 20k nodes, exact-shape validation per kind, one frozen failure sentinel. sanitizeJsonValue rejects non-finite numbers, non-plain objects, and toJSON serializers, and writes keys via defineProperty, which makes __proto__ payload keys inert by construction. Encoding re-decodes its own output before returning, so the writer can never produce something the reader would reject — invalid output becomes the sentinel instead.
  • Artifact refs are session-scoped and image-only: parts must reference the owning session (ref.sessionId checked at both the encode and the decodeRuntimeEvent boundary, the latter via hasOwnedModelProjection — no cross-session ref can ride a persisted event), carry a canonicalized raster MIME, and inline image bytes must round-trip as canonical base64 under the existing MAX_READ_IMAGE_BYTES. The planner validates and freezes without writing; persist() runs only after admission, so a rejected projection never leaves an orphan write on the artifact path.
  • Admission precedes publication: the call message and tool_start event now land only after prepareDurableToolAttempt (T1) succeeds — a rejected claim publishes nothing. T2 adoption deep-compares the committed event including modelProjection, so a replayed outcome cannot silently carry a different projection than the one admitted.
  • Live, replay, and restart share one representation: the transient settledModelOutputs lane and the post-T2 model-output recomputation are gone; replay materializes from the durable projection, rehydrating image refs for vision-capable models with per-artifact budget charging (deterministic decision keys, so re-materialization does not double-charge) and degrading to explicit text notes otherwise. Legacy events flow through the one compatibility codec (decodeEffectiveToolResultProjection), including the retired-ExploreAgent and shell-result shapes; unprojectable legacy path images pass through as legacy output rather than failing.
  • Session copy does not open a second projection path: the copy collects session_file refs from exactly the sites rewriteStorageRef reaches (including the new projection parts), remaps through the existing exact-copy owner, and rewriteProjectionArtifactRef throws on an invalid rewrite instead of emitting a corrupt projection.
  • Source policy is now one Core table: ARTIFACT_SOURCE_POLICIES (userDeletable / userVisible / sharedReadable) replaces the desktop's local visibility switch and the coordinator's local shared-source set; tool_result_projection is not user-deletable and not user-visible, matching its role as ledger content.

Executed on a real Windows machine at this head: clean forced rebuild, then Core/Runtime projection suites 45/45, the four ToolRuntime boundary suites 76/77 (the one failure is the pre-existing symlink-EPERM environment case — this machine has no Developer Mode; the failing assertion creates a symlink fixture), storage artifact/runtime-store + ai-sdk-backend 274/274, and the runtime-host recovery pair 1 pass + 1 platform skip (UDS). One honest limitation: the rewritten execution-model-composition suite does not finish on this machine (pre-existing environmental slowness in the process-heavy composition fixtures); the hosted test job runs it green on this head.


Automated review notice: This comment was posted by an automated review agent operated by zhiiw. It is not an independent human review and does not replace one.

简体中文

durable Tool Result 投影地基,全部核实而非假设:Core 层 schema 封闭有界(256KiB/64 段/深度 32/2 万节点),sanitizeJson 拒绝非有限数/非纯对象/toJSON,defineProperty 逐键写使 proto 键天然失效;编码后自解码自检,非法输出收编到唯一 sentinel。artifact ref 限本 session(编码与 decodeRuntimeEvent 双边界都查 sessionId)、白名单 raster MIME、canonical base64 往返校验;plan 不落盘、persist 在准入之后。T1 先准入后发布(call 消息/事件只在 claim 成功后落地);T2 adopt 深比对含 modelProjection。实时/重放/重启共享同一表示:settledModelOutputs 瞬态车道删除,重放按 vision 能力+预算把 artifact ref 再水化成图片(确定性 decision key 不重复计费),失败降级为明确文本注记;legacy 事件走唯一兼容 codec。session copy 只 remap ref 不另开投影路径,非法 rewrite 直接抛。artifact 来源策略收成 Core 一张三轴表。本机真 Windows:投影套件 45/45、tool-runtime 边界 76/77(1 个既有 symlink EPERM 环境噪音)、storage+backend 274/274;重写的 composition 套件本机跑不完(既有环境性),hosted test 同 head 跑绿——如实声明。

@Astro-Han
Astro-Han force-pushed the refactor/4283-durable-projection branch from b2b2ae8 to 6190657 Compare August 31, 2026 08:50

@hqhq1025 hqhq1025 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed exact head 619065706eaffb0d5ef2481058c88d12ac733fda. The previous P1 is fixed: MakaTool.toModelOutput is now a synchronous contract, and an untyped/plugin thenable fails closed immediately to the deterministic projection_failed sentinel, so a completed tool effect can no longer be stranded before T2. The new production-boundary regression verifies that a never-settling projector still commits the fallback outcome without waiting.

I found no new P0 or P1. The previously reported P2 around partially published, unreferenced projection artifacts remains an explicit known limitation scoped to the follow-up lifecycle work; it is not fixed by this head.

Validation included a clean install, build:test, full typecheck, Core 739/739, Runtime 3,081 pass with 13 skips, focused durable-boundary Runtime tests 305/305, renderer architecture 60/60 plus the production graph check, changed-file Biome, git diff --check, a clean merge tree against current main, and all current hosted checks green. Runtime Host completed 1,443 pass / 1 fail / 12 skip; the sole Bash sandbox-boundary failure reproduces on the prior head and the host denies both unshare and bwrap. Storage completed 1,039 pass / 1 warning-sensitive child-process failure / 10 skip; that crash-recovery file passed 5/5 under the intended Node 22 runtime with warnings suppressed. I could not independently exercise the unavailable Linux command sandbox.

Review notice: This review was prepared by an automated review agent operated by hqhq1025 and is published at the direction of AstroHan, who has read these findings and is the human accountable for them.

@M4n5ter M4n5ter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved at exact head 619065706eaffb0d5ef2481058c88d12ac733fda. The asynchronous-projector blocker is closed: toModelOutput is now a synchronous contract, and an untyped thenable immediately becomes the deterministic projection_failed result instead of delaying T2. The new never-settling-projector regression and 69 other focused Core, Runtime, and Storage tests pass, as do the four dependency-ordered builds.

The existing P2 artifact-lifecycle issue remains real and is not claimed fixed here: a current-head probe still persists artifact 1 when artifact 2 fails, then returns the failure projection. That follow-up is already recorded for the staged publication/reachability work in #4283 and does not block approval under the requested P0/P1 gate.

All exact-head hosted checks are green, both review threads are resolved, and the PR is mergeable and clean against current main.


Automated review notice: This comment was posted by an automated review agent operated by M4n5ter. It is not an independent human review and does not replace one.

@Astro-Han
Astro-Han merged commit bd951aa into apache:main Aug 31, 2026
7 checks passed
@Astro-Han
Astro-Han deleted the refactor/4283-durable-projection branch August 31, 2026 09:56
Astro-Han added a commit that referenced this pull request Aug 31, 2026
* refactor(runtime): budget and compact effective model history

Context budgeting, summarization, provider-native compaction, and history
compact checkpoint source digests each re-derived their own view of a Tool
Result. All four read the raw `function_response.result` — the execution
fact — rather than the durable model projection that #4287 commits atomically
at T2 and that live continuation, next-Turn replay, and restart already
replay. A bounded or redacted projection was therefore sized, summarized, and
hashed at the size and content of the raw evidence it replaced.

Move the sizing authority to the reducer: `estimateRuntimeEventChars`,
`estimateRuntimeEventsTokens`, and `groupEventsByTurn` leave the
dependency-free scalar helpers and live in `model-history.ts`, where a
`function_response` is measured through `effectiveToolResultModelValue` —
one decode of the effective projection shared by sizing and digests. The
summary size floor rides the same estimator, so it is fixed by construction.

Route the two compaction materializers through the same effective output as
primary replay: `effectiveReplayToolResultOutput` prefers the durable
projection and falls back to raw output only for a response that has none
(legacy or provider-native). This removes the divergent `toolResultOutput`
call in the LLM summarizer and the Codex compactor.

Derive the checkpoint source digest from effective model history. Rewriting
raw evidence under an unchanged projection no longer invalidates a valid
checkpoint, while replacing the projection does — a checkpoint can never
replay over content it did not cover. That is a source-policy change, so
`HISTORY_COMPACT_SOURCE_POLICY_VERSION` becomes v2 and a v1 checkpoint fails
the existing shape check and re-summarizes instead of claiming coverage this
policy never computed. `HistoryCompactCheckpoint` stays a contiguous-prefix
replacement; no sparse override is introduced.

Covered by `effective-history-compaction.test.ts` (budget sizes the
projection, summarization cannot read replaced raw output, the digest ignores
raw evidence but tracks the projection, a v1-policy checkpoint no longer
validates) and the runtime workspace suite.

Part of #4283 (PR 2).

Generated-by: Claude Code

* refactor(runtime): anchor the checkpoint digest on persisted fields

Review found that hashing the MATERIALIZED effective value bound a durable
digest to in-memory representation: the discriminant labels of an internal
union, the artifact placeholder wording in
`durableProjectionToToolResultOutput`, and — for legacy responses re-derived
through the compatibility codec — the Bash/File projectors and the encoder's
size bounds. Any later refactor of those would silently invalidate every
persisted checkpoint, with no reason for anyone to bump the source policy.

Select the persisted field that carries the response's model-visible content
instead: `providerOutput` for a provider-native result, `modelProjection` when
the response has one, and otherwise the raw `result` its effective content is
still derived from — which is exactly what the pre-projection digest hashed.
This is a structural choice over durable data only, so the digest is stable
under code evolution while keeping the property it was changed for: rewriting
raw evidence under an unchanged projection does not invalidate a checkpoint,
and replacing the projection does.

Sizing follows the same simplification, since the digest was its only other
consumer: `EffectiveToolResultModelValue` and `effectiveToolResultModelValue`
are gone and `estimateEffectiveToolResultChars` switches on the reducer's own
`decodeEffectiveToolResultProjection` result. That leaves
`estimateToolResultOutputChars` with a single caller, so it moves in beside it
and stops being exported — which also drops the import edge this PR had added
from the projection codec back into the budgeting helpers.

Measured while re-deriving a legacy response through the whole compatibility
codec on every call (200 responses x 200 KB: 94 ms, against 4 ms for the raw
length it replaced), and one request measures the same events several times —
budget verdicts, the compactable-content filter, and checkpoint prefix
matching each walk the history. Memoize the per-response count on the content
object, which is immutable once committed.

Refs #4283.

Generated-by: Claude Code

* fix(runtime): treat a superseded compaction checkpoint as absent, not fatal

Bumping the source policy to v2 was described as a safe degradation because
compaction fails open and re-summarizes. Review showed that is true of
`history-compaction`, `ai-sdk-compaction`, and the backend, but NOT of
conversation copy: `cloneAgentRunEvent` threw both when the shape check
rejected a checkpoint and when its prefix no longer matched. A run ledger
keeps every checkpoint it ever recorded, so a session that had compacted
under the old policy would have failed to branch, edit-and-resend, or start a
side conversation — permanently, since a newer checkpoint does not remove the
older record. Nothing in the suite covered it, because every copy fixture
built its checkpoint under the current policy.

A copy carries the canonical raw RuntimeEvents and can compact again, so drop
such a record instead, joining the `return null` that opaque provider state
already used for the same reason.

The same rejection made `execution-inspect` report every pre-upgrade session
as `compaction_checkpoint_invalid` at severity `error`, which would bury the
records that really are damaged. Distinguish a well-formed checkpoint minted
under an older policy with `isSupersededHistoryCompactCheckpoint`, and report
it as `compaction_checkpoint_superseded` at `info` with a matching
`superseded` validation state.

Covered by a copy regression test that records a v1-policy checkpoint and
asserts the copy completes carrying none; it fails against the previous
throwing branch.

Refs #4283.

Generated-by: Claude Code
Astro-Han added a commit that referenced this pull request Aug 31, 2026
…4350)

Archiving a Tool Result removed content from the model's view without recording
that it had: the active prune kept its replacement in a per-Turn map a restart
discarded, and the stale prune carried archive refs on the compaction policy and
re-derived them on every replay. Neither was durable, so there was no single
answer to what the model sees for a given Tool Result, and a replay path could
resurrect content a prune had already removed.

This adds one append-only model-projection-transition record on the operational
AgentRunEvent ledger and migrates both prune paths onto it. A Session-scoped
reducer folds the ledger onto the canonical RuntimeEvent stream, and replay,
budgeting, compaction, the current Turn and conversation copy all read that fold.
Source-digest validation plus the previousTransitionId chain make the result
independent of ledger order, arrival order and clocks; a content-derived id makes
a duplicated concurrent append idempotent. A record this build cannot decode
withholds its target's projection instead of replaying the raw body, and a record
that names no target fails the read rather than guessing.

The migrated structures are deleted rather than deferred: the per-Turn placeholder
map, the archive-ref policy table and its replay path, the second placeholder kind,
and the stale prune's own event rewriter. Projection artifact publication is now
all-or-nothing and carries an ownership receipt, so a plan retracts only what it
itself published — the partial-publication leak found in #4287's review.

Compatibility: the RuntimeEvent ledger keeps its existing shape and is never
rewritten; sessions with no transition records fold to their current history
unchanged. HistoryCompactCheckpoint keeps its contiguous-prefix meaning.

Refs #4283
Refs #4287

Generated-by: Claude Code
abhinav-phi pushed a commit to abhinav-phi/maka that referenced this pull request Sep 1, 2026
* feat(core): define durable tool result projections

Generated-by: Codex

* feat(runtime): add durable projection codec

Generated-by: Codex

* feat(runtime): commit projections atomically at T2

Generated-by: Codex

* refactor(runtime): replay effective tool result projections

Generated-by: Codex

* feat(runtime-host): persist projected artifacts and recoveries

Generated-by: Codex

* refactor(runtime): remove transient Tool Result authority

Generated-by: Codex

* fix(storage): reuse projected image artifacts

Generated-by: Codex

* fix(runtime): remap projected artifacts on copy

Generated-by: Codex

* refactor(runtime): remove synthetic projection return

Generated-by: Codex

* refactor(runtime): privatize synthetic result writer

Generated-by: Codex

* fix(runtime): validate projected images before persistence

Generated-by: Codex

* fix(runtime): reject unowned projection copies

Generated-by: Codex

* refactor(runtime): remove retired conversation copy owner

Generated-by: Codex

* test(runtime): verify durable projection owner boundaries

Generated-by: Codex

* refactor(runtime): keep projection helper types private

Generated-by: Codex

* test(runtime): target durable projection T2 rollback

Generated-by: Codex

* refactor(runtime): trim projection artifact input

Generated-by: Codex

* test(runtime): produce restart projection through T2

Generated-by: Codex

* style(runtime): format session manager tests

Generated-by: Codex

* fix(runtime): make projection admission side-effect free

Generated-by: Codex

* test(runtime): replay persisted image after restart

Generated-by: Codex

* test(runtime-host): cover projection artifact restart

Generated-by: Codex

* refactor(runtime): preserve projection content fidelity

Generated-by: Codex

* fix(storage): protect projection artifacts

Generated-by: Codex

* fix(runtime): admit tool calls before publication

Generated-by: Codex

* fix(runtime): bound projection copies before allocation

Generated-by: Codex

* test(runtime-host): consolidate projection lifecycle coverage

Generated-by: Codex

* test(runtime): lock T1 failure side effects

Generated-by: Codex

* refactor(core): unify artifact source policy

Generated-by: Codex

* fix(runtime): make tool result projectors total

Generated-by: Codex
abhinav-phi pushed a commit to abhinav-phi/maka that referenced this pull request Sep 1, 2026
…#4348)

* refactor(runtime): budget and compact effective model history

Context budgeting, summarization, provider-native compaction, and history
compact checkpoint source digests each re-derived their own view of a Tool
Result. All four read the raw `function_response.result` — the execution
fact — rather than the durable model projection that apache#4287 commits atomically
at T2 and that live continuation, next-Turn replay, and restart already
replay. A bounded or redacted projection was therefore sized, summarized, and
hashed at the size and content of the raw evidence it replaced.

Move the sizing authority to the reducer: `estimateRuntimeEventChars`,
`estimateRuntimeEventsTokens`, and `groupEventsByTurn` leave the
dependency-free scalar helpers and live in `model-history.ts`, where a
`function_response` is measured through `effectiveToolResultModelValue` —
one decode of the effective projection shared by sizing and digests. The
summary size floor rides the same estimator, so it is fixed by construction.

Route the two compaction materializers through the same effective output as
primary replay: `effectiveReplayToolResultOutput` prefers the durable
projection and falls back to raw output only for a response that has none
(legacy or provider-native). This removes the divergent `toolResultOutput`
call in the LLM summarizer and the Codex compactor.

Derive the checkpoint source digest from effective model history. Rewriting
raw evidence under an unchanged projection no longer invalidates a valid
checkpoint, while replacing the projection does — a checkpoint can never
replay over content it did not cover. That is a source-policy change, so
`HISTORY_COMPACT_SOURCE_POLICY_VERSION` becomes v2 and a v1 checkpoint fails
the existing shape check and re-summarizes instead of claiming coverage this
policy never computed. `HistoryCompactCheckpoint` stays a contiguous-prefix
replacement; no sparse override is introduced.

Covered by `effective-history-compaction.test.ts` (budget sizes the
projection, summarization cannot read replaced raw output, the digest ignores
raw evidence but tracks the projection, a v1-policy checkpoint no longer
validates) and the runtime workspace suite.

Part of apache#4283 (PR 2).

Generated-by: Claude Code

* refactor(runtime): anchor the checkpoint digest on persisted fields

Review found that hashing the MATERIALIZED effective value bound a durable
digest to in-memory representation: the discriminant labels of an internal
union, the artifact placeholder wording in
`durableProjectionToToolResultOutput`, and — for legacy responses re-derived
through the compatibility codec — the Bash/File projectors and the encoder's
size bounds. Any later refactor of those would silently invalidate every
persisted checkpoint, with no reason for anyone to bump the source policy.

Select the persisted field that carries the response's model-visible content
instead: `providerOutput` for a provider-native result, `modelProjection` when
the response has one, and otherwise the raw `result` its effective content is
still derived from — which is exactly what the pre-projection digest hashed.
This is a structural choice over durable data only, so the digest is stable
under code evolution while keeping the property it was changed for: rewriting
raw evidence under an unchanged projection does not invalidate a checkpoint,
and replacing the projection does.

Sizing follows the same simplification, since the digest was its only other
consumer: `EffectiveToolResultModelValue` and `effectiveToolResultModelValue`
are gone and `estimateEffectiveToolResultChars` switches on the reducer's own
`decodeEffectiveToolResultProjection` result. That leaves
`estimateToolResultOutputChars` with a single caller, so it moves in beside it
and stops being exported — which also drops the import edge this PR had added
from the projection codec back into the budgeting helpers.

Measured while re-deriving a legacy response through the whole compatibility
codec on every call (200 responses x 200 KB: 94 ms, against 4 ms for the raw
length it replaced), and one request measures the same events several times —
budget verdicts, the compactable-content filter, and checkpoint prefix
matching each walk the history. Memoize the per-response count on the content
object, which is immutable once committed.

Refs apache#4283.

Generated-by: Claude Code

* fix(runtime): treat a superseded compaction checkpoint as absent, not fatal

Bumping the source policy to v2 was described as a safe degradation because
compaction fails open and re-summarizes. Review showed that is true of
`history-compaction`, `ai-sdk-compaction`, and the backend, but NOT of
conversation copy: `cloneAgentRunEvent` threw both when the shape check
rejected a checkpoint and when its prefix no longer matched. A run ledger
keeps every checkpoint it ever recorded, so a session that had compacted
under the old policy would have failed to branch, edit-and-resend, or start a
side conversation — permanently, since a newer checkpoint does not remove the
older record. Nothing in the suite covered it, because every copy fixture
built its checkpoint under the current policy.

A copy carries the canonical raw RuntimeEvents and can compact again, so drop
such a record instead, joining the `return null` that opaque provider state
already used for the same reason.

The same rejection made `execution-inspect` report every pre-upgrade session
as `compaction_checkpoint_invalid` at severity `error`, which would bury the
records that really are damaged. Distinguish a well-formed checkpoint minted
under an older policy with `isSupersededHistoryCompactCheckpoint`, and report
it as `compaction_checkpoint_superseded` at `info` with a matching
`superseded` validation state.

Covered by a copy regression test that records a v1-policy checkpoint and
asserts the copy completes carrying none; it fails against the previous
throwing branch.

Refs apache#4283.

Generated-by: Claude Code
abhinav-phi pushed a commit to abhinav-phi/maka that referenced this pull request Sep 1, 2026
…pache#4350)

Archiving a Tool Result removed content from the model's view without recording
that it had: the active prune kept its replacement in a per-Turn map a restart
discarded, and the stale prune carried archive refs on the compaction policy and
re-derived them on every replay. Neither was durable, so there was no single
answer to what the model sees for a given Tool Result, and a replay path could
resurrect content a prune had already removed.

This adds one append-only model-projection-transition record on the operational
AgentRunEvent ledger and migrates both prune paths onto it. A Session-scoped
reducer folds the ledger onto the canonical RuntimeEvent stream, and replay,
budgeting, compaction, the current Turn and conversation copy all read that fold.
Source-digest validation plus the previousTransitionId chain make the result
independent of ledger order, arrival order and clocks; a content-derived id makes
a duplicated concurrent append idempotent. A record this build cannot decode
withholds its target's projection instead of replaying the raw body, and a record
that names no target fails the read rather than guessing.

The migrated structures are deleted rather than deferred: the per-Turn placeholder
map, the archive-ref policy table and its replay path, the second placeholder kind,
and the stale prune's own event rewriter. Projection artifact publication is now
all-or-nothing and carries an ownership receipt, so a plan retracts only what it
itself published — the partial-publication leak found in apache#4287's review.

Compatibility: the RuntimeEvent ledger keeps its existing shape and is never
rewritten; sessions with no transition records fold to their current history
unchanged. HistoryCompactCheckpoint keeps its contiguous-prefix meaning.

Refs apache#4283
Refs apache#4287

Generated-by: Claude Code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/XL Over 1000 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants