Skip to content

[Bug] Antigravity Google lane: post-fold remaining functionCall missing thoughtSignature → sticky 400 (gemini-4-argon-medium) #6079

Open

Description

@GGJdevelope

Description *

Same post-fold Google 400 as #5252, reproduced on Antigravity (agy) rather than DSH/Vertex.

After one successful compress, every later request of the session is rejected by Google until the lane is taken out of bili. The session cannot be repaired in place: thoughtSignature is gone from remaining functionCall parts, and it cannot be invented.

This client has no plugin seam and does not rewrite conversation history around the fold splice (docs/clients.md: Antigravity is launcher-only, closed Go language_server). That makes a pure “client resent unsigned parts” explanation (#5277) incomplete for this lane.

Repro

  1. Route Antigravity through bili by setting
    CLOUD_CODE_URL / AGY_ACP_CCPA_BASE_URL to
    http://127.0.0.1:8787/bili/https://daily-cloudcode-pa.googleapis.com.
  2. Use gemini-4-argon-medium with tools (default_api:run_command).
  3. Let the session grow until T1 injects compress.
  4. Fold applies (outcome applied, ~46k tokens saved).
  5. Immediate re-request 400s. Every retry 400s.

What we expected

A remaining functionCall part after fold either:

  1. keeps its original thoughtSignature verbatim, or
  2. is folded away entirely (no bare functionCall left on the wire), or
  3. is dropped with an explicit local warn instead of forwarding a body Google will 400.

Observed

  • Fold m00001–m00049 on session 94fa0cbc-6e4c-4568-a975-b6d71296ee15.
  • 67 msgs → 24 msgs, shrink ~70% (~46059/66022).
  • Next processTurn is immediately followed by:
    Function call is missing a thought_signature in functionCall parts / default_api:run_command / position 18.
  • Retry ~12 min later: same 400, position 2.
  • invariant-judge on the 400 request: clean fold verdict: matched 2/2, exempt carrier=1.
  • Not overflow: last settled input ~55294 vs window 1048576. bili’s overflow self-heal cannot engage (#4452 log).
  • Earlier session 2c3dcaa6-d3ae-406e-ba55-b425d84cfc76 hit the same 400 on this lane.

Related: #5252 (same 400 after fold, DSH + Vertex), #5277 (audit/warn only, no wire change), #3158 (standalone SSE signature parts — different stage).

Local workaround we applied: stop sending this host through bili (CLOUD_CODE_URL back to https://daily-cloudcode-pa.googleapis.com, plus per-route passthrough). Grok/other lanes stay compressed.


摘要

Google 车道(Antigravity / daily-cloudcode-pa.googleapis.com)一次正常折叠后,保留下来的 functionCall 丢了 thoughtSignature,下一轮起每请求都 400,会话无法继续。Antigravity 没有插件缝、不会在 fold splice 后改写历史,所以不能只归因于客户端重放丢签名。#5252 的同族问题;#5277 目前只打日志。


bili version *

0.1.213

Usage mode *

Proxy mode (client manually points its base URL at bili)

OpenBot custom Antigravity wrapper rewrote CLOUD_CODE_URL / AGY_ACP_CCPA_BASE_URL to /bili/https://daily-cloudcode-pa.googleapis.com. Not bili antigravity launcher, same URL rewrite.

Client and version *

Antigravity (agy) 1.4.5 (agy_acp_server.par, darwin-arm64). No native bili plugin.

Operating system *

macOS (darwin-arm64)

Upstream and model *

https://daily-cloudcode-pa.googleapis.com/v1internal:streamGenerateContent · gemini-4-argon-medium · declared window 1048576

Compression-related config (optional)

Default compress. No per-provider override at the time of the 400 (empty providers block). Later we set:

{
  "providers": {
    "https://daily-cloudcode-pa.googleapis.com": { "passthrough": true }
  }
}

Log excerpt

Times are UTC from ~/.local/state/billion-context/bili.log. Secrets stripped.

2026-11-10T10:41:53.183 processTurn: 67 msgs, renderTags=text-only, 7 text tagged, 1 tool tagged
2026-11-10T10:41:53.184 nudge INJECT T1: usage=9% (97349/1048576), growth=54647/22500, pendingT1=50785/50000 model=gemini-4-argon-medium
2026-11-10T10:41:53.188 forward POST → https://daily-cloudcode-pa.googleapis.com/v1internal:streamGenerateContent
2026-11-10T10:42:21.017 [acp-usage] round 1 input=101353 cached=17807 (cache hit 18%)
2026-11-10T10:42:21.040 [acp-proxy: compress requested 1 range(s): m00001–m00049]
2026-11-10T10:42:21.045 [acp-compress-obs] shrink 70% this-fold (~46059/66022 tok) · postCtx≈19878
2026-11-10T10:42:21.049 [Compressed m00001–m00049 → 1 block(s), ~46059 tokens saved.]
2026-11-10T10:42:21.051 [acp-loop] round 1: re-request view refreshed to post-compress fold
2026-11-10T10:42:21.656 processTurn: 24 msgs, renderTags=text-only, 5 text tagged, 0 tool tagged
2026-11-10T10:42:21.660 request: inbound=380.7KiB, outbound=272.6KiB
2026-11-10T10:42:21.669 [invariant-judge/main] clean fold verdict: matched 2/2, exempt carrier=1 reasoning=0 crush=0 ccr=0 truncate=0
2026-11-10T10:42:22.777 ← upstream 400: Function call is missing a thought_signature in functionCall parts. … function call `default_api:run_command` , position 18.

2026-11-10T10:54:53.620 processTurn: 25 msgs …
2026-11-10T10:54:53.640 [invariant-judge/main] clean fold verdict: matched 2/2, exempt carrier=1
2026-11-10T10:54:54.816 ← upstream 400: … function call `default_api:run_command` , position 2.

Can provide inbound/outbound dumps for this session if needed (ACP_DUMP_BODY / BILI_DUMP_4XX).

Activity

  1. ranxianglei commented on Nov 10, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    查重结论:不重复,且已有在途修复 PR,我未另开并行 PR。


    根因(我独立读旧代码验证过,成立):

    旧 filterRealCallChunk(src/loop/adapter-google.ts)用 parts.filter((_,i)=>keep[i]) 逐字丢弃 proxy part、原样保留 real part。Gemini 只对一轮里第一个 functionCall part 盖 thoughtSignature;当那个带签名的第一个调用恰好是会被服务端执行并剥离的 proxy 工具(compress),它就丢了,而幸存的首个真实调用没有签名 → 被原样转发给客户端 → 客户端把它记成「无签名的 leading functionCall」→ 下一轮回放 → Gemini 400。

    这条因果链精确解释了报告者的证据:折叠前一直 200(还没出现过「[带签名的 proxy + 真实]」并行轮),一次 compress 后每轮必 400,且点名的 run_command 正是与触发折叠那次 compress 并行的真实工具调用;会话无法就地恢复(签名已被客户端记成缺失,无法凭空补回)。
    (说明:我最初在 kernel 的折叠重建路径上做的探针复现不出来,正是因为 bug 不在 processTurn/coreToGoogle 折叠重建层,而在流式剥离/结算这一层——#6103 修对了地方。)

    修复评估(#6103,三处出站面,都在源头防中毒):

    1. src/loop/adapter-google.ts:settleToolCalls 计算出 promoteSig(首个真实调用无签名 && 被剥离的 proxy 带签名时),filterRealCallChunk 把该签名提升到对应 part 上(仅当目标本就无签名,绝不覆盖)。无 proxy、无提升时仍返回 "keep"(逐字节原样,保前缀缓存锚点)。
    2. src/stream-google.ts:非流式 JSON 路径同样捕获被剥离 proxy 的签名并挂到首个幸存真实 fc;纯 proxy 轮则挂到合成 note 文本 part。
    3. src/loop/core.ts:runCompressLoop 把本轮 proxy 签名传播给缺签名的并列 proxy tc 记录(仅 google)。
    4. PROXY_TOOL_SET 扩充:+acp_cache/acp_retrieve/acp_rule/acp_read_original_image。其中 acp_read_original_image 是干净收益(isProxyToolFor 恒真,此前在 Google 车道会泄漏给客户端,Egress image downscale: shrink big images before sending to the model, originals stay local and self-readable | 重做图片降采样(简化版):发模型前压大图、原图留本地不动、模型被告知尺寸后可自读原图 #3400 本意就是别转发)。

    测试: 6 个新用例、双向断言(符合 Gate Discipline #3617),含负向控制(真实调用自带签名绝不被覆盖)和最关键的一条回归锚点——runCompressLoop + 下一轮 googleToCore→coreToGoogle 回放后断言 surviving write_file 仍带签名(正是能钉住本 bug 的那条)。测试有牙。

    CI(镜像缓存,实时): 30 项,failed 0;static-gates/gate/mysql-contract 绿,test 分片在跑,e2e 待 gate。目前无失败。


    我提的风险点(建议合并前确认第 1 条,其余非阻塞):

    1. [唯一建议合并前确认] stream-google.ts 的 notePart 分支:纯 proxy 轮(模型只发 compress、没有真实工具/文本——很常见)时,把被剥离 compress 的 thoughtSignature 注入到合成的普通文本 note part 上。「真实 Gemini 是否接受非 reasoning 的普通 model 文本 part 携带 thoughtSignature」在本环境无法实测(单测走 mock)。若被拒,纯折叠轮可能新增 400,影响面大。建议:对真实端点快速确认一次,或收窄该分支(只在有真实 fc 幸存时提升签名,去掉对文本 part 的注入)。
    2. [低危 nit] PROXY_TOOL_SET 与 isProxyToolFor 条件不一致:acp_retrieve/acp_rule 现在被硬编码进 adapter-google 的 PROXY_TOOL_SET(无条件当 proxy 剥离),但 isProxyToolFor(src/absorb.ts:49)只在 CCR/rules 开启时才把它们当 proxy。特性关闭 + 模型幻觉调用 → 结算层剥离、执行层不执行 → 静默丢弃(旧行为是泄漏给客户端)。仅幻觉可达,低风险;更干净的做法是让两处条件对齐。
    3. [可接受] core.ts 把同一个签名复制到多个并列 proxy tc 记录——语义上最不确定,但防御性 + 仅 google,且有测试fix: post-merge review fixes + per-route context window + XDG config #6 兜底。

    判定:根因成立、修法在正确层次(源头防中毒)、测试扎实、CI 未见失败 → 建议可合并,合并前请确认风险点 1。我不合并(人工权限)。
    注意:该修复防止的是新会话中毒;报告者当前已中毒的会话仍然坏着,需重启/摘出该 host(与 #5252 一致,无法就地恢复)。


    结构化八问

    1. 是什么问题:Google(Gemini native/Vertex)车道上,长会话第一次折叠后,幸存的 functionCall 丢失 thoughtSignature,导致之后每一轮请求都 400,会话彻底不可用直到重启。
    2. 什么原因导致的:Gemini 只对一轮里第一个 functionCall 盖签名;当那个带签名的首个调用是被服务端执行并剥离的 bili proxy 工具(compress),剥离后幸存的首个真实调用没有签名,还被原样转发给客户端记进历史 → 下一轮回放必 400。根在流式剥离/结算层,不在折叠重建层。
    3. 影响范围与严重程度:所有 Google 车道、模型会把 bili proxy 工具与真实工具调用在同一轮并行发出、且带签名的 proxy 调用排在首位的场景。一旦触发,该会话每轮必 400、核心承诺失灵(长会话一折叠就死)——p1。
    4. 怎么修复的:在源头做「签名提升」——被剥离的 proxy 调用所带的 thoughtSignature,提升到该轮首个原本无签名的真实 functionCall 上(绝不覆盖已有签名),覆盖流式结算、非流式 JSON、re-request 循环重建三处出站面,避免客户端把无签名调用记进历史。
    5. 是否有回归问题:审过的既有行为——纯真实调用轮返回 keep 逐字节不变(保持);真实调用自带签名不被兄弟 proxy 覆盖(负向控制钉住,保持);全 proxy 轮整块丢弃行为不变(保持);插件模式因提升逻辑自门控(仅在实际发生剥离时触发)不受影响。潜在漂移两处:①notePart 向普通文本 part 注签名(风险点1,可能改变既有出站字节/新增 400);②PROXY_TOOL_SET 扩大使四个 ACP 工具在 Google 结算层被当 proxy 处理(风险点2,低危)。
    6. 是否破坏缓存:不破坏。无 proxy、无提升时 filterRealCallChunk 仍返回 "keep"(逐字节原样,前缀缓存锚点不动);只有真正需要剥离/提升时才重写。唯一额外改字节的点是 notePart 注签名(见风险点1)。
    7. 是否改了设计方向:不变。仍在既有三层出站面内做签名提升,未引入新架构/新数据流/新协议头。
    8. 是否引入了新的配置:无新 env/config/默认值。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions