Skip to content

feat(sdk): surface a parked agent run to Head Start callers - #4791

Open
0ski wants to merge 1 commit into
oskar/feat-chat-agent-version-skew-protectionfrom
oskar/feat-headstart-pending-version
Open

feat(sdk): surface a parked agent run to Head Start callers#4791
0ski wants to merge 1 commit into
oskar/feat-chat-agent-version-skew-protectionfrom
oskar/feat-headstart-pending-version

Conversation

@0ski

@0ski 0ski commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

A Head Start turn runs across two processes: step 1 in your warm server, step 2 in the agent run. When that run is pinned to a deployment that has not landed yet, it parks, so step 1 streams normally and step 2 arrives late with nothing saying why. pendingVersion now reaches every Head Start shape.

The transport learns it from the headStart POST and emits the existing event with a new head-start source:

const transport = useTriggerChatTransport<typeof myChat>({
  task: "my-chat",
  headStart: "/api/chat",
  accessToken: ({ chatId }) => mintChatAccessToken(chatId),
  startSession: ({ chatId, clientData }) => startChatSession({ chatId, clientData }),
  onEvent: (event) => {
    if (event.type === "run-pending-version") setDeploying(true);
    if (event.type === "first-chunk") setDeploying(false);
  },
});

The detached flow has no open browser connection to emit into, so it returns the flag:

const { chatId, pendingVersion, completion } = await chat.startHeadStart({ agentId, chatId, messages, run });
after(completion);
return Response.json({ chatId, deploying: pendingVersion });

chat.handover's session handle exposes it too, for callers that build their own response.

Design

The handler sets X-Trigger-Chat-Pending-Version: 1 only when the run is parked, next to the X-Trigger-Chat-* headers the transport already reads to hydrate. A chat that is not pinned, or is pinned to a live deployment, gets exactly the response it got before.

Nothing about the wait itself changes, and it is gentler than it looks. The handover signal is written to session.in, which is durable; a fresh run subscribes from the start of that stream rather than the live tail; and the agent's idleTimeoutInSeconds is consumed inside the run at turn 0, so a parked run does not burn its idle budget while parked. A parked turn resumes at step 2 once the deployment lands.

Stacked on #4789, which adds the session pin and the run-pending-version event this extends.

A Head Start turn splits across two processes: step 1 runs in the warm
server, step 2 in the agent run. When the run is pinned to a deployment
that has not landed yet it parks, so step 1 streams normally and step 2
arrives late, with nothing saying why.

`pendingVersion` now reaches all three Head Start shapes:

- the transport emits `run-pending-version` with a new `head-start`
  source, read from an `X-Trigger-Chat-Pending-Version` header the
  handler sets only when parked
- `chat.startHeadStart` returns it, for the detached flow that has no
  open browser connection to emit into
- the `chat.handover` session handle exposes it, for callers that build
  their own response

Nothing about the wait changes: the handover signal is durable, the
agent's idle timeout starts when the run boots rather than when it was
triggered, and a fresh run reads `session.in` from the beginning, so a
parked turn resumes at step 2 once the deployment lands.
@0ski
0ski marked this pull request as draft August 26, 2026 15:02
@changeset-bot

changeset-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 266d394

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 27 packages
Name Type
@trigger.dev/sdk Patch
@trigger.dev/python Patch
@internal/dashboard-agent Patch
@trigger.dev/build Patch
@trigger.dev/core Patch
@trigger.dev/react-hooks Patch
@trigger.dev/redis-worker Patch
@trigger.dev/rsc Patch
@trigger.dev/schema-to-json Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer Patch
@trigger.dev/rbac Patch
@trigger.dev/sso Patch
trigger.dev Patch
@internal/clickhouse Patch
@internal/llm-model-catalog Patch
@internal/metrics-pipeline Patch
@internal/redis Patch
@internal/replication Patch
@internal/run-engine Patch
@internal/run-store Patch
@internal/schedule-engine Patch
@internal/tracing Patch
@internal/webhook-engine Patch
@internal/webhook-sources Patch
@internal/testcontainers Patch
@internal/cache Patch

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

@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a75f662e-5d55-496a-9216-0e8ad5cd5253

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@0ski
0ski marked this pull request as ready for review August 26, 2026 15:20

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Open in Devin Review

Comment on lines +988 to +995
if (response.headers.get("X-Trigger-Chat-Pending-Version") === "1") {
this.emitEvent({
type: "run-pending-version",
chatId,
timestamp: Date.now(),
source: "head-start",
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Parked notice clears on head-start step 1

On the head-start path, step 1 always streams from the warm server, so first-chunk fires immediately (chat.ts) right after run-pending-version is emitted (chat.ts). The docs' recommended first-chunk clear (version-skew-protection.mdx) then hides the parked notice before step 2, the actually-parked part, is reached. Unlike the direct path, where first-chunk arrives only after the deployment lands.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant