Skip to content

feat(harness): add the progressive-disclosure handoff cache - #103

Merged
senamakel merged 2 commits into
mainfrom
harness-handoff
Aug 13, 2026
Merged

feat(harness): add the progressive-disclosure handoff cache#103
senamakel merged 2 commits into
mainfrom
harness-handoff

Conversation

@senamakel

Copy link
Copy Markdown
Member

Ported from OpenHuman's agent/harness/subagent_runner/handoff.rs. This is the movable part of Phase 5's subagent_runner family — see the scope note at the bottom for why the rest is not.

Independent of #102, with one overlap: both add regex = "1" to Cargo.toml. Whichever merges second drops that hunk; either order works.

What it does

A sub-agent calls a tool that returns a megabyte-scale payload (GMAIL_LIST_MESSAGES, NOTION_GET_PAGE, a bulk Drive listing). That blob goes into history as a tool-result message, and the next iteration ships the bloated history back to the provider, where it hits the context ceiling. One oversized result poisons every turn after it.

Progressive disclosure stashes the full payload, substitutes a short placeholder carrying size + preview + a result_id + how to query it, and lets the agent decide whether the preview already answers its task. The extraction step runs only when it asks for a narrower view.

Sibling strategy to harness::artifacts (#101): same problem, two different answers. Artifacts writes a deliverable to disk and hands on a path; handoff keeps an in-memory, FIFO-evicting, per-spawn cache for payloads nobody wants to persist.

Two deliberate changes from the original

  • apply_handoff takes threshold_tokens instead of reading OPENHUMAN_TEST_HANDOFF_THRESHOLD_TOKENS. A host-named environment backdoor has no place in a redistributed crate; a host legitimately varies the threshold per agent or per model context window; and env-var-driven tests race under parallel execution. Callers pass HANDOFF_OVERSIZE_THRESHOLD_TOKENS for the previous default.
  • The extraction tool stays host-side. A tool is host vocabulary, and OpenHuman's reaches its transcript, TurnModelSource, and inference routing. This module owns the cache and the placeholder, never the query — the same line drawn in feat(harness): add a generic artifact-offload module #101 and feat(harness): add tool-call parsing as harness::tool_calling #102.

Tests

The module had none where it came from — it was covered only indirectly, through the sub-agent runner's integration tests. 16 are added, aimed at failures that would be silent rather than loud:

Test Guards
eviction_is_fifo_and_bounded evicting the wrong end — dropping the entry a model is most likely to ask about
an_oversized_result_is_stashed_and_replaced_by_a_placeholder the advertised result_id not resolving, so the model is told to call a tool that finds nothing
an_error_result_passes_through_however_large hiding diagnostic text behind an extraction call the agent must read directly
an_extraction_result_is_never_re_stashed the non-converging loop: stash the answer, hand back a placeholder, extract again
the_preview_never_splits_a_multibyte_character a byte-indexed cut panicking rather than misformatting
the_threshold_is_honoured_in_both_directions the new parameter not actually deciding anything
ids_are_unique_across_stores a collision serving one tool's payload in answer to another's query
  • cargo test --all-features --lib1782 passed, 0 failed
  • cargo clippy --all-features --all-targets — clean
  • cargo fmt --check — clean

Scope note: why the rest of subagent_runner is not here

Measured rather than assumed. The family is 7,471 LOC with 203 references across 10 OpenHuman domains:

File LOC domains refs
ops/runner.rs 1,748 8 60
ops/graph.rs 1,450 6 33
ops/provider.rs 318 5 18
tool_prep.rs 449 4 14
extract_tool.rs 634 3 7
handoff.rs 287 0 0

The centre of gravity — runner + graph + provider, 3,516 LOC and 111 references — reaches Composio, config, memory, inference, security and the desktop surface. That is exactly the coupling the capability traits exist to invert, so moving it before the host repointing lands would mean carrying those domains into the crate rather than injecting them. handoff.rs is the one file with genuinely zero host coupling, so it is the one that moves now.

extract_tool.rs is its natural partner and still stays: it is a host Tool implementation reaching the transcript, TurnModelSource, and inference::context_window_for_model.

🤖 Generated with Claude Code

senamakel and others added 2 commits August 13, 2026 19:19
Ported from OpenHuman's `agent/harness/subagent_runner/handoff.rs`. It is
the movable part of Phase 5's `subagent_runner` family; the runner core
itself is not movable yet (see the PR body).

The problem it solves: a sub-agent calls a tool that returns a
megabyte-scale payload, that blob goes into history as a tool-result
message, and the NEXT iteration ships the bloated history back to the
provider where it hits the context ceiling. One oversized result poisons
every turn after it.

Progressive disclosure stashes the full payload, substitutes a short
placeholder carrying size + preview + a result id + how to query it, and
lets the agent decide whether the preview already answers its task. The
extraction step only runs when it asks for a narrower view.

This is the sibling strategy to `harness::artifacts`: same problem, two
different answers. Artifacts writes a deliverable to disk and hands on a
path; handoff keeps an in-memory, FIFO-evicting, per-spawn cache for
payloads nobody wants to persist.

Two changes from the original, both deliberate:

- `apply_handoff` takes `threshold_tokens` instead of reading the
  threshold from an `OPENHUMAN_TEST_HANDOFF_THRESHOLD_TOKENS` environment
  variable. A host-named env backdoor has no place in a redistributed
  crate, a host legitimately varies the threshold per agent or per model
  context window, and env-var-driven tests race under parallel execution.
- The extraction tool stays host-side. A tool is host vocabulary, and
  OpenHuman's reaches its transcript, `TurnModelSource` and inference
  routing. This module owns the cache and the placeholder, not the query.

The module had NO tests where it came from - it was covered only
indirectly through the sub-agent runner's integration tests. 16 are added
here, aimed at the failures that would be silent rather than loud:

- eviction is FIFO and bounded, so it drops the oldest rather than the
  entry a model is most likely to ask about
- the placeholder's advertised result id actually resolves, or the model
  is told to call a tool that will find nothing
- an `Error` result passes through however large, because errors are
  diagnostic text the agent must read directly rather than a payload to
  hide behind another call
- an extraction result is never re-stashed, which would otherwise loop:
  stash the answer, hand back a placeholder, extract again
- the preview is taken by chars, not bytes, so a multibyte payload cannot
  panic the renderer

Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add the `regex` crate as a direct dependency to support stripping markup, data-URIs, and whitespace from oversized tool payloads in the harness handoff module. This dependency has already been resolved in the OpenHuman kernel profile, so it introduces no new packages there.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@senamakel, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fe9d34fd-26ab-4d0b-91e2-89b7a14099fb

📥 Commits

Reviewing files that changed from the base of the PR and between 30d6b3b and edbd4ee.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • Cargo.toml
  • src/harness/handoff.rs
  • src/harness/handoff_test.rs
  • src/harness/mod.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@senamakel
senamakel merged commit c9fea63 into main Aug 13, 2026
3 checks passed
@senamakel
senamakel deleted the harness-handoff branch August 13, 2026 16:55
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.

1 participant