Skip to content

Start worktree.create's agent through the launch executor - #26709

Merged
brennanb2025 merged 3 commits into
mainfrom
brennanb2025/p1b-worktree-create-executor
Oct 9, 2026
Merged

brennanb2025 merged 3 commits into
mainfrom
brennanb2025/p1b-worktree-create-executor

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

ELI5

Orca has one host operation that starts agents (the launch executor). Creating a workspace with an agent through worktree.create (the CLI's orca worktree create --agent … --prompt …, the phone's "create workspace from issue/PR", and every other caller of that request) still started its agent its own way, beside the executor. This PR sends those agent requests through the executor too. Nothing you see changes.

What Changed

The problem. Agent launches had two separate starting paths on the host. agent.launch ran through the executor; worktree.create with startupAgent or startupDraft called the workspace create directly and let it build the agent. Two paths meant every launch rule (which surface, how the prompt arrives, what is reported) had to be kept in step by hand, and the remaining host producers (headless automations, orchestration workers) had no shared entry to move onto.

What is different for the user: nothing, by design. Same request, same reply (including agentTerminalHandle), same agent, same tabs and focus:

  • orca worktree create --agent claude --prompt "…" prints the same result, and the prompt still rides the agent's start command at any length.
  • Agents that take their prompt after start (Aider, Goose, …) still get it once they are up.
  • The phone's issue/PR URL still lands in the agent's input box unsent and editable.
  • With "new agent tabs open as chat" switched on, worktree.create still starts a terminal agent: its reply promises a terminal handle.
  • Folder workspaces and SSH hosts take the same create path as before.

The mechanism. The worktree.create handler now calls createWorktreeWithStartupAgent instead of createManagedWorktree; it takes the same arguments and returns the same result. When the request names an agent or a linked draft, it runs the executor with three host-internal inputs (none of them on the wire):

  1. Terminal-only (terminalOnly): the launch settles as a terminal regardless of the chat default. It is the same branch of the mode decision that a "terminal" setting takes.
  2. Legacy host prompt policy (promptPolicy: 'legacy-host'). The compiler enforces its shape: the executor's input is now one of two types, and a legacy-host launch must be terminal-only, must create a workspace, and cannot pass a surface factory, while every other launch must pass one: the create owns the text, exactly as before. It folds a prompt into the start command at any length, sends it after start to agents that read it then, and pastes a draft unsent. The executor delivers nothing itself. The create's startup terminal is the only surface: if it could not start the agent here (it hands the start to the window instead, or reports why in warning), the executor does not build a second one.
  3. A shared host entry (createWorktreeWithStartupAgent) with its own workspace factory. It keeps worktree.create's own create arguments (it does not await setup provisioning the way agent.launch's factory does). Headless automations and remote workers can later call it in place of createManagedWorktree.

For a linked draft with no agent named, the host still picks the agent the same way (requested agent, then the default, then one detected on the host that runs it). That choice now happens just before the create, so the executor can name the agent. It is handed to the create through an internal field that only steers which agent the draft starts, so the workspace still records exactly what main records, including when that agent's start command cannot be built. Detection still runs once, also when no agent is found.

Why

This is the first step of moving every fresh agent start on the host onto one executor, without changing any client. Migrating the host side of worktree.create covers the CLI, the phone, older phones and every other worktree.create caller at once. Moving the CLI or phone onto agent.launch instead would change the CLI's printed result and need an old-host fallback. It would also need a host draft writer for new workspaces, so it is a feature, not parity.

Differences from the common pattern, labeled:

  • Intended: worktree.create stays as its own wire method and keeps a terminal-only contract. Clients and hosts update independently, and its reply promises a terminal handle.
  • Temporary: the legacy prompt policy sits beside the executor's own policy until a later change gives both one planner.

Linked Issue

Maintainer change; part of the launch-unification work (no separate issue).

Visual Proof

No visual change by design. Main (left) vs this PR (right), hidden test app with stand-in agents that log what they receive:

orca worktree create --agent claude --prompt "fix the login bug": one terminal tab, prompt on the agent's command line.

main PR
q1-before-opened.png q1-after-opened.png

Same with "new agent tabs open as chat" switched on: still a terminal agent.

main PR
q4-before-opened.png q4-after-opened.png

The phone's create-from-issue with an agent that reads input after start: the issue URL waits in the input, unsent.

main PR
c-q6c-before.png c-q6c-after.png

Testing

  • startup-agent-worktree-create.test.ts: the entry passes a 3 KB prompt to the create with no typed-line measuring and no setup awaiting. It also covers a post-start agent, an empty prompt, a draft (startupDraft, no startupAgent), a draft with no agent resolved, a prebuilt command, a create that started no agent (no second surface), and a create failure.
  • agent-launch-legacy-host-policy.test.ts: terminal-only ignores the chat default. The legacy policy hands the whole text to the create, reports a draft as not delivered, and refuses anything but a terminal-only create.
  • worktree-create-startup-agent.test.ts: recorded CLI and phone requests go through the real worktree.create handler with the chat default on. They get the same reply shape and handle, and the draft stays unsent.
  • orca-runtime-startup-draft-launch-parity.test.ts: a linked-draft create on a real runtime detects the agent once and records the same agent and opens the same terminals as main, including when the agent's start command cannot be built.
  • Existing worktree, agent-launch, executor and startup tests pass. tc:node and the CI static checks pass.

Manual QA on macOS (hidden test apps, main vs this PR, stand-in agents by absolute path): CLI create with a prompt, with a 3,000-character prompt, with no prompt, with an agent that reads input after start, with the chat default on, with --activate, and the phone's raw draft request (Claude gets the URL through its draft flag, an agent without one gets it pasted with no Enter). Every row matched main: same JSON apart from terminal ids, one agent tab, no workspace switch unless asked, nothing submitted. Not verified: an SSH host (no free test target), the phone app itself (covered by a recorded-request test), Windows and Linux (no platform-specific code changed).

  • I manually tested these changes locally
  • Automated tests added/updated

Review

Two adversarial reviewers, an architecture review that sketched its own design first, a second-round review of the fixes, and a release-readiness pass. No P0-P2 findings. Fixed: detection running twice for a draft with no agent; the draft path recording the wrong agent when that agent's command can't be built (it could also leave a workspace with no terminal); runtime guards replaced by compile-time types; truthful internal prompt receipts; parity tests on a real runtime.

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

No wire change: worktree.create params and reply are unchanged, so older CLIs, phones and hosts keep working in every pairing. SSH, folder and WSL creates are unchanged: they still go through the same create, which routes them as before.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

The host's worktree.create handler now starts a requested agent (startupAgent,
or a linked startupDraft) through executeAgentLaunch instead of calling the
create directly. Nothing on the wire changes: the request, the reply and
agentTerminalHandle are the same, the agent is still the workspace's startup
terminal whatever the chat default says, and the create still delivers the
text itself (folded into the command at any length, a follow-up for agents
that read it after start, and the draft pasted unsent).

Adds three host-internal executor inputs: terminalOnly, the legacy-host prompt
policy, and a shared entry (createWorktreeWithStartupAgent) that host
producers can call in place of createManagedWorktree.
Problem: after worktree.create moved onto the launch executor, a create from
a linked issue (phone "create from issue", or a raw worktree.create with
startupDraft) could differ from before in two narrow cases. When the draft's
agent came from the settings default or from detection and its launch command
could not be built (for example unbalanced quotes in the saved agent
arguments), the workspace was recorded as created with that agent even though
it never started, and a background create opened no terminal at all instead
of its plain shell. When no agent was found, detection ran twice (an extra SSH
round trip on remote repos).

User-facing change: none intended; both cases now behave exactly as before
the executor move.

Mechanism:
- The entry hands the draft's chosen agent to the create as a main-internal
  startupDraftAgent, which steers only the draft; createdWithAgent stays what
  the caller sent. A draft that resolves no agent is created without the
  draft, so the create does not detect again.
- AgentLaunchExecution is a discriminated union: every normal launch must
  bring its surface factory, and a legacy-host launch must be terminal-only,
  bring a workspace factory, and cannot bring surfaces. The runtime guards
  the types replace are gone; the target kind is checked before any create.
- Legacy-host receipts follow the delivery module's rule: a blank prompt is
  no prompt, a submit folded into the launch command is handed-to-terminal,
  a post-start send the create does not await is unconfirmed (host-internal),
  a draft is not-delivered.
- The no-agent error lives with the legacy-host policy and is caught only by
  the shared entry.
- Tests: a real-runtime parity test (stored agent, terminal spawns,
  detection count vs the create alone, including the unbuildable plan),
  exact-argument checks over every non-agent startup field, compile-time
  checks for the union, and direct tests of the draft agent resolver.
Two comments stated that headless automations and federation already go
through createWorktreeWithStartupAgent; both still call
createManagedWorktree directly. Reword them as the plan. Comment-only.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review, QA and CI summary (head 9fcfc3a)

The problem. Orca's host had two separate ways to start an agent. agent.launch went through the launch executor. Creating a workspace with an agent through worktree.create started the agent on its own. That covers the CLI's orca worktree create --agent … --prompt …, the phone's "create workspace from issue/PR", older phones and paired web. Every launch rule had to be kept in step by hand, and the host's remaining producers had no shared entry to move onto.

What changes for the user: nothing, by design. Same CLI output (including agentTerminalHandle), still a terminal agent when "new agent tabs open as chat" is on, the prompt arrives the same way, the phone's issue URL stays unsent and editable, and tabs and focus behave as before. Internally, those requests now run through the executor via a shared host entry, with two host-only inputs (terminal-only, and the old prompt delivery). Nothing on the wire changed.

Fixed during review (no P0-P2 found):

  • A phone draft with no agent available ran agent detection twice; now once.
  • The phone draft path could record the wrong "created with" agent when that agent's start command can't be built (for example saved arguments with an unbalanced quote). Without activation, that could also leave the workspace with no terminal at all. It now records and opens exactly what main does.
  • The executor's new inputs are enforced by the compiler instead of runtime checks.
  • Internal prompt receipts are truthful. They never reach a client.
  • Added parity tests that run on a real runtime.

Deferred (none user-visible today):

  • The terminal-only receipt reads like the settings default when the chat default is on. Nothing shows it in this PR; it matters when orchestration workers move onto the executor. It is kept identical to Start plain new-tab agent launches through the host's agent.launch #26489's terminal force so Start plain new-tab agent launches through the host's agent.launch #26489 can switch to this input with no behavior change.
  • The entry looks up the repo once more than strictly needed.
  • On the draft path, agent detection runs before the create's own validation checks.
  • Pre-existing on main, not this PR: agent.launch may start a second agent when the startup terminal fails to spawn after its start was handed to the window. Worth a separate follow-up.

Verified.

  • Unit and integration tests: recorded CLI and phone requests through the real handler, and real-runtime draft parity.
  • tc:node, the CI static checks, and CI green on every pushed head.
  • Main-vs-PR manual QA on macOS hidden test apps with logging stand-in agents. Covered: a prompt, a 3,000-character prompt, no prompt, an agent that reads input after start, the chat default on, --activate, and the phone's raw draft request with Claude, the default agent and Aider. Every row matched main apart from terminal ids, with one agent tab and nothing submitted. Screenshots are in the PR body.

Not verified.

  • An SSH host (no free test target).
  • The phone app itself (covered by a recorded-request test instead).
  • Windows and Linux (no platform-specific code changed).
  • The exact edge case the round-1 fix targets, live (covered by the real-runtime test).

@brennanb2025
brennanb2025 marked this pull request as ready for review October 9, 2026 01:37
@coderabbitai

coderabbitai Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0ec65dfa-cbfb-4618-b6a2-4f79f1dbd29a
📥 Commits

Reviewing files that changed from the base of the PR and between dec18a8 and 9fcfc3a.

📒 Files selected for processing (18)
  • src/main/agent-launch/agent-launch-execution.ts
  • src/main/agent-launch/agent-launch-executor.ts
  • src/main/agent-launch/agent-launch-legacy-host-policy.test.ts
  • src/main/agent-launch/agent-launch-legacy-host.ts
  • src/main/agent-launch/agent-launch-mode.ts
  • src/main/agent-launch/agent-launch-prompt-delivery.ts
  • src/main/agent-launch/agent-launch-surface-factories.ts
  • src/main/agent-launch/startup-agent-worktree-create.test.ts
  • src/main/agent-launch/startup-agent-worktree-create.ts
  • src/main/runtime/orca-runtime-activate-managed-worktree.ts
  • src/main/runtime/orca-runtime-create-managed-worktree.ts
  • src/main/runtime/orca-runtime-startup-draft-launch-parity.test.ts
  • src/main/runtime/rpc/methods/worktree-create-startup-agent.test.ts
  • src/main/runtime/rpc/methods/worktree.test.ts
  • src/main/runtime/rpc/methods/worktree.ts
  • src/main/runtime/runtime-managed-worktree-create-types.ts
  • src/main/runtime/runtime-worktree-agent-startup.test.ts
  • src/main/runtime/runtime-worktree-agent-startup.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.


📝 Walkthrough

Walkthrough

The changes add legacy-host launch handling and terminal-only mode to the launch executor. Worktree creation routes startup-agent requests through the executor. Draft startup agent selection now uses a shared resolver that checks requested and default agents before detecting available agents. New tests cover launch policy behavior, agent selection, worktree creation, and RPC requests.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 9fcfc

This change routes worktree.create startup agents through the launch executor while keeping the request and reply shape unchanged. Tests cover the main paths, including prompt delivery, draft handling and failure cases. No blocking issue was identified, though SSH hosts and non-macOS platforms were not exercised.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 56.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 25 functions across 18 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check Passed The title clearly and concisely identifies the main change: routing worktree.create agent startup through the launch executor.
Description check Passed The description is complete and focused. It explains the user impact, implementation, rationale, testing, visual evidence, compatibility considerations, and unverified platforms. The maintainer note a…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@brennanb2025
brennanb2025 merged commit d7e985c into main Oct 9, 2026
56 checks passed
brennanb2025 added a commit that referenced this pull request Oct 9, 2026
Main's worktree.create caller (#26709) builds a legacy-host launch factory
with only createWorktree; type that policy's factory as worktree-only so it
needn't implement this PR's createFolderWorkspace, which only agent.launch uses.
brennanb2025 added a commit that referenced this pull request Oct 9, 2026
Main's orchestration workers (#26724) and worktree.create (#26709) pass
worktree-only launch factories, so createFolderWorkspace is optional on the
factory; a folder create without it is refused before anything is created.
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