Repository navigation
feat(mothership): answer a long workflow tool run still running, and stop detached runs on Stop - #9507
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
b1614a3 to
2be49a8
Compare
There was a problem hiding this comment.
Review completed against the latest diff
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
[Medium impact] The PR appears safe to merge; no new actionable issues were found. SummaryWorkflow tools can answer long runs with their execution IDs while those runs continue. Stop also finds workflow runs left running by earlier calls.
The previous threads were rechecked. The helper changes and replacement tests address their concerns; no new findings were accepted.
|
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 24 files
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
ab242a1 to
2b23efe
Compare
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 24 files
You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
Turn on auto-fix | Re-trigger cubic
… worker's wait A run_workflow* call held the turn until its run settled, up to the execution limit, while a synchronous workflows run can already detach after --wait-timeout (#9464). Sim now declares workflow-tool-wait-timeout and honors the waitTimeoutMs a workflow tool frame names (synced from the mothership contract): - Server-run call: the handler gives the run its execution id up front and, once the wait passes, answers {status: running, executionId, workflowId}. The run goes on in-process; the tool execution keeps holding it, so its lease and a Stop through the call's abort signal still reach it. - Browser-run call: once the wait passes and a browser holds the call's execution claim, the call is settled background, the detach a browser sends on pagehide. The browser's run goes on, still bound to the call, so a Stop cancels it. - A background workflow completion now carries status: running beside its ids, the shape the worker waits on. A frame without waitTimeoutMs, or one with a malformed value, holds the call as before.
…ast a call A synchronous workflows run the worker waits on in slices detaches after its first --wait-timeout (#9464, mothership#872), and from then on no call's abort signal reaches it. Stop delivery cancelled only browser workflow runs bound to open calls, so a Stop mid-slice ended the chat while the draft run went on to completion. Driven through the real controller and the Stop route (live case in the next commit), the run was still running a minute after the Stop. Stop delivery now also cancels the runs this chat run's calls started and left running: a workflows run the CLI answered still running (exit 4 and its run record), and a workflow tool answered still running. A run the chat only waited on (workflows runs wait) is not one, since something else started it. The cancel is the same in-process abort and durable cancel flag a browser run gets; a detached run registers its in-process aborter. Autopsy: the slice tests on both sides covered waits that settle, lapse or lose an answer, and none stopped the chat mid-slice; the Stop tests seeded only open browser-run calls. stop-delivery.test.ts and the live declared-stop case now cover it.
…slice through the real controller Two live cases beside the sliced draft run (opt-in, SIM_HELPERS_SMOKE and MSHIP_WORKER_ROOT): - tool: run_workflow on a Sim declaring workflow-tool-wait-timeout answers its 500 s run still running after 240 s, the worker waits on it as run-wait slices and reads it back, and the model sees the settled run's output. - declared-stop: the Stop button's request while the worker waits on a detached draft run ends the turn and cancels the run. The harness now marks the turn's tool execution and billing as the chat route does, registers and wires the turn's controller as lifecycle/start.ts does, gives the tool case a real AES key to seal its provenance, and keeps logged errors' messages in the evidence.
… without a JSON round trip
…un is dispatched Review of the wait: - A run still in authorization or admission when the wait passes is not answered running under an id nothing has used yet; the call waits until the run is handed to the executor (onDispatch) or settles. - The browser-run detacher sleeps interruptibly and stops on either outcome of its waiter, so no derived promise rejects unhandled. - The live case takes its AES key from generateRandomHex. - The mock-only Stop delivery test is dropped: the live declared-stop case drives the real Stop route, storage and cancellation, and fails without the fix (the run stays running); detachedWorkflowExecutionId keeps its unit test.
…real Postgres and Redis The browser-run detach and the detached-run read now run through the real claim, settlement, published confirmation, waiter and stored tool rows (workflow-client-settlement.integration.ts), replacing unit tests that asserted only on mocks.
…nterruptible sleep
…un is readable A run past the call's wait was answered running as soon as it was handed to the executor, before the executor wrote its run log, so an immediate workflows runs wait could find no run. The call now answers running only once the run's log exists, re-reading it each second until then or until the run settles; the dispatch hook is gone.
… exists, and never discard a run that settled meanwhile
3e10a62 to
08c08f0
Compare
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
No issues found across 24 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
Turn on auto-fix | Re-trigger cubic
Summary
A
run_workflow,run_workflow_until_block,run_from_blockorrun_blockcall held the chat turn until its run settled, up to the execution limit. A synchronousworkflows runfrom the CLI can already detach after--wait-timeout(#9464), and the worker waits on it in 240 s slices (simstudioai/mothership#872). This PR does the same for the workflow tools, and fixes Stop for both kinds of detached run.Workflow tools answer a long run still running. Sim now declares the
workflow-tool-wait-timeoutclient capability. It honors thewaitTimeoutMsthat the worker names on a workflow tool frame. The contract is synced from the mothershipprotocol.ts.{ status: "running", executionId, workflowId }. A run still in preflight or startup is answered once it becomes readable, or with however it settles. The run goes on in-process. The tool execution keeps holding it (retainSandboxExecution), so the call's lease and Stop still reach it until it settles.background. This is the same detach a browser sends onpagehide. The browser's run goes on, still bound to the call.status: "running"next to its ids. The worker waits on that shape.waitTimeoutMs, or with a malformed value, holds the call as before.Fix: Stop now stops a detached run. Once a sliced
workflows rundetaches, no call's abort signal reaches it. Stop delivery cancelled only browser runs bound to open calls. So a Stop in the middle of a slice ended the chat but left the draft run going until it finished.Stop now also cancels the runs that this chat run's calls started and left running:
workflows runthat the CLI answered with exit 4 and its run record;A run the chat only waited on (
workflows runs wait) is excluded, because something else started it. The cancel is the same in-process abort plus durable cancel flag that a browser run gets.Companion: simstudioai/mothership#881
Type of Change
Testing
Unit tests:
mutations.test.ts:handlers.test.ts: the frame's wait reaches only workflow tools, and a malformed wait is ignored.repository.test.ts: which calls count as having started a detached run. This includes a run behind global flags. A wait on another run never counts.payload.test.ts: the capability is declared.client.test.tsand the confirm-route test: the background shape now includesstatus.Integration tests, against real Postgres and Redis (
workflow-client-settlement.integration.ts,bun run test:integration workflow-client-settlement, 15/15):run_workflowpast a 500 ms wait goes through the real claim, settlement, published confirmation and waiter. It is answeredbackgroundwith{ workflowId, executionId, status: running }. Its row stays delivered, claimed and unsettled, so Stop still finds it.getDetachedWorkflowExecutionsover stored tool rows returns the CLI and tool runs that detached, and leaves out a wait on another run and a settled tool.Commands:
vitest run lib/mothership app/api/copilot lib/organizations lib/workflows/application: 274 files, 3649 passed.tsc --noEmit(apps/sim): clean.Live, through the real controller (
saved-run-read.live.test.ts; realrunCopilotLifecycle, sim_cli handler, embedded CLI, v2 execute and runs routes, executor; scripted model; worker = the companion branch). The draft is Start, then Wait 300 s, then Wait 200 s, then a Function returning{invoice: 42, settled: "after-waits"}.workflows run --wait-timeout 240(exit 4, 240.1 s), thenruns wait(exit 4, 240.05 s), thenruns wait(exit 0, 29.1 s), thenruns get --include-output(0.02 s). Turn 509.7 s. The model sawcompletedwith the output.run_workflow)executionId. Thenruns wait(exit 4, 240.0 s), thenruns wait(exit 0, 29.1 s), then the read-back (0.03 s). Turn 509.6 s. The model sawstatus: completedandoutput.result = {invoice: 42, settled: "after-waits"}. The run's trigger iscopilot. Its call's lease held until the run ended (500.4 s)./api/copilot/chat/abort) arrived 6 s into the firstruns waitslice. The turn ended about 2 s later, but the run was stillrunninga minute after.cancelled26 ms after the Stop, and the turn ended at 246.7 s.Reviewers, please focus on two places:
answerWithinWaitinmutations.ts: what holds the run after the early answer, and its abort signal.detachedWorkflowExecutionIdinrepository.ts: which calls count as having started a run.Known limit: when a turn's provenance registry is already incomplete, results are withheld, and the still-running answer is withheld with them. Only the execution id survives, in
effect, so the worker cannot recognize it and the model sees a withheld result naming the execution. A server re-run after a takeover does not keep the frame's wait and holds the call as before.Checklist