Repository navigation
Deferred tool results — let tool handlers suspend a turn and resume via submitToolResult() #1552
Description
Describe the feature or problem you'd like to solve
Custom tool handlers must resolve before the tool.call JSON-RPC response is sent — there's no way to signal "I'll have the result later."
Proposed solution
If tool.call can accept a deferred result, CLI can remain in a suspended state (e.g. further messages are queued) until client submits tool results later.
This enables long-running tools (human-in-the-loop approvals, etc) especially in durable orchestration where the CLI cannot be kept running for long periods of time. Today these scenarios require something like the following workaround:
tool.callcomes in- client returns 'the tool call ABC has been processed; you will be notified when it completes'
- (while waiting for long-running tool) client may destroy and resume CLI session
- client resumes CLI session and sends message
tool call ABC has results; call get_deferred_result(ABC) - CLI calls
get_deferred_result(ABC)so that the result goes through same tool result flow
This adds extra assistant turns / token usage. It would be useful to have something like:
tool.callcomes in- client returns something like
{ 'result': 'deferred' } - (while waiting for long-running tool) client may destroy and resume CLI session
- client resumes CLI session with some new payload
{ 'type': 'submit-deferred-tool-results', 'toolCallId': '...', 'result': { ... } } - CLI immediately interprets the results and continues the session
This would simplify the back and forth and does not rely on LLM intelligently knowing to call get_deferred_result. In a durable/serverless scenario the CLI may be stopped and resumed on a different worker, so it is challenging to hold the tool.call JSON-RPC call open. The deferred result type lets the CLI checkpoint the pending tool call into session state so it survives destroy/resume.
Example prompts or workflows
Sample workflows include human-in-the-loop, webhooks, or any long-running tool call in an environment where we expect the CLI session may be destroyed and resumed during the tool execution (e.g. in durable / serverless environments).
This is pseudocode for example SDK code this might enable:
import { CopilotClient, defineTool } from "@github/copilot-sdk";
const client = new CopilotClient();
// 1. Create session with a deferred-capable tool
const session = await client.createSession({
model: "claude-sonnet-4.5",
tools: [
defineTool("request_approval", {
description: "Request human approval for an action.",
parameters: { type: "object", properties: { reason: { type: "string" } }, required: ["reason"] },
handler: async ({ reason }, invocation) => {
// Kick off external process, pass toolCallId for correlation
await sendSlackApprovalRequest(invocation.toolCallId, reason);
return { resultType: "deferred" };
},
}),
],
});
// 2. Send prompt — LLM calls the tool, gets deferred, turn is parked
await session.send({ prompt: "Get approval to deploy v2.0 to production." });
// 3. Save IDs, tear down — worker can scale to zero
const sessionId = session.sessionId;
await session.destroy();
await client.stop();
// ─── time passes: minutes, hours, days ─────────────────────────────
// Slack webhook fires on a completely different worker/container.
// It has sessionId + toolCallId from the original request.
// 4. New worker resumes the session and submits the result
const client2 = new CopilotClient();
const resumed = await client2.resumeSession(sessionId);
await resumed.submitToolResult(toolCallId, {
textResultForLlm: "Approved by @alice at 3:42 PM",
resultType: "success",
});
// CLI resumes the LLM turn with a proper tool_result → assistant responds
await resumed.destroy();
await client2.stop();Additional context
No response
Activity
karanbirsingh commentedon Feb 20, 2026
I saw this similar incident on SDK repo - github/copilot-sdk#517
patniko commentedon Oct 26, 2026
Thanks for the detailed proposal; this workflow is now supported. Calls to host-provided tools surface as
external_tool.requestedevents and stay pending until the host answers them withsession.tools.handlePendingToolCall(keyed by the event'srequestId). If the session is suspended and later resumed withcontinuePendingWork: true, pending calls from the main agent remain answerable, andsession.tools.listPendingRequests(Copilot CLI v1.0.95 / Copilot SDK v1.0.19 and later) lets a host that never saw the original event discover them. If something is still missing for your durable or serverless scenario on the latest version, feel free to reopen with details.Triaged as part of a Copilot SDK backlog review, with help from GitHub Copilot.