Skip to content

fix(harness): recover common tool-call argument mistakes and stop repeating cut-off calls - #365

Merged
senamakel merged 20 commits into
mainfrom
fix-tool-call-errors
Oct 10, 2026
Merged

senamakel merged 20 commits into
mainfrom
fix-tool-call-errors

Conversation

@senamakel

@senamakel senamakel commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Summary

Fixes for the tool-call failures in OpenHuman production Langfuse traces (Oct 3–10), each with a regression test. Moves the vendor/tinytools gitlink to tinyhumansai/tinytools#59.

Failure in production Fix
A tool named tool_call failed 100/100 times. DeepSeek wraps a real call in an envelope ({"name":"use_skill","arguments":…}, {"use_skill":{…}}, {skill,tool,args}). New agent_loop/tool_call_wrapper.rs. When the name is a pseudo name (tool_call, call_tool, function_call, …) and no real tool has it, the call is unwrapped and sent to the real tool. String arguments go through recover_object. This runs before admission, so approvals and policy see the real tool. The event emitted is InvalidToolArgs { recovery: "unwrapped:<tool>" }. A payload that names no tool stays an unknown-tool error.
todo items sent title/item/text/task instead of content. These are accepted as alternatives to content (content wins). The item schema no longer requires them. A blank item is still refused.
tool_search was called with description/name/q/skill/{} instead of query. Those keys are read as query. Empty arguments get a short error with an example call.
A schema rejection echoed the whole JSON Schema back on every failure. The rejection now gives a short expected arguments: <type_signature> hint. The invalid arguments for tool prefix is unchanged, because hosts classify on it.
Tool calls cut off at the 16,384 output-token limit were retried with the same oversized call (workflow_builder turns of 1000s+). New wording: don't repeat the call, split it into smaller calls. A second consecutive cut-off of the same tool gets a stronger message, and a further one ends the run with LimitExceeded.
A non-zero shell exit is exported as Langfuse ERROR (27% of shell calls showed as errors, about 70% of them ordinary non-zero exits). The OTLP converter honors an observation.level span attribute: WARNING exports as WARNING for any span status. Hosts use it for command exits, cancelled turns and force-closed spans.

Tests

  • cargo test --workspace: no failures (harness 2423, graph 476, plus the other crates)
  • clippy -D warnings and cargo fmt --check are clean
  • New tests: tool_call_wrapper_tests.rs (12), tools_args_recovery_tests.rs (3 wrapper cases), content_aliases_are_accepted, schema_admits_content_aliases_and_a_missing_status, answer_tool_search_reads_query_aliases, answer_tool_search_with_no_query_shows_an_example_call, use_skill_args_sent_as_relaxed_json_string_runs_the_tool, use_skill_with_null_tool_and_args_runs_the_listing_half, validation_corrective_is_a_short_hint_not_the_whole_schema, truncated_tool_call_tests.rs (4), a_span_can_set_its_level_to_warning

Docs: docs/modules/harness/tool.md and the RunPolicy doc comments are updated.

Note: this branch is based on the commit OpenHuman currently pins (b60a650), and main is 8 commits ahead.

Summary by CodeRabbit

  • New Features

    • Todo items can now provide their text using common alternatives such as “title,” “task,” or “description.” Tool searches also accept search terms under several common field names.
    • Calls wrapped in common tool-call formats can be routed to the intended tool when it’s available.
    • Trace exports can preserve explicitly marked warning levels.
  • Improvements

    • Invalid tool arguments receive clearer correction guidance, including support for recovering some loosely formatted inputs.
    • Oversized tool calls prompt the model to split the work into smaller steps. Repeated cutoffs for the same tool can stop the run.

senamakel and others added 19 commits October 10, 2026 07:04
Vendors the tinytools package so the build no longer depends on fetching it at compile time.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The loop now dispatches tool calls returned by the model and feeds their
results back into the conversation, so multi-step tool use works end to
end. Tool handling lives in a dedicated module to keep the loop itself
focused on orchestration.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Moved the tool call wrapping logic out of the agent loop into a dedicated
module so it can be reused and tested independently. No behaviour change.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
A call whose name is a wrapper alias such as tool_call or function_call is now
unwrapped before the discovery bridge, rules, hooks and gates run, so those
stages see the real tool name and arguments. The rewrite is reported as an
argument repair rather than an unknown tool call, since the call does run.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Introduce a bridge module that connects the discovery tool to the harness, and add tests covering its behaviour.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The todo tests were extracted from the parent module into their own
mod_tests file to keep the implementation and its test suite separate.
No test behaviour was changed.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add tests for the todo tool covering empty input, duplicate entries, and
status transitions so regressions in validation and state handling are
caught early.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add tests for the agent loop's handling of truncated tool calls, verifying
that partial or malformed tool call payloads are detected and surfaced
correctly rather than being silently dropped.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Move the tool call execution and result collection out of the main loop
body into a dedicated helper so the loop reads as a sequence of clear
steps. Behaviour is unchanged.

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

Track which tools were cut off by the output token limit on the previous
truncated turn so a repeat of the same oversized call is answered with a
stronger corrective instead of another token-cap boost, and end the run
when the same tool is cut off again after that warning. The output cap is
fixed, so re-sending the same large call only burns the retry budget.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The spent() test fixture now initializes the truncated_tool_names and
truncated_repeat_names sets, which the TurnRecovery struct gained. Without
them the fixture no longer compiles against the current struct shape.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Moved the response recovery logic out of the agent loop into a dedicated
module so the loop body stays focused on orchestration. No behaviour
change.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The invalid-args tool error now returns a compact TypeScript-style signature of the expected arguments rather than the full JSON Schema, so the docs and the `InvalidArgsPolicy` comments were updated to describe what actually goes back into the transcript.

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

Expands the tool-call recovery documentation to cover wrapped pseudo-tool
calls that are retargeted to the real tool before admission, and per-property
object recovery plus null-dropping for optional properties. Also notes on
RunPolicy that a tool cut off on two consecutive truncated turns gets a
stronger corrective and one more cut-off call stops the run.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Adjust the OTLP trace exporter so spans are serialized and exported correctly, and add tests covering the export path to guard against regressions.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Reformatted two attribute push calls in the OTLP trace exporter and its
tests to satisfy rustfmt line width limits. No behaviour change.

Auto-committed-on: macbook
Co-authored-by: Medulla <medulla@tinyhumans.ai>
…e_export/otlp.rs,crates/tinyage

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

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@tinysweeper

tinysweeper Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Tiny Sweeper review

Tiny Sweeper reviewed this change across 6 lane(s) and found 12 active actionable finding(s). Detailed lane evidence and any incomplete work are listed below.

State: Changes requested
Priority: critical
Reviewed head: 42fcd8e7ff67
Updated: 1791603787 (Unix time)

Review snapshot

Change surface Files Review signal Count
Production 12 Active findings 9
Tests 7 Noted findings 0
Documentation 1 Resolved findings 36
Configuration 0 Pending checks/questions 0

Completeness: Complete
Test assessment: No supported feature-to-test mapping was available; this does not mean tests are absent or passed.

What changed

The review could not produce a supported behavioral summary; inspect the cited changed surface and lane details below.

Features

None identified with supported citations.

Tests

No supported feature-to-test mapping was produced. Test execution is not inferred.

Findings

  • medium · critique · Require the complete skill payload before selecting use_skill — Any wrapper payload containing only `{"skill":"email"}` (or a payload with `skill` but no usable `tool`/`args`) is rewritten to `use_skill`. The documented special shape is `{skill (crates/tinyagents\-harness/src/agent\_loop/tool\_call\_wrapper\.rs:83)
  • critical · security · Construct the alias argument with the loop key — `json!({ key: ... })` treats `key` as a literal object field name, not as the value of the loop variable. Every iteration therefore passes `{ "key": "read a pdf" }`, causing `answe (crates/tinyagents\-harness/src/tool/discover/mod\_tests\.rs:446)
  • medium · security · Require a complete skill payload before selecting use_skill — Any object containing `skill` is retargeted to `use_skill` unless another callable name is present, including incomplete payloads such as `{ "skill": "email" }`. This bypasses the (crates/tinyagents\-harness/src/agent\_loop/tool\_call\_wrapper\.rs:83)
  • medium · security · Test every argument alias and its precedence — The new wrapper accepts the shared `ARGUMENT_KEYS` aliases, but the added tests cover only `arguments` and `args`; they do not verify fallback through the other accepted aliases or (crates/tinyagents\-harness/src/agent\_loop/tool\_call\_wrapper\.rs:127)
  • high · tests · Track repeated truncations by invocation, not tool name — The repeat detection still keys on the tool *name*: `truncated_tool_names` and `truncated_repeat_names` are `BTreeSet<String>` of names, and the matching in `tools.rs` answers a cu (crates/tinyagents\-harness/src/agent\_loop/response\_recovery\.rs:125)
  • high · description · Track repeated cut-off calls by invocation, not tool name — The stop-the-run guard still keys on the tool *name*: two cut-off calls to the same tool on consecutive turns — even for genuinely different, smaller work — arm the one-more-cut-of (\(pull request description\))
  • medium · description · Advertise the accepted text-field aliases in the todo schema — The tool now accepts `title`/`item`/`text`/`task`/`description` as the item's text, but the schema sent to the model still lists only `content`. A model that would otherwise have d (\(pull request description\))

Previously reported and still active

  • Require the complete skill payload before selecting use\_skill
  • Wire the new test module into agent\_loop

Resolved this pass

  • Add the tool call wrapper module source file
  • Wire the new test module into agent_loop
  • medium — Test query alias fallback and precedence
  • Add the tool call wrapper module source file
  • Add the referenced recovery test module
  • Add the referenced truncated-call test module
  • Track repeated truncations by invocation, not tool name
  • Wire the new test module into agent_loop
  • Use the loop value as the JSON field name
  • Track repeated calls by invocation rather than tool name
  • Advertise every accepted text-field alias in the schema
  • Populate repeated truncation positions before checking them
  • Wire the new test module into agent_loop
  • Populate the repeated truncation positions
  • Add the tool call wrapper module source file
  • Add the referenced recovery test module
  • Add the referenced truncated-call test module
  • Wire the new test module into agent_loop
  • Wire the new test module into agent_loop
  • Populate repeated truncation positions before checking them
  • Populate the repeated truncation positions
  • Test query alias fallback and precedence
  • Require the complete skill payload before selecting use_skill
  • Require the complete skill payload before selecting use_skill
  • Add the tool call wrapper module source file
  • Add the referenced recovery test module
  • Add the referenced truncated-call test module
  • Test query alias fallback and precedence
  • Require the complete skill payload before selecting use_skill
  • Wire the new test module into agent_loop
  • Wire the new test module into agent_loop
  • Use the loop value as the JSON field name
  • Populate repeated truncation positions before checking them
  • Populate the repeated truncation positions
  • Require the complete skill payload before selecting use_skill
  • Track repeated calls by invocation rather than tool name

Before merge

  • Address carried finding Require the complete skill payload before selecting use\_skill.
  • Address carried finding Wire the new test module into agent\_loop.
  • Address Construct the alias argument with the loop key (crates/tinyagents\-harness/src/tool/discover/mod\_tests\.rs).
  • Address Track repeated truncations by invocation, not tool name (crates/tinyagents\-harness/src/agent\_loop/response\_recovery\.rs).
  • Address Track repeated cut-off calls by invocation, not tool name (\(pull request description\)).

How this fits together

flowchart LR
  n0["parse_items<br/>changed"]:::changed
  n1["dispatch"]:::impacted
  n1 -->|calls| n0
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading
Agent review details

critique

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: Reviewed 3 files; 1 finding. (11 earlier finding(s) still open) _Code retrieval was unavailable (model: ladder embeddings returned 400 Bad Request: {"error":{"message":"unknown ladder vectors; known ladders are flash (also chat-v1, flash-v1), instant (also no-think, instant-v1), reasoning (also deepseek), max-reasoning (also max-reasoning-v1), deepseek-flash (also reasoning-v1, agentic-v1), deep (also luna), scribe, uncensored, vectors-oai3 (also embeddings-oai3-v1), vision (also vision-v1, multimodal-v1), image (also images-v1, image-v1), vi), so this review saw the diff alone._ _Memory was unavailable (model: cortex: v1/recall: error sending request for url (http://cortexdb:3141/v1/recall\)\), so this review ran without it._
  • Evidence: crates/tinyagents\-harness/src/agent\_loop/tool\_call\_wrapper\.rs — Require the complete skill payload before selecting use_skill

security

  • Conclusion: Failure
  • Scope reviewed: all assigned evidence
  • Lane summary: Reviewed 3 files; 3 findings. (4 earlier finding(s) still open) (1 observation(s) grouped into shared inline comments) _Code retrieval was unavailable (model: ladder embeddings returned 400 Bad Request: {"error":{"message":"unknown ladder vectors; known ladders are flash (also chat-v1, flash-v1), instant (also no-think, instant-v1), reasoning (also deepseek), max-reasoning (also max-reasoning-v1), deepseek-flash (also reasoning-v1, agentic-v1), deep (also luna), scribe, uncensored, vectors-oai3 (also embeddings-oai3-v1), vision (also vision-v1, multimodal-v1), image (also images-v1, image-v1), vi), so this review saw the diff alone._ _Memory was unavailable (model: cortex: v1/recall: error sending request for url (http://cortexdb:3141/v1/recall\)\), so this review ran without it._
  • Evidence: crates/tinyagents\-harness/src/tool/discover/mod\_tests\.rs — Construct the alias argument with the loop key
  • Evidence: crates/tinyagents\-harness/src/agent\_loop/tool\_call\_wrapper\.rs — Require a complete skill payload before selecting use_skill
  • Evidence: crates/tinyagents\-harness/src/agent\_loop/tool\_call\_wrapper\.rs — Test every argument alias and its precedence

tests

  • Conclusion: Failure
  • Scope reviewed: all assigned evidence
  • Lane summary: This revision adds the previously missing wrapper module and both test modules, wires them into `agent_loop`, tests the tool_search query aliases with precedence, and adds heuristics so `{skill, ...}` payloads are only retargeted to `use_skill` when that is unambiguous — resolving those earlier findings. Three findings remain: the repeated-truncation tracking is still keyed by tool name rather than by invocation, the alias loop in the new tool_search test passes a literal `"key"` field name, and the todos schema still does not advertise the alias fields the tool now accepts. (2 findings discarded for not matching a changed line) (5 earlier finding(s) still open) _Code retrieval was unavailable (model: ladder embeddings returned 400 Bad Request: {"error":{"message":"unknown ladder vectors; known ladders are flash (also chat-v1, flash-v1), instant (also no-think, instant-v1), reasoning (also deepseek), max-reasoning (also max-reasoning-v1), deepseek-flash (also reasoning-v1, agentic-v1), deep (also luna), scribe, uncensored, vectors-oai3 (also embeddings-oai3-v1), vision (also vision-v1, multimodal-v1), image (also images-v1, image-v1), vi), so this review saw the diff alone._ _Memory was unavailable (model: cortex: v1/recall: error sending request for url (http://cortexdb:3141/v1/recall\)\), so this review ran without it._
  • Evidence: crates/tinyagents\-harness/src/agent\_loop/response\_recovery\.rs — Track repeated truncations by invocation, not tool name

commits

  • Conclusion: Neutral
  • Scope reviewed: all assigned evidence
  • Lane summary: Nothing sensitive found in what this pull request commits.

description

  • Conclusion: Failure
  • Scope reviewed: all assigned evidence
  • Lane summary: The wrapper module, both referenced test modules, and the agent_loop wiring all landed; query alias precedence is now tested and the truncation-repeat positions are populated before use. Two earlier concerns remain: the repeated cut-off guard still tracks by tool name rather than invocation, and the todo schema still does not advertise the text aliases the tool accepts. Otherwise the change looks sound and safe to merge. (4 earlier finding(s) still open) _Code retrieval was unavailable (model: ladder embeddings returned 400 Bad Request: {"error":{"message":"unknown ladder vectors; known ladders are flash (also chat-v1, flash-v1), instant (also no-think, instant-v1), reasoning (also deepseek), max-reasoning (also max-reasoning-v1), deepseek-flash (also reasoning-v1, agentic-v1), deep (also luna), scribe, uncensored, vectors-oai3 (also embeddings-oai3-v1), vision (also vision-v1, multimodal-v1), image (also images-v1, image-v1), vi), so this review saw the diff alone._ _Memory was unavailable (model: cortex: v1/recall: error sending request for url (http://cortexdb:3141/v1/recall\)\), so this review ran without it._
  • Evidence: \(pull request description\) — Track repeated cut-off calls by invocation, not tool name
  • Evidence: \(pull request description\) — Advertise the accepted text-field aliases in the todo schema

e2e

  • Conclusion: Neutral
  • Scope reviewed: all assigned evidence
  • Lane summary: No end-to-end harness in this repository: no e2e test files and no e2e workflow.
Evidence and run details
  • Models: gpt-5.6-luna, glm-5.3-flash
  • Spend: $0.015414
  • Tokens: 232962 input · 21950 output · 28081 cached · 0 embedding
Head State Pass summary
7d8afa81353b changes requested 12 active finding(s), 0 resolved finding(s) (at 1791603147)
42fcd8e7ff67 changes requested 7 active finding(s), 36 resolved finding(s) (at 1791603787)

tinysweeper 0.1.0

@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Review in Change Stack →Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: bb743249-785a-4d43-9dde-c8a48c130b69

📥 Commits

Reviewing files that changed from the base of the PR and between 7d8afa8 and 42fcd8e.


📒 Files selected for processing (3)
  • crates/tinyagents-harness/src/agent_loop/tool_call_wrapper.rs
  • crates/tinyagents-harness/src/agent_loop/tool_call_wrapper_tests.rs
  • crates/tinyagents-harness/src/tool/discover/mod_tests.rs

 ___________________________________________________________________________________________________________
< A CodeRabbit review a day keeps the merge cop away: https://x.com/ThePrimeagen/status/1986151686839116209 >
 -----------------------------------------------------------------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7731a960-1e11-4ff2-a1f3-fca6949e5003


📥 Commits

Reviewing files that changed from the base of the PR and between ae6c4d3 and 7d8afa8.



📒 Files selected for processing (21)
  • crates/tinyagents-graph/src/todos/mod_tests.rs
  • crates/tinyagents-graph/src/todos/tool.rs
  • crates/tinyagents-harness/src/agent_loop/mod.rs
  • crates/tinyagents-harness/src/agent_loop/response_recovery.rs
  • crates/tinyagents-harness/src/agent_loop/tool_call_wrapper.rs
  • crates/tinyagents-harness/src/agent_loop/tool_call_wrapper_tests.rs
  • crates/tinyagents-harness/src/agent_loop/tools.rs
  • crates/tinyagents-harness/src/agent_loop/tools_args_recovery_tests.rs
  • crates/tinyagents-harness/src/agent_loop/truncated_tool_call_tests.rs
  • crates/tinyagents-harness/src/agent_loop/turn_recovery.rs
  • crates/tinyagents-harness/src/agent_loop/turn_recovery_tests.rs
  • crates/tinyagents-harness/src/agent_loop/types.rs
  • crates/tinyagents-harness/src/context/mod.rs
  • crates/tinyagents-harness/src/context/types.rs
  • crates/tinyagents-harness/src/observability/trace_export/otlp.rs
  • crates/tinyagents-harness/src/observability/trace_export/otlp_tests.rs
  • crates/tinyagents-harness/src/runtime/types.rs
  • crates/tinyagents-harness/src/tool/discover/bridge.rs
  • crates/tinyagents-harness/src/tool/discover/mod_tests.rs
  • docs/modules/harness/tool.md
  • vendor/tinytools


Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.




📝 Walkthrough
📝 Walkthrough

Walkthrough

The changes add input aliases for todo items and tool searches, add pseudo-tool unwrapping and repeated-truncation handling, change tool-argument recovery messages, export OTLP observation levels, and update the vendored tinytools reference.

Changes

Todo Content Aliases

Layer / File(s) Summary
Todo item parsing and schema
crates/tinyagents-graph/src/todos/tool.rs, crates/tinyagents-graph/src/todos/mod_tests.rs
Todo parsing accepts content, title, item, text, task, and description, in that order, using the first non-blank value. The schema no longer requires content or status. Tests cover precedence, fallback, and schema validation.

Tool-Call Recovery

Layer / File(s) Summary
Wrapped calls and argument recovery
crates/tinyagents-harness/src/agent_loop/tool_call_wrapper.rs, crates/tinyagents-harness/src/agent_loop/tools.rs, crates/tinyagents-harness/src/agent_loop/tool_call_wrapper_tests.rs, crates/tinyagents-harness/src/agent_loop/tools_args_recovery_tests.rs, crates/tinyagents-harness/src/agent_loop/mod.rs, crates/tinyagents-harness/src/runtime/types.rs, docs/modules/harness/tool.md
Tool admission unwraps recognized wrappers when their target is callable. Argument-recovery messages use a compact expected-argument signature. Tests cover wrapper payloads and recovery behavior; the documentation describes these changes.
Repeated truncated-call recovery
crates/tinyagents-harness/src/agent_loop/types.rs, crates/tinyagents-harness/src/context/..., crates/tinyagents-harness/src/agent_loop/response_recovery.rs, crates/tinyagents-harness/src/agent_loop/tools.rs, crates/tinyagents-harness/src/agent_loop/turn_recovery.rs, crates/tinyagents-harness/src/agent_loop/truncated_tool_call_tests.rs, crates/tinyagents-harness/src/agent_loop/turn_recovery_tests.rs, crates/tinyagents-harness/src/agent_loop/mod.rs, crates/tinyagents-harness/src/runtime/types.rs
The agent loop tracks truncated tool names and repeated batch positions. It gives repeated calls a distinct corrective and returns LimitExceeded after a further truncation of the same tool. Tests cover repeated and alternating tool calls.

OTLP Observation Levels

Layer / File(s) Summary
Span level export
crates/tinyagents-harness/src/observability/trace_export/otlp.rs, crates/tinyagents-harness/src/observability/trace_export/otlp_tests.rs
OTLP export uses an explicit WARNING level when present, otherwise exports ERROR for error spans. It excludes the level attribute from generic metadata. Tests cover level and status-message output.

Tool Search Query Aliases

Layer / File(s) Summary
Search query resolution
crates/tinyagents-harness/src/tool/discover/bridge.rs, crates/tinyagents-harness/src/tool/discover/mod_tests.rs
Tool search checks query first, then q, text, description, name, skill, and search. Missing or blank queries produce an error with an example request.

Tinytools Reference

Layer / File(s) Summary
Subproject reference update
vendor/tinytools
The subproject reference changed from commit bd60b9b52f1998b4483b9cf107a72d3338e672f8 to c3d99e079702fb3723b58881f48a97892073143b.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant Model
  participant Admission as Tool admission
  participant Unwrapper as unwrap_wrapped_call
  participant Tool as Callable tool
  Model->>Admission: Submit pseudo-tool call
  Admission->>Unwrapper: Decode wrapper payload
  Unwrapper-->>Admission: Return callable tool name and arguments
  Admission->>Tool: Dispatch unwrapped call
Loading

Suggested reviewers: sanil-23



Merge Risk: ⚪ Minimal · up to 7d8af

This change broadens tool-call argument recovery, stops repeated cut-off calls, and exports warning-level spans. No concrete merge-blocking defect was established. The tinytools submodule update could not be inspected, so confirm it separately.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 7d8af

Recovered calls still pass existing permission checks, and potentially incomplete calls remain blocked from execution. The main remaining uncertainty is the updated dependency, whose implementation was unavailable for comparison.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The newly accepted representations reach tools already callable through the run’s registry and existing discovery policy. Effective downstream exposure depends on those tools and host permissions; deployed tenant, service, credential, and environment scope was not established. Todo persistence remains selected by the active context’s thread.

Security Findings and Attack Paths

  • inferred — The inspected bypass candidates did not establish newly acquired authority: hidden or unregistered target names do not gain dispatch through wrapper recovery, and calls marked potentially truncated are answered before dispatch. This conclusion is bounded to the inspected admission and recovery paths and does not resolve the unavailable dependency implementation.

Trust Boundaries and Controls

  • observed — Wrapper decoding normalizes untrusted input but does not itself authorize execution. The effective target and arguments remain subject to the shared admission controls, including the final rules check and host authorization. Wrapped tool_search requests use the existing gated discovery bridge.

Resilience and Maintainability Implications

  • observed — Truncation guards use batch positions rather than provider call IDs. Terminal repetition retracts the appended response and exits before admission. Recovery state resets after resolved tool or final turns, and concurrent execution admits calls before starting tool futures. Interrupted-context reuse and combined wrapper/truncation behavior lack explicit regression coverage, although the inspected paths retain fail-closed guards.

Hardening Proposals

  • proposed — Where target identity can be determined without admitting incomplete arguments, align repetition accounting with the effective callable identity. Preserve positional rejection and the global retry budget, and cover alternating wrapper/canonical representations and interrupted-context reuse.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 75.38% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 65 functions across 19 files. (2 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly summarizes the primary changes: recovering common tool-call argument mistakes and stopping repeated cut-off calls.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.


Full details: Docstring Coverage

Explanation

Docstring coverage is 75.38% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 65 functions across 19 files. (2 skipped: 2 unsupported.)




  • Fix all pre-merge checks with AI
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

A rabbit checks the tool-call trail,
And finds a wrapper’s hidden tale.
The todo fields now share the load,
Warnings take their marked-out code.
I thump my paws: the tests prevail!

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

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Requesting changes: 1 lane(s) blocking, worst finding is critical.

Fix or reply to the findings below and push. The next review clears this automatically once they are gone — you should not need to dismiss anything by hand.

             $0.0597 · 806,828 in / 56,956 out · 91,104 cached (11%) · gpt-5.6-luna, glm-5.3-flash
critique:    $0.0364 · 458,437 in / 39,448 out · 51,384 cached (11%) · gpt-5.6-luna, glm-5.3-flash
security:    $0.0227 · 281,491 in / 13,557 out · 38,120 cached (14%) · gpt-5.6-luna
tests:       $0.0002 · 21,956 in  / 1,032 out  · 64 cached (0%)      · glm-5.3-flash
description: $0.0002 · 21,846 in  / 172 out    · 1,408 cached (6%)   · glm-5.3-flash

Comment thread crates/tinyagents-harness/src/agent_loop/mod.rs
Comment thread crates/tinyagents-harness/src/agent_loop/mod.rs
Comment thread crates/tinyagents-harness/src/agent_loop/mod.rs
Comment thread crates/tinyagents-harness/src/agent_loop/response_recovery.rs
Comment thread crates/tinyagents-harness/src/tool/discover/bridge.rs
Comment thread crates/tinyagents-harness/src/tool/discover/mod_tests.rs
Comment thread crates/tinyagents-harness/src/agent_loop/response_recovery.rs
Comment thread crates/tinyagents-graph/src/todos/tool.rs
Comment thread crates/tinyagents-harness/src/agent_loop/tools.rs
@tinysweeper tinysweeper Bot added the priority: p0 Drop what you are doing. Data loss, a live break, or an exploitable hole. label Oct 10, 2026
A payload shaped like `{name, skill, arguments}` was being rewritten into a
use_skill call whenever the skill key was present, even when `name` referred to
a real callable tool that legitimately takes `skill` as its own argument. The
wrapper now only rewrites when `name` is not a callable tool, and tests cover
both this case and a blank query falling through to a populated alias.

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

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Requesting changes: 3 lane(s) blocking, worst finding is critical.

Fix or reply to the findings below and push. The next review clears this automatically once they are gone — you should not need to dismiss anything by hand.

             $0.0154 · 232,962 in / 21,950 out · 28,081 cached (12%) · gpt-5.6-luna, glm-5.3-flash
critique:    $0.0071 · 82,406 in  / 7,238 out  · 15,099 cached (18%) · gpt-5.6-luna, glm-5.3-flash
security:    $0.0076 · 81,024 in  / 7,125 out  · 12,790 cached (16%) · gpt-5.6-luna
tests:       $0.0002 · 22,897 in  / 2,594 out  · 64 cached (0%)      · glm-5.3-flash
description: $0.0002 · 22,950 in  / 2,305 out  · 64 cached (0%)      · glm-5.3-flash

.and_then(Value::as_str)
.map(str::trim)
.is_some_and(|name| !is_wrapper_name(name) && is_callable(name));
if !wrapped_name && !named_real_tool {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium critique confident

Require the complete skill payload before selecting use_skill

Any wrapper payload containing only {"skill":"email"} (or a payload with skill but no usable tool/args) is rewritten to use_skill. The documented special shape is {skill, tool, args}, so incomplete or malformed wrapper payloads should remain untouched and receive the normal corrective response rather than being admitted as a use_skill call. Require the complete skill payload, including the fields needed by the pack schema, before returning this rewrite.


Additional security observation

priority medium confident

Require a complete skill payload before selecting use_skill

[RULE] incomplete-input-validation

Any object containing skill is retargeted to use_skill unless another callable name is present, including incomplete payloads such as { "skill": "email" }. This bypasses the intended {skill, tool, args} shape and turns malformed pseudo-calls into real use_skill invocations, producing misleading downstream validation or execution behavior. Require the fields needed by the use_skill contract before returning this rewrite; otherwise leave the wrapper untouched.

[RULE] incomplete-payload-validation ·

async fn answer_tool_search_reads_query_aliases() {
let policy = ToolDiscoveryPolicy::default();
for key in ["q", "text", "description", "name", "skill", "search"] {
let answer = answer_tool_search(&catalog(), &policy, &json!({ key: "read a pdf" })).await;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority critical security confident

Construct the alias argument with the loop key

json!({ key: ... }) treats key as a literal object field name, not as the value of the loop variable. Every iteration therefore passes { "key": "read a pdf" }, causing answer_tool_search to reject the query and the first assertion to fail. Build a Value::Object with key.to_string() so the test actually covers each alias.

[RULE] test-correctness ·

/// when it is a JSON string; otherwise every key but the name key, which is
/// the "flattened" spelling `{"tool": "x", "query": "..."}`.
fn wrapped_arguments(payload: &Map<String, Value>, name_key: &str) -> Value {
for key in ARGUMENT_KEYS {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority medium security likely

Test every argument alias and its precedence

The new wrapper accepts the shared ARGUMENT_KEYS aliases, but the added tests cover only arguments and args; they do not verify fallback through the other accepted aliases or behavior when multiple aliases are present. Since alias selection determines the arguments passed to the real tool, add focused tests for every alias and for the documented precedence order.

[RULE] missing-alias-coverage ·

"[agent_loop] same tool cut off by the output limit twice; answering with the stop-repeating corrective"
);
}
turn_recovery

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority high tests uncertain

Track repeated truncations by invocation, not tool name

The repeat detection still keys on the tool name: truncated_tool_names and truncated_repeat_names are BTreeSet<String> of names, and the matching in tools.rs answers a cut-off call with the stop-retrying corrective — or ends the run — whenever any call to that same tool name is cut off again. A legitimate, differently-shaped invocation of the same tool (e.g. a small save_workflow call after a huge one was cut) inherits the previous call's strike and can terminate the run on its first truncation. Track the strike per call shape or per invocation (for example a hash of the tool name plus argument length, or a counter of consecutive truncated turns that re-sent the same call), so a new, smaller call is judged on its own.

[RULE] wrong-key-tracking ·

@senamakel
senamakel merged commit 8786b3d into main Oct 10, 2026
14 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p0 Drop what you are doing. Data loss, a live break, or an exploitable hole.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant