From a47df6a805d45cae2dea08ed9547b59ca581ba62 Mon Sep 17 00:00:00 2001 From: Justin Rich Date: Tue, 26 May 2026 15:55:07 -0700 Subject: [PATCH] fix(automation): create workspace + launch agent in a single relay call (SUPER-783) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "New workspace" automations (the default for scheduled agent runs) failed silently: the workspace would never get created and the agent would never start. The user-visible symptom was an opaque dispatch failure. ## Root cause `dispatchAutomation` made two sequential relay round-trips for the new-workspace path: 1. `workspaces.create` → returns workspace id 2. `agents.run` → spawns the agent against that id Each round-trip independently races the tunnel state on the host side. The first call would usually succeed (workspace gets created in cloud + host), but the second call would frequently land in a moment where the tunnel had just dropped — the relay returned 503 "Host not connected" and the agent never spawned. The cloud wrote the run as `dispatch_failed` even though a workspace had actually been created on the host. ## Fix The host's `workspaces.create` handler already supports launching agents inline via the `agents: []` field (see `packages/host-service/.../workspaces.ts:1025` — `dispatchSugarAgents`). The Superset SDK (`packages/sdk/src/resources/workspaces.ts:41`) already uses this. The dispatch path was the only caller that split create and launch into two trips. Pass `agents: [{ agent, prompt }]` in the same `workspaces.create` call. One relay round-trip instead of two, no second tunnel race, atomic on the host side. The pinned-workspace path (`automation.v2WorkspaceId !== null`) is unchanged — still a single `agents.run` call. --- .../trpc/src/router/automation/dispatch.ts | 65 ++++++++++++------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/packages/trpc/src/router/automation/dispatch.ts b/packages/trpc/src/router/automation/dispatch.ts index 2add6b9dc28..9a961d6f5e5 100644 --- a/packages/trpc/src/router/automation/dispatch.ts +++ b/packages/trpc/src/router/automation/dispatch.ts @@ -101,29 +101,30 @@ export async function dispatchAutomation( host.machineId, ); + let result: AgentRunResult; if (automation.v2WorkspaceId) { workspaceId = automation.v2WorkspaceId; + result = await runAgentOnHost({ + relayUrl, + hostId: routingKey, + jwt, + workspaceId, + agent: automation.agent, + prompt: automation.prompt, + }); } else { - const created = await createWorkspaceOnHost({ + const created = await createWorkspaceWithAgentOnHost({ relayUrl, hostId: routingKey, jwt, projectId: automation.v2ProjectId, automation, - runId: run.id, + agent: { agent: automation.agent, prompt: automation.prompt }, }); workspaceId = created.workspaceId; + result = created.agent; } - const result = await runAgentOnHost({ - relayUrl, - hostId: routingKey, - jwt, - workspaceId, - agent: automation.agent, - prompt: automation.prompt, - }); - await dbWs .update(automationRuns) .set({ @@ -224,14 +225,14 @@ async function recordSkipped( return row; } -async function createWorkspaceOnHost(args: { +async function createWorkspaceWithAgentOnHost(args: { relayUrl: string; hostId: string; jwt: string; projectId: string; automation: SelectAutomation; - runId: string; -}): Promise<{ workspaceId: string; branchName: string }> { + agent: { agent: string; prompt: string }; +}): Promise<{ workspaceId: string; agent: AgentRunResult }> { // Full-precision timestamp keeps branch names readable AND collision-free // for anything coarser than 1 second. // e.g. "2026-04-19-17-30-00" @@ -244,22 +245,20 @@ async function createWorkspaceOnHost(args: { const branchName = deduplicateBranchName(candidateBranch, []); const workspaceName = args.automation.name.slice(0, 100); + type AgentLaunchResult = + | ({ ok: true } & AgentRunResult) + | { ok: false; error: string }; + const result = await relayMutation< { projectId: string; name: string; branch: string; + agents: Array<{ agent: string; prompt: string }>; }, { - workspace: { - id: string; - projectId: string; - name: string; - branch: string; - }; - terminals: Array<{ terminalId: string; label?: string }>; - agents: Array; - alreadyExists: boolean; + workspace: { id: string }; + agents: AgentLaunchResult[]; } >( { @@ -275,10 +274,28 @@ async function createWorkspaceOnHost(args: { projectId: args.projectId, name: workspaceName, branch: branchName, + agents: [args.agent], }, ); - return { workspaceId: result.workspace.id, branchName }; + const agentResult = result.agents[0]; + if (!agentResult) { + throw new Error( + `workspace ${result.workspace.id} created but host returned no agent result`, + ); + } + if (!agentResult.ok) { + throw new Error(`agent launch failed: ${agentResult.error}`); + } + + return { + workspaceId: result.workspace.id, + agent: { + kind: agentResult.kind, + sessionId: agentResult.sessionId, + label: agentResult.label, + }, + }; } async function runAgentOnHost(args: {