ProxiedCopilotRuntimeAgent can retain stale runtimeMode when /info changes Intelligence → SSE
Problem
ProxiedCopilotRuntimeAgent can retain a stale runtimeMode after the runtime's /info response changes from intelligence to sse, causing the client to send a run/connect request down the Intelligence delegate path against a runtime that is actually serving plain SSE. The Intelligence delegate then calls await response.json() on a text/event-stream body and throws.
Where
packages/core/src/core/agent-registry.ts, the runtime-info reconciliation loop (currently guarded by canReuseRuntimeAgent):
const existing = Object.prototype.hasOwnProperty.call(this.remoteAgents, id)
? this.remoteAgents[id]
: undefined;
if (
existing instanceof ProxiedCopilotRuntimeAgent &&
this.canReuseRuntimeAgent(existing, runtimeUrl, this._runtimeTransport)
) {
this.applyHeadersToAgent(existing);
this.applyCredentialsToAgent(existing);
this.applyRuntimeFetchToAgent(existing);
return [id, existing]; // <-- runtimeMode / intelligence are never refreshed here
}private canReuseRuntimeAgent(agent, runtimeUrl, transport): boolean {
const connection = this.remoteAgentConnections.get(agent);
return (
connection?.runtimeUrl === runtimeUrl.replace(/\/$/, "") &&
connection.transport === transport
);
}canReuseRuntimeAgent compares runtimeUrl and transport only. It does not compare runtimeMode. Only the "create a new agent" branch a few lines later sets runtimeMode: runtimeInfoResponse.mode ?? RUNTIME_MODE_SSE. So an already-existing ProxiedCopilotRuntimeAgent instance keeps whatever runtimeMode it was originally constructed with for its entire lifetime, even after subsequent /info calls report a different mode.
ProxiedCopilotRuntimeAgent.run()/connectAgent() branch on the agent's own (possibly stale) runtimeMode:
if (this.runtimeMode === RUNTIME_MODE_INTELLIGENCE) return this.#runViaDelegate(input);
// else: plain HTTP/SSEMinimal reproduction (conceptual)
- Runtime initially reports
mode: "intelligence"(e.g.CPK_INTELLIGENCE_API_KEYconfigured).CopilotKitCoreconnects;AgentRegistrycreates aProxiedCopilotRuntimeAgentfor"default"withruntimeMode: "intelligence". - Runtime is reconfigured to
mode: "sse"(Intelligence disabled) —runtimeUrlandtransportunchanged. CopilotKitCore(or the app) re-fetches/infoand reconciles remote agents.canReuseRuntimeAgentreturnstrue(URL/transport match) → the existing agent instance is reused as-is.agentRegistry._runtimeMode(the core-level value) is correctly updated to"sse", butexistingAgent.runtimeModeis not.- Any subsequent
run()/connectAgent()call on that agent instance still takes the Intelligence delegate branch. POST /agent/:id/runhits the actual (SSE-mode) runtime, which returnsContent-Type: text/event-streamwith a body startingdata: {"type":"RUN_STARTED",...}.- The Intelligence delegate's
requestJoinCredentials$doesawait response.json()on that SSE body and throwsUnexpected token 'd', "data: {...}" is not valid JSON.
Confirmed against
- Installed
@copilotkit/[email protected](dist/index.mjs:1382-1409): identical reuse-without-refresh pattern. - Current
main(packages/core/src/core/agent-registry.ts, fetched directly): the reuse guard is now the namedcanReuseRuntimeAgenthelper, but it still only comparesruntimeUrl/transport—runtimeModeis still not part of the check, and no mode-based invalidation exists elsewhere in the file.
Expected behavior
A runtimeMode change reported by /info should invalidate/recreate the affected proxy agent(s) (or otherwise synchronize the existing instance's mode-specific state: runtimeMode, intelligence, and any delegate it may have created), the same way canReuseRuntimeAgent already invalidates on a runtimeUrl/transport change.
Real-world impact
Any browser tab left open across a runtime configuration change from Intelligence to SSE (a redeploy, an env var change, a rollback) gets permanently stuck attempting the Intelligence delegate path for the rest of that page's lifetime, with no visible indication beyond a response.json() parse error on send. A full page reload is currently the only recovery.
Related but not equivalent
#7069 addresses the delegate not honoring single-route transport configuration — a different bug in the same subsystem (delegate/transport handling), not this mode-staleness issue. #6469 previously fixed the delegate never refreshing headers after creation — same "cached once, never refreshed" pattern, but for headers rather than mode.
Source: CopilotKit/CopilotKit