Skip to content

after_tool middleware order inverted: progressive-disclosure handoff is dead code, outcome capture sees pre-cap content #4464

Description

@senamakel

Part of the v0.58.7 → HEAD TinyAgents-migration audit (see parent issue). The tinyagents crate runs after_tool hooks in reverse registration order (tinyagents-1.3.0/src/harness/middleware/mod.rs:318-326, .iter().rev()); two of our registrations assume the opposite, with comments asserting parity the code doesn't deliver.

A. Progressive-disclosure handoff is dead code

TurnContextMiddleware::install (src/openhuman/tinyagents/middleware.rs:283-300) registers HandoffMiddleware before ToolOutputMiddleware, with a comment asserting handoff runs first. Reverse order means the 16 KiB output budget truncates first; a capped payload (~4k tokens) can never cross the 50k-token handoff threshold (subagent_runner/handoff.rs:38). The entire ResultHandoffCache/extract_from_result machinery (ops/runner.rs:604-680, ops/graph.rs:256-262) is unreachable — integrations_agent's oversized Composio payloads are hard-truncated with nothing to drill into (legacy tool_source.rs stashed the raw result).

B. ToolOutcomeCaptureMiddleware sees pre-cap content

It's pushed after context_mw.install (src/openhuman/tinyagents/mod.rs:1371), so its after_tool runs before summarization/tokenjuice/caps — despite its doc "Runs last… records the final (summarized/capped) content" (middleware.rs:1375-1376). Effect: the sink holds the full raw output of every tool call in memory for the whole turn (multi-MB), and failure classification / ToolCallRecord.output_summary see pre-cap content. (MemoryProtocolMiddleware is already pushed first for exactly this reason — the pattern exists.)

Fix plan

  1. Flip the registration order in TurnContextMiddleware::install so effective after_tool order is: handoff (sees raw) → summarizer/tokenjuice/caps → capture (sees final). Fix the comments to state the crate's reverse-order rule explicitly.
  2. Move the ToolOutcomeCaptureMiddleware push before context_mw.install in assemble_turn_harness.
  3. Add a seam unit test that registers sentinel middlewares and asserts the effective execution order — so a future crate change or refactor can't silently invert it again.
  4. e2e: oversized scripted tool result → assert the model-visible message carries the handoff pointer and the drill-down tool can retrieve the stashed payload.

Acceptance criteria

  • A >50k-token tool result produces a handoff pointer, not a blind truncation; the cache is populated.
  • ToolCallOutcome/failure classification record post-cap content; memory held per call is bounded.
  • Order-assertion test in place.

Metadata

Metadata

Assignees

No one assigned

    Labels

    agentBuilt-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/.bugpriority: mediumEdge case or race conditionsubtaskSubtask of a larger tracked effort.

    Type

    No type

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions