fix(agent): treat a fence whose info string is a call tag as a call - #30
Conversation
Told to call tools inside <tool_call> tags, DeepSeek V4 writes its native invoke XML there. The tagged grammar claimed the block, found no JSON body, and dropped the call as malformed, though the same invoke parses bare. When a tag body opens with a named invoke, decode it with invoke_xml. The anchor keeps an invoke quoted inside other body text from executing. Refs tinyhumansai/openhuman#6722
Tiny Sweeper reviewTiny Sweeper reviewed this change across 6 lane(s) and found 0 active actionable finding(s). Detailed lane evidence and any incomplete work are listed below. State: Ready for maintainer review Review snapshot
Completeness: Complete What changedThe review could not produce a supported behavioral summary; inspect the cited changed surface and lane details below. FeaturesNone identified with supported citations. TestsNo supported feature-to-test mapping was produced. Test execution is not inferred. FindingsNo active actionable findings. Before mergeNone. How this fits togetherflowchart LR
n0["find_re<br/>changed"]:::changed
n1["scan_fences<br/>changed"]:::changed
n2["...anges_cover_languages_and_unclosed_fences<br/>changed"]:::changed
n3["probe_decided"]:::impacted
n4["len"]:::impacted
n5["fence_ranges"]:::impacted
n6["next_opener"]:::impacted
n7["decode_arguments"]:::impacted
n8["is_closing_marker"]:::impacted
n1 -->|calls| n4
n2 -->|calls| n5
n2 -->|tests| n5
n3 -->|calls| n4
n3 -->|calls| n6
n5 -->|calls| n1
n6 -->|calls| n0
n6 -->|calls| n4
n6 -->|calls| n8
n7 -->|calls| n4
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
Agent review detailscritique
security
tests
commits
description
e2e
Evidence and run details
|
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: Comment |
There was a problem hiding this comment.
tinysweeper found nothing blocking. Approving.
$0.0250 · 174,895 in / 20,538 out · 1,901 cached (1%) · ladder/vectors, gpt-5.6-luna, deepseek-v4-flash, deepseek/deepseek-v4-flash · 804 embedded
critique: $0.0107 · 62,287 in / 4,978 out · 0 cached (0%) · gpt-5.6-luna
security: $0.0115 · 84,412 in / 2,140 out · 1,901 cached (2%) · gpt-5.6-luna
tests: $0.0005 · 15,810 in / 6,974 out · 0 cached (0%) · deepseek-v4-flash
description: $0.0014 · 7,358 in / 4,365 out · 0 cached (0%) · deepseek/deepseek-v4-flash
|
Review at
All four shapes on your list stay protected, in batch and in both stream modes. Batch and stream agree on every row at this head. Bonus fix worth a test: at #29's head, One widening to consider (low severity): Accepted by design, just noting it: a model that genuinely quotes a call as The |
Adds a closed JSON tag body quoting an invoke (fails without the anchor) and a <tool_call><function=…> body (dropped on main). Narrows the decode_body doc to what the anchor guarantees: only the first invoke is anchored, as on the bare path.
DeepSeek V4 Flash wrote the opener as the fence info string, ```<tool_call>, and never closed the fence. The info string read as a language, so the unclosed fence protected the call to end of text as an example and it was dropped. A fence whose info string opens with a complete call tag (tag-family opener, named invoke, bare <invoke>) is now a call fence. A language-tagged fence still protects its contents. Refs tinyhumansai/openhuman#6722
opens_with_call_tag reused NAMED_INVOKE_OPEN_RE, which also accepts <function …> and any XML namespace, so an XSLT fence such as ```<xsl:function name="f"> became a call. Only a tag-family opener or an (optionally DSML-prefixed) <invoke> now marks a call fence.
0b100fa to
cc7107b
Compare
|
Rebased onto #29's |
|
Re-checked at |
Summary
DeepSeek V4 Flash put the call opener on the fence line itself,
```<tool_call>, and never closed the fence.protected.rsread<tool_call>as the fence's language. It isn't inTOOL_CALL_LANGUAGES, so the unclosed fence protected everything to the end of the text as an example, and the call was dropped.With this change, a fence whose info string starts with a complete call tag is a call fence and is not protected. A call tag here means a tag-family opener (
<tool_call>,<|tool_call|>, DSML and attribute forms), or an<invoke>, bare or named, optionally DSML-prefixed. The predicatetagged::opens_with_call_tagusesTAG_REplus one narrowFENCE_INVOKE_RE, anchored at offset 0. After review (cc7107b),<function …>,<function=…>and XML-namespaced tags are deliberately excluded:```<xsl:function name="f">is code, and with the broader grammar regex it dispatchedf. The grammars then scan the fence line as they would anywhere else. The body is decoded by #29, and the earlier stray</tool_call>in the same turn is swept by the existing orphan-closer handling.A language-tagged fence still protects its contents:
```xml,```xml<tool_call>and```text <tool_call>all stay examples. Bare```fences were already unprotected by design (protected.rsmodule docs), and this PR doesn't change that.Related issue
Refs tinyhumansai/openhuman#6722. Together with #29 this covers the reproduction. Verified against the real user record via the harness-options probe (counts only): the record now yields exactly 1 call (
InvokeXml), and narration-only records still yield 0.API or behavior changes
A fence whose info string opens with a call tag is no longer reported by
fence_ranges/open_fence_start, so calls inside it parse. There is no public signature change.Validation
Commands actually run, with their outcome:
cargo fmt --all -- --check: cleancargo clippy --all-targets --all-features -- -D warnings: ran without--all-features(cargo clippy --all-targets -- -D warnings): cleancargo build --all-targets --all-features: not run with--all-features; the default-feature build via clippy/test succeededcargo test --all-features: rancargo test(default features): all pass (113 + 334 + 20 + 1)Also ran
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps: clean.Tests
Added to
src/parse/test/engine.rs:an_unclosed_fence_whose_info_string_is_a_call_tag_is_a_call: a sanitized shape of the real turn (a<todos>block, a stray</tool_call>, then an unclosed```<tool_call>with a wrapped invoke)a_fence_whose_info_string_is_a_named_invoke_is_a_calla_closed_fence_whose_info_string_is_a_call_tag_is_a_calla_language_fence_still_protects_a_call_tag_example: controls for```xml,```xml<tool_call>,```text <tool_call>,```<function name="f">,```<xsl:function name="f">and```<function=shell>. The last three dispatched with the pre-review predicate:f(command="rm -rf /")was the first failure.Red check: with #29 applied and this change absent, the first three fail on
calls.len() == 1, and the control passes.Documentation
I updated the
protected.rsmodule docs to list the new exception.Checklist
#[allow(...)],#[ignore], or relaxed lints.envcontents in the diff or the descriptionReview status:
tinysweeper/reviewtimed out at the current head (900s, 'No code was reviewed'). It is advisory, not a required check. tinysweeper APPROVED the earlier head0b100fa, and the fleet reviewer reviewed every head.