Skip to content

Start orchestration workers with their brief on the launch command line (step 4 of 7) - #24081

Closed
brennanb2025 wants to merge 10 commits into
brennanb2025/launch-prompt-argv-carryfrom
brennanb2025/worker-brief-on-launch-line
Closed

brennanb2025 wants to merge 10 commits into
brennanb2025/launch-prompt-argv-carryfrom
brennanb2025/worker-brief-on-launch-line

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 26 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1389 $\color{#cf222e}{\Huge{\mathbf{−}}}$​213 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1176
Prod 31 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​959 $\color{#cf222e}{\Huge{\mathbf{−}}}$​134 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​825

What this fixes

Orchestration's worker-start launched the agent empty, waited for it to look idle, then pasted the dispatch brief (the worker's task and the commands it reports with) into its screen.

On a fresh Codex 0.157 home that paste lost or cut the first ~1 KB of the brief five times out of five, and orchestration still recorded the start as "observed", because Codex's startup spinner title counted as a started turn (#23745).

A pasted Enter could also land on a startup dialog such as Codex's "Update available", which defaults to "Update now".

What changes for you

  • On macOS, Linux, WSL panes and POSIX SSH hosts, a worker gets its whole brief on its launch command line, which the host stages so it arrives whole. Nothing is pasted, so no part of the brief is lost and no Enter lands on a dialog.
  • On a Windows host, and wherever the line cannot carry the brief whole (an SSH host whose shell may be WSL, or a host that cannot write the folder it stages lines in), the brief is pasted once the agent is ready, exactly as today.
  • worker-start reports "observed" only after the agent's own hook reports a turn that carried the prompt. A spinner title, a session start with no prompt, or output bytes no longer count while hooks are working.
  • With agent hooks turned off, or never reaching the pane, a worker is judged as today: a title turn-start edge, or the agent holding its terminal with no startup dialog on screen. Starts are not held for the whole timeout.
  • If the agent exits at startup, the start fails and its terminal is torn down, as today, instead of waiting out the timeout.
  • If a startup dialog (trust, update) holds the worker, the receipt says so and recommends worker-stop. The terminal stays bound to the Dispatch, so answering the dialog yourself runs the task and its report settles normally.
  • The sidebar, activity thread and phone show the worker's task, as today: the hook reports the brief itself, which every reader compacts to its task line.
  • A worktree the start creates is set up as today: the agent's terminal first, then setup and default tabs, with no extra empty shell. A wait-for-setup policy still holds the agent until setup succeeds.

Scope

Affected: orca orchestration worker-start launching a terminal worker in a fresh terminal, for every agent that takes its first prompt on its command line (Claude, Codex, Gemini, Cursor, Antigravity, Grok and the others), on macOS, Linux, Windows, WSL and SSH hosts, including worktrees the start creates.

Not affected: reused terminals (--terminal), structured (chat) workers, and agents that take text only after start. Those keep the paste after ready.

The plan, and where this PR sits

This is step 4 of 7 of the agent launch plan.

  1. feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) #24257: the host owns prompt delivery; a prompt is pasted only once the agent is ready, and nothing is typed after a startup crash.
  2. Stage long agent launch lines where they are typed, so they arrive whole (step 2 of 7) #23962: long or multi-line launch lines are staged where they are typed, so they arrive whole.
  3. Carry an agent's first prompt on its command line instead of pasting it (step 3 of 7) #24080: the first prompt rides the agent's own command line, through one carry rule that picks the line, a private launch file, or the paste.
  4. This PR (Start orchestration workers with their brief on the launch command line (step 4 of 7) #24081): orchestration workers offer their brief to that same rule, and a start counts only when the agent's own hook reports the turn.
  5. Launch identity and record: one host-assigned caller identity and a two-write launch record, so a restart mid-launch finds the running agent.
  6. Placement and the instant tab: the tab appears at once, in the split you clicked from.
  7. feat(source-control): start the AI buttons' agents through agent.launch (step 2 of 2) #23748, reworked: the desktop AI buttons go through agent.launch.

How it works

  1. Worker start builds the brief before the spawn: it pre-allocates the terminal handle the brief names, and names the CLI command the terminal will run, from the same WSL decision the spawn makes.
  2. It hands the brief to the terminal create (or to agent-first worktree creation) as an ordinary startup prompt, with main's paste as the caller's fallback (once-agent-runs). Step 3's rule decides: the exact line, else the paste. The rule reports which through onStartupPromptCarry.
  3. A brief left for the paste goes through today's path unchanged: the readiness wait, then the paste.
  4. A brief that rode the line waits only on the repo's wait-for-setup policy, then its turn start. The hook status store records explicitPromptStartedAt (when the current working run first saw an event carrying an explicit prompt); it is kept in memory, never persisted, and reaches the runtime on the existing status rows, SSH rows included.
  5. The turn-start watch (launch-turn-start-observation.ts) takes the hook proof first. If the shell reports the launch command finished and the agent is not in front, the start fails as exited. If no hook event reaches the pane within 10 s, or hooks are off, a title turn-start edge counts as observed, and the agent holding its terminal counts as started with no turn signal (unsupported) as soon as the foreground read proves it and a direct read of the screen shows no startup dialog.

Why this approach

  • One rule decides where every launch prompt goes, so a worker brief gets exactly what any other prompt gets on that host, including the fallback where a host cannot take the line.
  • A command-line argument arrives whole or the agent does not start; no readiness guess can drop it.
  • Acceptance comes from the agent's own prompt hook, through the one status store every reader already uses (docs/reference/agent-status-store.md), not from a terminal title; where hooks give nothing, main's evidence decides, so a start is never slower than main.

Differences from the common pattern:

  • Intended: a started turn is accepted from the agent's own prompt-submit event, as in the common pattern, but through Orca's hook store; the timestamp is live-only and never persisted, so a restart cannot replay a stale acceptance.
  • Intended: a start blocked by a dialog keeps its terminal (reported as an unknown start) instead of tearing down, because the brief is already on the command line and answering the dialog runs it.
  • Intended: on a Windows host a Claude or Codex brief is pasted, as main pastes it, rather than put on the line or read from a launch file. Why safe: it follows from the one carry rule, and main pasted worker briefs on every host.
  • Intended: every agent that takes its prompt on its command line now gets the brief on the line where the host carries it, not only Claude and Codex. Why safe: it follows from the one carry rule, and main pasted worker briefs on every host.

How this differs from the previously reviewed version (b7ec3502370)

Round-1 review fixes, as a second commit on top of the rebuilt change:

  • The brief no longer forces its own launch file past the rule's host checks. It is an ordinary startup prompt, so a host that cannot take a launch file (an SSH host whose shell may be WSL) gets the paste instead of a refused start.
  • The brief is no longer marked sensitive (it has carried no secret since fix(orchestration): stop minting and printing the dispatch capability #23994), so the worker's status shows its task as on main instead of the launch file's pointer.
  • A launch the shell reports finished, with no agent in front, fails and tears down the start instead of waiting out the window as unknown.
  • With hooks off or never reaching the pane, the start is judged by main's evidence instead of waiting out the window as unknown.
  • Created worktrees are agent-first again, as on main: no extra empty shell, setup and default tabs after the agent. The separate pre-agent setup gate is removed.
  • The brief's CLI command comes from the spawn's own WSL decision, so a Windows-path repo opened in a WSL shell tells the worker orca-ide, as main does.
  • A terminal that comes back under another handle than its brief names is closed before the start fails, so a retry cannot run the task twice.
  • After final QA (hooks off under load: ready 11.3 s vs main 8.6–9.2 s; instrumented: feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) #24257's foreground read took 3.4–5 s, then a fixed 2 s dialog settle), a launch the foreground read proves no longer waits that 2 s: it counts once a direct screen read shows no startup dialog. The read itself is feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) #24257's and unchanged here. Pinned with fake-timer tests.
  • awaitStructuredWorkerSetupGate is renamed awaitWorkerSetupGate (it now serves both kinds of worker), and the unreachable "no prompt hook" readiness branch is folded into the fallback above.

Size

56 files, +2,282/−312 against step 3: production 29 files +785/−97, tests 27 files +1,497/−215.

Testing

  • 100 named test files: 2,329 tests pass, 1 expected failure, 1 skipped; one timing test outside this PR (runtime-rpc-long-poll-transport keepalives) failed once at load ~105 and passed alone 3/3, including feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) #24257's foreground-read tests, which step 3 now carries: every test this PR touches, every test importing a changed module (worker start, worker dispatch, hook status store, prompt verification, worktree startup, terminal create), the two end-to-end unit tests that start a worker, and the runtime spec harness orca-runtime.test.ts.
  • The settle removal was undone three ways: all 3 ablations fail (settle back in worker start, agent taken with a dialog on screen, a fixed settle after the read in the observer).
  • Each round-1 fix was undone on its own and its tests re-run: all 14 ablations fail (sensitive launch file back, caller paste mode ignored in terminal create and worktree startup, uncarried brief treated as carried, no exit verdict, exit not failed, no hook-silence fallback, hooks setting ignored, worktree not agent-first, startup handle dropped locally and remotely, WSL never predicted, prediction unused, mismatched terminal not closed).
  • tc:node, tc:web and tc:cli pass; oxlint on the 56 changed files and check:code-quality:changed are clean.
  • Live QA of the earlier combined version: Claude 2.1.285 sent UserPromptSubmit with the command-line prompt and the worker read "observed" 0.6–0.7 s after the hook; Codex's UserPromptSubmit carried its prompt before "observed", and Codex 0.158 skipped its update dialog when started with a prompt. This version has not been run live; the coordinator schedules QA per PR.

Visual Proof

N/A: worker start has no UI of its own; the evidence is the receipt and the hook timeline, which the coordinator's QA will attach.

Notes

Author X handle: @BrennanKB5

@brennanb2025
brennanb2025 force-pushed the brennanb2025/launch-prompt-argv-carry branch 2 times, most recently from cb0d0fa to de94a21 Compare October 2, 2026 07:33
@brennanb2025
brennanb2025 force-pushed the brennanb2025/worker-brief-on-launch-line branch from 8386519 to f57b243 Compare October 2, 2026 07:35
@brennanb2025 brennanb2025 changed the title Start orchestration workers with their brief on the launch command line (step 3 of 3) Start orchestration workers with their brief on the launch command line (step 4 of 7) Oct 2, 2026
@brennanb2025
brennanb2025 force-pushed the brennanb2025/worker-brief-on-launch-line branch 7 times, most recently from cc63203 to 74086ca Compare October 2, 2026 11:38
brennanb2025 added a commit that referenced this pull request Oct 2, 2026
The worker brief was its only producer, and #24081 now routes the brief through
the carry rule as ordinary text, so every launch file was minted with
sensitive: false. The flag, its wire field, its parse check and the skip in the
pane prompt cache are removed; nothing that crossed the wire was released.
@brennanb2025
brennanb2025 force-pushed the brennanb2025/worker-brief-on-launch-line branch 5 times, most recently from 8ffdf2f to 193eead Compare October 2, 2026 15:02
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Final live QA of the agent-launch stack, the worker-start rows (2026-10-02). Report only; full report with every number kept by the coordinator.

Builds compared: main 302526411dd and this PR at 193eeaddff3 (earlier heads where noted). Real Claude 2.1.287 on the M4Air through an isolated rig; stub agents on Windows.

Case main 193eeaddff3
macOS, hooks on, existing worktree (2 pairs) ready 8.55–8.65 s, observed 0.71–0.74 s, observed
macOS, hooks on, new worktree (2 pairs) 8.77–8.85 s 0.88–1.06 s
macOS, hooks off (2 pairs; plus 4 pairs on the same product code) 8.46–9.44 s 0.18–1.38 s (unsupported / observed)
macOS, agent exits at startup waits the whole 120 s timeout fails at 9.9 s: "Agent exited before its first turn started; the shell is back in front."
macOS, Gemini (stub) never delivers, fails at 120.6 s brief on its command line (on eed699ffac1)
Windows Claude (stub with Orca's hook), new and existing worktree 12.49 / 12.99 s 12.44 / 13.17 s; brief pasted once on both
Windows hooks off, and Codex 42.2 s outcome_unknown the same
  • Hooks off was worse than main on earlier heads (eed699ffac1: 17.6–32.0 s vs 9.0–11.9 s at load 25–84). Instrumented cause: not the hook wait; each check of what is in front of the terminal scanned the whole machine's process table (3.4–10 s), then a fixed 2 s dialog wait. Fixed by 6090872321f (no fixed wait) plus feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) #24257's pane-scoped read (3–59 ms per check). Not re-measured above load 26.
  • Claude acts on a brief carried on its command line with no typed lead-in sentence: 10/10 "READY" on both arms; a file-write task done at 3.5 s vs main's 11.7 s. The sidebar and worktree ps show the task title, never the pointer.
  • Windows: a worker whose agent exits at startup still waits the full timeout (90 s), as on main. The description says such a start fails instead; true on macOS, not on Windows, where the brief is pasted after ready.
  • A hooks-off unsupported receipt can return before Claude has recorded the brief (0.18 s vs 0.62 s); a crash in that window would not fail the start. Not tested.

@brennanb2025
brennanb2025 force-pushed the brennanb2025/worker-brief-on-launch-line branch from 193eead to a704428 Compare October 2, 2026 16:46
@brennanb2025

Copy link
Copy Markdown
Contributor Author

QA update on a7044286c72 (2026-10-02), the restack onto #24080 d8cfcf397a4. The M4Air lane reran the worker rows with real Claude.

  • When the launch line cannot be staged (read-only temp folder): worker-start fails in 0.46 s at terminal_create with "Orca could not write the script that carries the agent's launch line and prompt (EACCES …), so the agent was not started. [launch_file_unavailable]". Nothing is typed, no agent starts, and no tab is left (residualResources: []). Main in the same setup starts the worker and pastes the brief (ready 9.02 s), so this is worse than main only in that setup, and by design.
  • Control, folder writable: ready in 1.10 s (observed, brief on the launch line) vs main 8.79 s.
  • Everything else from my earlier comment is unchanged (worker code is the same as 193eeaddff3): hooks on and off, ready in 0.2–1.1 s vs main 8.5–9.4 s; a startup crash fails at 9.9 s on macOS; on Windows a startup crash still waits the full timeout, as on main.

@brennanb2025
brennanb2025 force-pushed the brennanb2025/worker-brief-on-launch-line branch from a704428 to 94e15be Compare October 2, 2026 18:00
@brennanb2025

Copy link
Copy Markdown
Contributor Author

QA update on 94e15bef2be (2026-10-02), the restack onto #24080 ca1c4046ed4. Worker starts, main 302526411dd vs this head.

Case main 94e15bef2be Verdict
macOS, temp folder read-only, real Claude, hooks on ready 8.65–9.02 s, pasted ready 9.34 s, observed, brief pasted once, nothing extra typed PASS (the terminal_create failure on a7044286c72 is gone)
macOS, folder writable ready 9.19 s ready 1.03 s, brief on the launch line PASS
Windows cmd, local temp read-only, Claude / Grok stubs outcome_unknown 44.2 s / ready 4.7 s, pasted once outcome_unknown 44.2 s / ready 3.0 s, pasted once PASS
Windows WSL workspace, ~/.cache/orca broken after first use, Claude / Grok agent starts, brief never pasted, fails on a readiness timeout at 92–95 s fails at terminal_create in 2.4 / 4.7 s with launch_file_unavailable FAIL vs the expected paste + ready (from #24080's WSL fallback not engaging; see its comment). Faster than main, with a reason.

Nothing was typed into any shell in these rows.

@brennanb2025
brennanb2025 force-pushed the brennanb2025/worker-brief-on-launch-line branch 3 times, most recently from 3ad7e2b to d93c542 Compare October 2, 2026 20:19
Round-1 review fixes. The worker brief no longer forces its own sensitive
launch file past the host checks. Worker start offers the brief as an ordinary
startup prompt with main's paste (`once-agent-runs`): it rides the agent's
launch line where the line carries it exactly, and is otherwise pasted once the
agent is ready, as main pasted it, so a host that cannot take a launch file no
longer fails the start. With no sensitive file, the agent's hook reports the
brief itself, which status readers compact to its task as on main.

- Created worktrees are agent-first again, as on main: the startup terminal
  keeps the handle the brief names, setup and default tabs follow it, and a
  wait-for-setup sequence still holds the agent until setup succeeds.
- The brief names the CLI command the spawn's own WSL decision will give the
  terminal.
- A launch the shell reports finished, with no agent in front, fails and tears
  down the start. Where hooks are off or never reach the pane, the launch is
  judged by main's evidence: a title turn-start edge, or the agent holding its
  terminal.
…settle

With agent hooks off, a worker whose brief rode its launch line is judged by the
agent holding its terminal. That verdict used to wait a further 2 s for a
startup dialog to settle; it now counts as soon as the foreground read proves
the agent and a direct read of the screen shows no startup dialog. The
foreground read itself is unchanged.
…n terminal

#24257's read now takes the pane's root pid from the session inventory and one
ps limited to its terminal, so the launch-observation tests fake those rows
instead of the whole-machine scan the read no longer uses.
… its staging folder

A host that cannot write the folder staged lines and launch files go in now
plans main's delivery up front, so a worker brief offered with main's paste is
left for that paste instead of being refused at terminal create.
@brennanb2025
brennanb2025 force-pushed the brennanb2025/worker-brief-on-launch-line branch from d93c542 to 414d9ab Compare October 2, 2026 23:31
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Final QA on this head 414d9abfeaa (on #24080 0a43c0a28fd), 2026-10-02, main 302526411dd vs the stack. The launch rows for the whole stack are in the #24080 comment. Worker-start rows on this head:

Case main 414d9abfeaa Verdict
Windows WSL workspace, Claude worker (stub without hooks) brief pasted once; outcome_unknown at 46.56 s brief pasted once; outcome_unknown at 46.56 s PASS (= main)
Windows WSL workspace, Grok worker brief pasted once; ready at 6.96 s brief pasted once; ready at 5.56 s PASS

Not re-run on this head: the macOS worker rows (real Claude with hooks on and off, read-only staging folder), last measured on 94e15bef2be. Nothing was typed into any shell in these rows.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 3, 2026 02:14

@pullfrog pullfrog 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.

Important

Two paths can fail a launched worker that is actually fine: the exit test folds a 'unknown' foreground read into exited, and the launch-evidence fallback is gated on "some status row exists" rather than "the prompt-carrying hook was seen". Both are inline with fixes. A third, smaller consistency gap is inline as well.

Reviewed changes

  • Worker brief on the launch line (worker-launch-brief.ts, worker-start-agent-placement.ts, worker-worktree-creation.ts, worker-topology.ts, local-worker-start.ts): the handle is minted before the spawn, the preamble is built early, and the brief is offered to the one carry rule as startupPrompt with paste once-agent-runs; carried decides line vs. deliverWorkerDispatchPreamble, and a pre-allocated-handle mismatch closes the adopted terminal and fails the start.
  • Launched-turn acceptance (launch-turn-start-observation.ts, worker-start-turn-observation.ts, orca-runtime-agent-prompt-request-correlation.ts): the hook's prompt-carrying turn is the proof; title edge or agent-in-front is the fallback when hooks are off/silent; a shell-finished launch with no agent in front fails as exited; a startup dialog reports turn_start_blocked and keeps the terminal bound with worker-stop recommended.
  • New hook field explicitPromptStartedAt (server-status-application.ts, server-types.ts, server-persistence.ts, server-status-identity.ts, agent-status-ipc-payload.ts, runtime-hook-agent-row-selection.ts, orca-runtime-serialize-agent-prompt-submission.ts): pinned for a working run, cleared when the turn ends, live-only, never persisted.
  • Prediction and budget sharing (spawn-cli-command-prediction.ts, worker-setup-gate.ts, runtime-managed-worktree-create-types.ts, worktree startup plumbing): WSL-aware CLI command predicted for a not-yet-spawned terminal, and setup plus the launched turn share one start budget floored at AGENT_PROMPT_EFFECT_TIMEOUT_MS.

I traced brief delivery across all four worker-start paths (existing worktree, local new worktree, SSH new worktree, reused terminal) and it is delivered exactly once: onStartupPromptCarry fires exactly once and startLocalWorker's paste branch is mutually exclusive with the carried branch. The four new-test files I ran pass.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

while (!signal.aborted && Date.now() < deadline) {
if (probe.launchRecorded()) {
launchSeen = true
} else if (launchSeen && (await probe.readForeground()) !== 'agent') {

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.

!== 'agent' treats 'unknown' as an exit. readLaunchedAgentForeground returns 'unknown' whenever the host cannot prove the foreground — a transient readTerminalProcessRows/getForegroundProcess miss locally, or an SSH relay that cannot answer inspectProcess — and LaunchedAgentForeground documents only 'shell' as "the agent exited". Once the launch command retires (launchRecorded() false) and the next read is 'unknown', this returns exited, observeWorkerLaunchTurnStart throws, and the start fails at turn_observation, tearing down a terminal whose agent is still alive.

Technical details
# `unknown` foreground must not count as an exit

## Affected sites
- `src/main/runtime/launch-turn-start-observation.ts:81` — `else if (launchSeen && (await probe.readForeground()) !== 'agent') return 'exited'`
- `src/main/runtime/launched-agent-foreground.ts:56-61` — `asLaunchedAgentForeground` maps `'other'` to `'agent'`, so `readForeground` is only ever `'agent' | 'shell' | 'unknown'`
- `src/main/runtime/orca-runtime-activate-managed-worktree.ts:231-232` — the established read uses `=== 'shell'` for `isShellInFront`

## Required outcome
- Only a foreground read that positively proves the shell came back (`'shell'`) is an exit; `'unknown'` leaves the launch unproven, so the fallback / hook proof still decides.

## Evidence
- The only `exited` test (`src/main/runtime/launch-turn-start-observation.test.ts`) uses `readForeground: async () => 'shell'`; no test covers `'unknown'`, which is why this passed.
Suggested change
} else if (launchSeen && (await probe.readForeground()) !== 'agent') {
} else if (launchSeen && (await probe.readForeground()) === 'shell') {

this.observeLaunchHookTurn(handle, ptyId, launch.launchStartedAt, timeoutMs, stop)
}
: {}),
hookReachedPane: () => this.getFreshExplicitAgentStatusForPty(handle, ptyId) !== null,

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.

hookReachedPane means "a fresh status row exists", but the hook proof accepts only a row carrying explicitPromptStartedAt. launch-turn-start-observation.ts:86 suppresses the launch-evidence fallback for the whole window whenever this is true, so a row that carries no prompt — a prompt-less SessionStart, a refresh, a hydrated row, or an older/paired host whose hooks never set hasExplicitPrompt — blocks the fallback and the carried brief settles outcome_unknown after the full timeout. Main accepted exactly this launch from the title edge or the agent-in-front read.

Technical details
# Status-row presence is not prompt-hook proof

## Affected sites
- `src/main/runtime/orca-runtime-agent-prompt-request-correlation.ts:138` — `hookReachedPane: () => this.getFreshExplicitAgentStatusForPty(handle, ptyId) !== null`
- `src/main/runtime/launch-turn-start-observation.ts:84-98` — fallback only runs inside `if (hooksSilent)`, and `hooksSilent` requires `!probe.hookReachedPane()`
- `src/main/runtime/agent-prompt-submission-verification.ts:128-133` — `explicitPromptOnly` accepts only `current.explicitPromptStartedAt > baseline`
- `src/main/agent-hooks/server/server-status-application.ts:295-310` — the field is set only when `payload.hasExplicitPrompt === true`
- `src/main/agent-hooks/ssh/ssh-relay-session.ts:1693` — an old relay may forward no `hasExplicitPrompt`, so newer local main computes no field while rows still flow

## Required outcome
- A launch whose hooks reach the pane but never carry the prompt field should still be judged by main's evidence (title turn-start edge, or the agent holding the terminal) once `LAUNCH_HOOK_SILENCE_MS` has passed, rather than only by the hook proof.

## Suggested approach
- Track whether a row actually carried `explicitPromptStartedAt` (not merely that a row exists), or clear the `hooksSilent` gate on the silence window even when rows are present.

## Open questions for the human
- Is this accepted for agents (e.g. Antigravity) whose hooks publish a session-start row but no prompt-carrying hook for a command-line prompt?

if (sameRun && previous.explicitPromptStartedAt !== undefined) {
return previous.explicitPromptStartedAt
}
return payload.hasExplicitPrompt === true && payload.isReplay !== true ? at : undefined

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.

resolveExplicitPromptStartedAt omits the payload.toolAgentId === undefined && payload.providerSessionOnly !== true guards its sibling resolveTurnStartedAt applies (:36-42), so a child-agent or identity-only event that carries hasExplicitPrompt can stamp the main run's field, and the launch observer can accept it as the worker's brief. Add the same guards.

Brings #24257's merge of main (e02adc5) through #23962 and #24080.
- Worker start: a structured session or a brief that rode the launch line still waits only on
  the setup gate; any other worker waits through main's waitForWorkerAgentReady, which routes a
  fresh launch through #24257's worker-start wait and a reused terminal to plain tui-idle.
- startupPromptPaste is threaded through main's resolveWorktreeCreateAgentStartup and its
  extracted terminal startup builder.
- Main's worker agent preflight (configured agent params, the OpenCode model probe) moved into
  local-worker-agent-launch.ts so local-worker-start stays under the line cap.
…nto brennanb2025/worker-brief-on-launch-line

A brief that rode the launch line still skips the paste; a pasted brief takes #24257's new check.
The local brief-check test uses Amp, whose brief is pasted here since an argv agent's rides the line.

@pullfrog pullfrog 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.

ℹ️ No new issues in the incremental changes. The worker-start brief is now wired to the host's agent-in-front write guard, so a pasted brief is never typed into a shell that returned after the agent exited.

Reviewed changes

  • Wired the worker brief to the host's agent-in-front write guard: local-worker-start passes launchedAgent (null for a caller-supplied --terminal) into deliverAndSettleWorkerStartReadiness, which forwards it to deliverWorkerDispatchPreamble; a fresh-launch brief now refuses the paste unless the host proves the launched agent is in front, while a reused terminal keeps its idle-wait paste.
  • Narrowed the settlement args agent/launchedAgent from string | null to TuiAgent | null, so the guard receives a checked agent.
  • Retargeted worker-brief-agent-in-front.test.ts at amp — an agent whose brief is pasted, so the guard actually runs — and added a reused-terminal case asserting no foreground read and one write.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

brennanb2025 added a commit that referenced this pull request Oct 5, 2026
…o the stack is one chain

#24934 was stacked on #24257; it now sits on #24081 (24257 -> 23962 -> 24080 -> 24081 -> 24934).
The prompt-delivery table keeps #24080's argv row and this branch's unconfirmed row. The restart
replay test pins its stub to the paste path, since #24080's fixture now reports an offered prompt
carried unless told otherwise.
brennanb2025 added a commit that referenced this pull request Oct 5, 2026
@brennanb2025
brennanb2025 marked this pull request as draft October 6, 2026 17:19
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Parked as a draft: launch-point unification (all agent launches through agent.launch: #24934, #25430, #25624) is being rebuilt directly on main so it doesn't wait on delivery changes. This PR changes how the first prompt reaches the agent, and it comes back after unification lands, as its own PR on top of it.

brennanb2025 added a commit that referenced this pull request Oct 6, 2026
… written when the surface exists

Step 1 of the agent-launch unification, on main.

- The dispatcher stamps every request's caller from what its connection proved (runtime socket:
  the local CLI; the desktop's IPC: the desktop; a paired socket: its device). Params never set it.
- The launch record is written twice: once when the tab exists (what creation settled: on the
  launch command, a draft, or a submit still `unconfirmed`), and again once the prompt's fate is
  known. A restart in between finds the running agent instead of answering "unknown".
- A replay re-derives its terminal handle from the pane key in the running host, and shows
  `unconfirmed` only to callers that advertise agent.launch.prompt-unconfirmed.v1.
- The record store opens in its own slot, without building the chat host; the chat host is built
  on that same store.

Rebuilt from this PR's own commits (b9adf88, 0d7d9b5, c83f44d, 39d15d5) onto
main, without #24080/#24081. Conflicts: the delivery doc table (main's "line fits" row plus the
`unconfirmed` row), and main's journal-database open in install(), which now goes through the
record-store slot.
brennanb2025 added a commit that referenced this pull request Oct 6, 2026
… written when the surface exists (#24934)

Step 1 of the agent-launch unification, on main.

- The dispatcher stamps every request's caller from what its connection proved (runtime socket:
  the local CLI; the desktop's IPC: the desktop; a paired socket: its device). Params never set it.
- The launch record is written twice: once when the tab exists (what creation settled: on the
  launch command, a draft, or a submit still `unconfirmed`), and again once the prompt's fate is
  known. A restart in between finds the running agent instead of answering "unknown".
- A replay re-derives its terminal handle from the pane key in the running host, and shows
  `unconfirmed` only to callers that advertise agent.launch.prompt-unconfirmed.v1.
- The record store opens in its own slot, without building the chat host; the chat host is built
  on that same store.

Rebuilt from this PR's own commits (b9adf88, 0d7d9b5, c83f44d, 39d15d5) onto
main, without #24080/#24081. Conflicts: the delivery doc table (main's "line fits" row plus the
`unconfirmed` row), and main's journal-database open in install(), which now goes through the
record-store slot.
brennanb2025 added a commit that referenced this pull request Oct 7, 2026
…ed (#25430)

* feat(agent-launch): host-assigned caller identity and a launch record written when the surface exists

Step 1 of the agent-launch unification, on main.

- The dispatcher stamps every request's caller from what its connection proved (runtime socket:
  the local CLI; the desktop's IPC: the desktop; a paired socket: its device). Params never set it.
- The launch record is written twice: once when the tab exists (what creation settled: on the
  launch command, a draft, or a submit still `unconfirmed`), and again once the prompt's fate is
  known. A restart in between finds the running agent instead of answering "unknown".
- A replay re-derives its terminal handle from the pane key in the running host, and shows
  `unconfirmed` only to callers that advertise agent.launch.prompt-unconfirmed.v1.
- The record store opens in its own slot, without building the chat host; the chat host is built
  on that same store.

Rebuilt from this PR's own commits (b9adf88, 0d7d9b5, c83f44d, 39d15d5) onto
main, without #24080/#24081. Conflicts: the delivery doc table (main's "line fits" row plus the
`unconfirmed` row), and main's journal-database open in install(), which now goes through the
record-store slot.

* feat(agent-launch): show an agent's tab at once, where the caller asked for it

An agent.launch now shows its terminal tab before admission and spawn, in
the requested placement (group and/or anchor tab), and the pane attaches to
the agent as soon as it runs. A pane whose agent can't start, or whose start
can't be confirmed, says so instead of becoming a plain shell, and keeps
saying so across restarts. A user's close of the tab or its pane during the
launch stops it and answers agent_launch_tab_closed. Whose view moves is
unchanged from main for every caller.

Rebuilt on main (with #24934) from the previous branch head 8f62858.

* style(runtime): one-line the tab-order map so the headless browser-tabs runtime stays under its line limit

The merge of main (#25724's emitMobileSessionTabsSnapshot metadata) plus this branch's close mark put
the file one line over max-lines. Formatting only.
brennanb2025 added a commit that referenced this pull request Oct 7, 2026
…agent.launch (#25624)

* feat(agent-launch): host-assigned caller identity and a launch record written when the surface exists

Step 1 of the agent-launch unification, on main.

- The dispatcher stamps every request's caller from what its connection proved (runtime socket:
  the local CLI; the desktop's IPC: the desktop; a paired socket: its device). Params never set it.
- The launch record is written twice: once when the tab exists (what creation settled: on the
  launch command, a draft, or a submit still `unconfirmed`), and again once the prompt's fate is
  known. A restart in between finds the running agent instead of answering "unknown".
- A replay re-derives its terminal handle from the pane key in the running host, and shows
  `unconfirmed` only to callers that advertise agent.launch.prompt-unconfirmed.v1.
- The record store opens in its own slot, without building the chat host; the chat host is built
  on that same store.

Rebuilt from this PR's own commits (b9adf88, 0d7d9b5, c83f44d, 39d15d5) onto
main, without #24080/#24081. Conflicts: the delivery doc table (main's "line fits" row plus the
`unconfirmed` row), and main's journal-database open in install(), which now goes through the
record-store slot.

* feat(agent-launch): show an agent's tab at once, where the caller asked for it

An agent.launch now shows its terminal tab before admission and spawn, in
the requested placement (group and/or anchor tab), and the pane attaches to
the agent as soon as it runs. A pane whose agent can't start, or whose start
can't be confirmed, says so instead of becoming a plain shell, and keeps
saying so across restarts. A user's close of the tab or its pane during the
launch stops it and answers agent_launch_tab_closed. Whose view moves is
unchanged from main for every caller.

Rebuilt on main (with #24934) from the previous branch head 8f62858.

* feat(agent-launch): the desktop AI buttons start their agent through agent.launch

Part 3 of 3 of the agent-launch unification, on main. The AI buttons (Fix
checks, the source-control actions, commit/push recovery, Explain commit,
notes and annotation sends, session continuation) started their agent from
the window. They now ask the host's agent.launch to start it, with no
prompt, into the tab the window makes at the click, under one operation id
per click; the window's pane waits for the host's agent instead of spawning
a shell. The window then pastes the prompt with main's own paste, moved out
verbatim and started only once the host's agent holds the tab, so delivery
and follow-ups are exactly main's. A typed new-tab prompt and launches while
chat is the default keep main's path. The source-control dialog drops its
launch-command preview, which the host builds now.

* style(runtime): one-line the tab-order map so the headless browser-tabs runtime stays under its line limit

The merge of main (#25724's emitMobileSessionTabsSnapshot metadata) plus this branch's close mark put
the file one line over max-lines. Formatting only.

* fix(agent-launch): watch an AI button's agent for readiness from its first output

The window started watching for the agent's ready signal only after the host
answered agent.launch, so an agent that had already turned on bracketed paste
by then was never seen ready and its prompt waited out the readiness budget.

Readiness is now watched from the moment the tab's terminal exists, as main
watches it; the paste is written, and the chat copy seeded, only once the host
says its agent started in that tab.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Closing this older worker-launch stack. #26724 replaced launch routing; create an issue for carrying the complete terminal-worker brief and confirming its first turn before closing.

The remaining problem is tracked in #27676.

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