Context
When code_mode is enabled, codemode.Wrap() collapses all toolsets into one composite (pkg/teamloader/teamloader.go:932). That composite returns PartialStartError while at least one inner toolset is healthy. PR #4062 deliberately exempts partial starts from the backoff gate so the healthy subset stays listed, but this means the composite's per-turn retry of the degraded subset (e.g. a RAG toolset hitting 429 rate limits) is not paced by the gate — the same burst behaviour described in #4060 can still occur for code-mode agents.
Known path
codeModeTool.Start → partial failure → StartableToolSet.startLocked → s.resetStartBackoff() → next turn: full burst again.
Proposed fix
Arm the gate when a PartialStartError's aggregated cause is retryable (the gate already supports s.started = true, so the healthy subset keeps listing). This requires a focused test that a partial-start with a retryable inner cause paces the next TryStart while a non-retryable partial does not.
Context
When
code_modeis enabled,codemode.Wrap()collapses all toolsets into one composite (pkg/teamloader/teamloader.go:932). That composite returnsPartialStartErrorwhile at least one inner toolset is healthy. PR #4062 deliberately exempts partial starts from the backoff gate so the healthy subset stays listed, but this means the composite's per-turn retry of the degraded subset (e.g. a RAG toolset hitting 429 rate limits) is not paced by the gate — the same burst behaviour described in #4060 can still occur for code-mode agents.Known path
codeModeTool.Start→ partial failure →StartableToolSet.startLocked→s.resetStartBackoff()→ next turn: full burst again.Proposed fix
Arm the gate when a
PartialStartError's aggregated cause is retryable (the gate already supportss.started = true, so the healthy subset keeps listing). This requires a focused test that a partial-start with a retryable inner cause paces the next TryStart while a non-retryable partial does not.