fix(headless): register ArchiveRead when Harbor archives tool results - #2028
Merged
Conversation
Harbor's AI SDK path always resolves an archive dir (the output dir falls back to /logs/agent, and the archive dir falls back to a subdirectory of it), so the tool-result archive writer is on by default. Both stale and active prune then replace large results with placeholders that explicitly tell the model to call ArchiveRead — a tool headless never bound, because it hand-writes its tool array and never goes through buildBuiltinTools. The result was removed from context and the recovery path named in its place did not exist. Bind ArchiveRead behind exactly the same gate as the writer: - export buildArchiveReadTool from @maka/runtime; it was unreachable outside the package, so headless could not construct the tool at all - add a ref-addressed ToolResultArchiveResourceReader for the Harbor archive dir, sharing one read-and-verify helper with the replay reader rather than duplicating it; replay keeps its stricter runtime-event identity check layered on top - thread the reader from runHarborCellFromEnv, which already owns the resolved env that wires the writer, so the two cannot diverge The test helper that projects the product surface now derives the reader from the same env as production, so the fixture cannot drift from it. Fixes #2025
Astro-Han
force-pushed
the
fix/headless-archive-read-registration
branch
from
August 3, 2026 15:08
0469897 to
83a50e5
Compare
Review cross-check found the tool surface and the archive writer disagreeing when MAKA_CONTEXT_BUDGET=off: the writer was gone but ArchiveRead was still bound, offering the model a tool no ref could ever satisfy. Fold the budget gate into harborCellToolResultArchiveDir so writer and both readers derive from one authority. The two tests added with the fix rebuilt production wiring inside a test helper, so deleting the real wiring left them green. Drive them through runHarborCellFromEnv instead and observe the surface it assembles, then cover the resource reader's failure reasons, which had none. Drop the unreachable maxBytes/too_large branch: both call paths address an archive by its exact recorded size, and Harbor reads the whole record.
Astro-Han
marked this pull request as ready for review
August 3, 2026 15:54
This was referenced Aug 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Harbor's headless AI SDK path archives large tool results but never bound
ArchiveRead, so pruned results were replaced by placeholders instructing the model to "Call ArchiveRead with the provided ref" — a tool that did not exist on the surface. The original result left context and the named recovery path was unreachable.This is the default Harbor path, not an opt-in: the output dir falls back to
/logs/agent, the archive dir falls back to a subdirectory of it, and both stale and active prune default to enabled.Bind
ArchiveReadbehind exactly the same gate as the writer:buildArchiveReadToolfrom@maka/runtime. It was absent fromindex.tsand from the package's subpath exports, so headless could not construct the tool at all — desktop only worked because it goes throughbuildBuiltinToolsinternally.ToolResultArchiveResourceReaderover the Harbor archive dir.ArchiveReadaddresses an archive bymaka://archive/...ref (artifact id, hash, size), which the existing replay reader cannot serve because it requires runtime-event identity. Both now share one read-and-verify helper instead of duplicating the read path, and replay keeps its stricter event-identity check layered on top rather than having it relaxed.runHarborCellFromEnv, which already owns the resolved env that wires the writer, so the two cannot diverge.The structural cause — the archive capability being three independent optional fields spread across two configuration surfaces, which makes "writer on, reader tool absent" representable — is tracked separately in #2026 and deliberately not addressed here, so this fix stays behavior-only and benchmark deltas remain attributable.
Closes #2025
Verification
npm run test:dist --workspace @maka/headless— 1316 passed, 0 failed, 1 pre-existing skip.npm run typecheck(all workspaces) — clean.npm run lint,npm run format:check— clean.npm run test:dist --workspace @maka/runtime— 2814 passed, 4 failed. The 4 failures (Read/Write/Edit/Glob and Grepworkspace-containment tests) are pre-existing macOS/var↔/private/varsymlink failures; verified identical on the unmodified branch point (2814 passed / 4 failed) before and after the change.Two tests were added at the existing backend-input seam:
Harbor ai-sdk backend binds ArchiveRead whenever it archives tool results— fails on the unfixed code with the writer present and the tool absent, which is the reported defect.Harbor ArchiveRead reads back a tool result the cell archived— an end-to-end round trip through the tool'simpl. Confirmed it actually catches reader regressions by temporarily stubbing the resource reader to returnnot_foundand watching it fail.The pre-existing archive test only asserted that the writer exists and never that
ArchiveReadwas callable, so the fixture shared the same one-directional blind spot as the implementation. The test helper that projects the product surface now derives the reader from the same env as production.