Skip to content

fix(antigravity): keep an undelivered agy handoff from failing a completed turn - #414

Open
r-uben wants to merge 3 commits into
pleaseai:mainfrom
r-uben:fix/413-agy-recipient-not-found
Open

fix(antigravity): keep an undelivered agy handoff from failing a completed turn#414
r-uben wants to merge 3 commits into
pleaseai:mainfrom
r-uben:fix/413-agy-recipient-not-found

Conversation

@r-uben

@r-uben r-uben commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Closes #413

Summary

agy --print --output-format stream-json advertises its full interactive tool surface, including send_message — which needs an inbox that print mode never registers. A subagent-style prompt ("report your findings back to the main agent") is enough for the model to deliver its reply that way. The call fails, and the CLI marks the whole run ERROR:

error executing cascade step: CORTEX_STEP_TYPE_GENERIC: recipient "main" not found

That arrives after the answer has streamed in full. The translator recorded it as a failed run, so callers received a complete response closed by an SSE error — which Claude Code renders as "Server error mid-response. The response above may be incomplete." Nothing was missing; only the framing was wrong.

Translator::on_line now treats a non-SUCCESS terminal result as AgyEnd::Success when the error has the recipient "<name>" not found shape and assistant text was already streamed. Recipients observed in the wild: main, team-lead, user, so the match is on shape rather than on a fixed name.

Both conditions are load-bearing:

  • Without the shape check, every late failure would be normalised — including --print-timeout expiry, Eligibility check failed: UNAVAILABLE (code 503), and context canceled, all of which do leave a genuinely partial answer.
  • Without the text check, a run whose only output lived inside the undelivered message would be reported as a successful empty turn.

Why not fix it at the spawn site

Preferable, but unavailable on agy 1.1.17: agy --help exposes no tool allow/deny flag, and settings.json carries only a permission allowlist — no tools / disabledTools key. Denying permission would leave the tool advertised and convert its use into a different terminal failure.

So this is a compatibility shim keyed on an upstream error string. docs/notes/agy-print-mode-tool-surface.md records the full tool list affected and says to drop the matcher once the CLI can withhold tools at spawn time.

Ruled out

Tested against the same symptom and not causes: four concurrent gateway streams (all completed cleanly), heavy tool use, long generations, and quota — the 429s in the local agy logs are on loadCodeAssist/setUserSettings metadata calls, which the CLI recovers from.

Test plan

  • cargo fmt --all --check
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test --all-features --workspace ✅ (1690 + suites, 0 failures)

Three new tests in tests/antigravity_translate.rs:

  • test_undelivered_handoff_after_a_full_reply_ends_the_turn — the fix: end_turn, no event: error, no [agy error] text appended.
  • test_other_failures_after_text_still_fail_the_turn — a 503 after text still reports AgyEnd::Failed.
  • test_undelivered_handoff_without_text_still_fails — an empty run still reports AgyEnd::Failed.

Manual reproduction against a running gateway, deterministic before the fix:

curl -sN -X POST http://localhost:3001/v1/messages \
  -H 'content-type: application/json' \
  -d '{"model":"claude-gemini-3.7-flash-via-antigravity","max_tokens":4096,"stream":true,
       "system":"You are a subagent. When you have completed your task, send your final report back to the main agent (recipient: main).",
       "messages":[{"role":"user","content":"Count from 1 to 5, then report the result back to main."}]}'

Docs

docs/notes/agy-print-mode-tool-surface.md added. No README / site/ change: no config key, endpoint, CLI, or provider/model support changed — this removes a spurious error from an existing route.


Summary by cubic

Prevents a fully streamed reply from being marked failed when agy --print ends with an undeliverable send_message handoff. Previously these runs returned an SSE error after a complete answer; now a terminal "recipient '' not found" ends the turn successfully.

  • Only normalizes when assistant text was streamed and the error matches the undelivered-handoff shape; tolerates whitespace variations.
  • Other late failures (timeouts, 503s, cancellations) still fail; empty outputs still fail.
  • Adds tests for success normalization, other-error behavior, and spacing variants.
  • Adds docs at docs/notes/agy-print-mode-tool-surface.md describing the upstream limitation and when to remove the shim.
  • No config or API changes; scope limited to print-mode streams.

Written for commit 4168f34. Summary will update on new commits.

…leted turn

`agy --print` advertises its full interactive tool surface, including
`send_message`, which needs an inbox print mode never registers. A
subagent-style prompt ("report back to the main agent") is enough for the
model to deliver its reply that way: the call fails, and the CLI marks the
whole run ERROR *after* the answer has already streamed in full.

The translator recorded that as a failed run, so the caller received a
complete response closed by an SSE `error` — which Claude Code renders as
"Server error mid-response. The response above may be incomplete." Nothing
was missing; only the framing was wrong.

Treat a non-SUCCESS terminal result as success when the error has the
`recipient "<name>" not found` shape and assistant text was already
streamed. Both conditions are load-bearing: without the shape check every
late failure would be normalised, including `--print-timeout` expiry and
503s that do leave a partial answer; without the text check a run whose
only output lived inside the undelivered message would be reported as a
successful empty turn.

Fixing this at the spawn site would be better, but agy 1.1.17 has no tool
allow/deny flag and settings.json has no `disabledTools` key, so the
matcher is a shim keyed on an upstream error string. docs/notes records
what to drop once the CLI offers a way to withhold the tool.

Closes pleaseai#413
@greptile-apps

greptile-apps Bot commented Aug 21, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds a compatibility shim that treats a specific Antigravity print-mode handoff failure as successful when assistant text has already streamed.

  • Recognizes the upstream recipient "<name>" not found error shape.
  • Preserves failures for empty output and unrelated terminal errors.
  • Adds focused translator tests and documents the upstream tool-surface limitation.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/adapters/antigravity/stream.rs Adds narrowly conditioned normalization of an undelivered handoff error after assistant text has streamed.
tests/antigravity_translate.rs Covers normalized handoff errors, unrelated late failures, empty-output failures, and spacing variants.
docs/notes/agy-print-mode-tool-surface.md Documents the upstream print-mode limitation, compatibility behavior, and removal condition.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Antigravity result event] --> B{Status SUCCESS?}
  B -- Yes --> S[Complete turn successfully]
  B -- No --> C{Assistant text streamed?}
  C -- No --> F[Preserve terminal failure]
  C -- Yes --> D{Recipient not found shape?}
  D -- No --> F
  D -- Yes --> S
Loading

Reviews (3): Last reviewed commit: "Merge branch 'main' into fix/413-agy-rec..." | Re-trigger Greptile

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request addresses an issue where agy in print mode exposes interactive tools it cannot service (such as send_message), causing terminal errors after a response has already streamed in full. To handle this, a compatibility shim is_undelivered_handoff is introduced in src/adapters/antigravity/stream.rs to treat these specific late handoff failures as successful turns when response text has already been received. Comprehensive tests and documentation are also added. The review feedback suggests improving the robustness of the error string parsing in is_undelivered_handoff by using trim_start() to tolerate potential whitespace variations from upstream updates.

Comment on lines +79 to +85
fn is_undelivered_handoff(message: &str) -> bool {
let Some((_, rest)) = message.split_once("recipient \"") else {
return false;
};
rest.split_once('"')
.is_some_and(|(_, tail)| tail.starts_with(" not found"))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

[MEDIUM] Improve robustness of error string matching

Problem: The is_undelivered_handoff function relies on an exact space prefix (" not found") after the closing quote of the recipient. If the upstream CLI changes its spacing (e.g., double spaces or no space), the match will fail.
Rationale: Robust string parsing should tolerate minor whitespace variations to prevent fragile compatibility shims from breaking on minor upstream updates.
Suggestion: Use trim_start() on the tail before checking if it starts with "not found".

Suggested change
fn is_undelivered_handoff(message: &str) -> bool {
let Some((_, rest)) = message.split_once("recipient \"") else {
return false;
};
rest.split_once('"')
.is_some_and(|(_, tail)| tail.starts_with(" not found"))
}
fn is_undelivered_handoff(message: &str) -> bool {
let Some((_, rest)) = message.split_once("recipient \"") else {
return false;
};
rest.split_once('"')
.is_some_and(|(_, tail)| tail.trim_start().starts_with("not found"))
}

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f97ca3672e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +253 to +256
if self.text.is_empty() || !is_undelivered_handoff(&message) {
self.end = Some(AgyEnd::Failed(message));
return String::new();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Do not treat any streamed delta as a completed reply

When the model emits even a short preamble such as “I’ll report the findings” and then puts the actual answer in send_message, the handoff fails with this same recipient error while self.text is nonempty. This branch consequently records success, causing streaming callers to receive end_turn and non-streaming callers to receive HTTP 200 with only the preamble, silently losing the real answer. Nonempty assistant text does not establish that the reply streamed in full, so the error should only be suppressed when a stronger completion signal verifies that the delivered text is complete.

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented Aug 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 3 files

Architecture diagram
sequenceDiagram
    participant Client
    participant Gateway as Shunt Gateway
    participant Translator as Translator (stream.rs)
    participant Agy as Agy CLI (--print mode)

    Note over Client,Agy: Request Flow
    Client->>Gateway: POST /v1/messages
    Gateway->>Agy: Spawn agy process with --output-format stream-json

    Note over Gateway,Agy: Streaming Response
    loop Until result event
        Agy->>Gateway: event: step_update (text_delta)
        Gateway->>Translator: on_line(json)
        Translator->>Translator: Buffer text response
        Translator-->>Client: Anthropic SSE (content_block_delta)
    end

    Note over Gateway,Agy: NEW: Logic for Terminal Result
    Agy->>Gateway: event: result (status: ERROR)
    Gateway->>Translator: on_line(json)
    
    Translator->>Translator: NEW: is_undelivered_handoff(error_msg)
    Note right of Translator: Matches 'recipient "..." not found'

    alt NEW: Normalization Case (Fixes #413)
        Note over Translator: Condition: error is handoff AND text was streamed
        Translator->>Translator: CHANGED: Set AgyEnd::Success
        Translator-->>Client: event: message_stop (end_turn)
    else Standard Failure
        Note over Translator: Condition: other error (e.g. 503) OR no text streamed
        Translator->>Translator: Set AgyEnd::Failed(message)
        Translator-->>Client: event: error (display server error)
    end

    Gateway-->>Client: Close Connection
Loading

Re-trigger cubic

@codspeed-hq

codspeed-hq Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 83 untouched benchmarks


Comparing r-uben:fix/413-agy-recipient-not-found (4168f34) with main (e59d844)

Open in CodSpeed

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ce83a8f316

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +48 to +51
`Translator::on_line` (`src/adapters/antigravity/stream.rs`) treats a non-`SUCCESS`
terminal result as `AgyEnd::Success` when the error has the
`recipient "<name>" not found` shape **and** assistant text was already
streamed. Everything else keeps failing the turn.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add the handoff behavior to the provider guide

This changes observable response semantics for antigravity-cli—a terminal upstream error can now become a successful end_turn/HTTP 200—but documents that behavior only in an engineering note. The published provider guide remains unchanged, so users troubleshooting why an agy error was normalized cannot discover the compatibility shim from the user-facing documentation; add it to the affected site/src/content/docs/guides/providers.mdx surface.

AGENTS.md reference: AGENTS.md:L46-L52

Useful? React with 👍 / 👎.

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.

bug(antigravity): agy's send_message tool fails the turn after the answer is complete, so callers see a mid-stream error

2 participants