Skip to content

fix(headless): register ArchiveRead when Harbor archives tool results - #2028

Merged
Astro-Han merged 2 commits into
mainfrom
fix/headless-archive-read-registration
Aug 3, 2026
Merged

fix(headless): register ArchiveRead when Harbor archives tool results#2028
Astro-Han merged 2 commits into
mainfrom
fix/headless-archive-read-registration

Conversation

@Astro-Han

Copy link
Copy Markdown
Contributor

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 ArchiveRead behind exactly the same gate as the writer:

  • Export buildArchiveReadTool from @maka/runtime. It was absent from index.ts and from the package's subpath exports, so headless could not construct the tool at all — desktop only worked because it goes through buildBuiltinTools internally.
  • Add a ref-addressed ToolResultArchiveResourceReader over the Harbor archive dir. ArchiveRead addresses an archive by maka://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.
  • Thread the reader from 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 Grep workspace-containment tests) are pre-existing macOS /var/private/var symlink 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's impl. Confirmed it actually catches reader regressions by temporarily stubbing the resource reader to return not_found and watching it fail.

The pre-existing archive test only asserted that the writer exists and never that ArchiveRead was 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.

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
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(headless): register ArchiveRead when Harbor enables tool-result archiving

1 participant