feat: allow user message when turn is running - #778
sr07asthana wants to merge 27 commits into
Conversation
🦋 Changeset detectedLatest commit: 6b3d073 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # python/trueforge_sdk/.fern/metadata.json
| } | ||
|
|
||
| public async *send(messages: AgentThreadSendBatch): AsyncGenerator<AgentThreadAppendContext, void, unknown> { | ||
| if (messages.length > 0 && !isUserToolApprovalOrResponseBatch(messages)) { |
There was a problem hiding this comment.
We should have a separate CancelTurn method that does all this ( that we later reuse in cancel API as well)
We shouldn't add such conditional handling directly in orchestrator.
Perhaps we can model it as a "send cancel" event to orchestrator? @chiragjn thoughts here?
There was a problem hiding this comment.
cancel api is separate in our case right? we decided cancel and closing these calls will be two separate processess right?
The previous turn is already frozen. You start a new turn with a real user message while a sub-agent (or approval) was still open in the snapshot.
Then this orchestrator flow runs, in send() of that new turn
We discussed yesterday that cancel itself should not close all pending subagent/pending actions. It should be done when next user message arrives. cc @chiragjn
There was a problem hiding this comment.
I did not get this.
A user message creates a new turn, which should cancel the previous turn. So sending a user message should have the exact same behaviour as say clicking cancel turn button.
This was what we discussed.
The problem here is that we already mark the turn as done. I believe this change will make more sense once we pause the turn instead.
Currently the only scenario we need to handle is that the turn is running, and we send a user message - which interrupts the turn.
There was a problem hiding this comment.
freeze/cancel-button stops the old turn, this send() path stops leftover threads/tools on the next turn.
Resolve the session test imports while preserving steering validation and main's active executor coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
| role: 'tool', | ||
| tool_call_id: toolCallId, | ||
| content: DUMMY_TOOL_MESSAGE_CONTENT, | ||
| content, |
There was a problem hiding this comment.
Cancelled tools never reach event log
Medium Severity
A user message now synthesizes tool responses for pending approvals, client-side tools, and sub-agents, but those closures are written only into LLM context with output: []. createTurn drains send() via collectContextAppends, which ignores output, and stream() never sees the append. Clients that already received HITL required-actions therefore get no TOOL_RESPONSE in the session event log or live stream, so the UI can keep showing those calls as open.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 7d9edc6. Configure here.
There was a problem hiding this comment.
This is accepted, for now we have decided it will be clients responsibility to handle previous pending tool calls, user actions and threads in case a new user message is sent
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 64133f2. Configure here.


Summary
Closes #
Changes
How was this tested?
Checklist
pnpm build,pnpm test,pnpm typecheck,pnpm lint:ci, andpnpm format:checkpass locallypackages/trueforge-sdk,python/trueforge_sdk,.github/fern/openapi/openapi.json,docs/openapi.json) — fork PRs omit SDK regen; maintainers regenerate after merge.env.exampleupdated if configuration or behavior changedNote
Medium Risk
Changes core turn steering, LLM context repair, sub-agent lifecycle, and durable thread snapshots—behavior shifts when users interrupt pending work, though covered by new contract and orchestration tests.
Overview
Users can start a new turn with a user message while approvals, client-side tool prompts, or in-flight sub-agent threads are still pending, instead of being blocked or forced to wait for resolution.
Interrupt vs resume:
AgentThread.sendpicksinterruptwhen the batch includes a user message andresumeotherwise. Before validation,buildOpenToolCallClosureappends synthetic tool responses for open calls on the last assistant message—cancelled copy on interrupt (including approval, client-side, and sub-agent calls) vs dangling/retry copy on resume (still skipping pending-user-action and thread-creation calls on resume). Pending-approval checks no longer reject user messages; empty sends while awaiting input still fail.Orchestration & persistence: Non-approval sends drop child threads from the in-memory orchestrator (metrics retained). Session
createTurntreatscapability_statesas the authoritative live thread id set—previous threads omitted are not copied into the next snapshot (in-memory, Postgres, and SQLite). RootAGENT_DONEwitherrorno longer surfaces as turn output in terminal field extraction.Tests cover steering over pending approval, sub-agent drop vs empty resume, store thread omission, and closure modes.
Reviewed by Cursor Bugbot for commit 6b3d073. Bugbot is set up for automated code reviews on this repo. Configure here.