Skip to content

feat(agent-launch): host-assigned caller identity and a launch record written when the tab exists (unification 1 of 3) - #24934

Merged
brennanb2025 merged 2 commits into
mainfrom
brennanb2025/agent-launch-identity-record
Oct 6, 2026
Merged

brennanb2025 merged 2 commits into
mainfrom
brennanb2025/agent-launch-identity-record

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 14 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​840 $\color{#cf222e}{\Huge{\mathbf{−}}}$​69 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​771
Prod 18 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​410 $\color{#cf222e}{\Huge{\mathbf{−}}}$​103 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​307

ELI5

When you ask Orca to start an agent with a prompt, Orca writes down "I'm starting this". If the reply gets lost, asking again gives back the same agent instead of starting a second one.

Until now Orca only wrote down the result at the very end, after the prompt was delivered. If Orca restarted while the new agent was still starting, the note said "unknown". The phone then showed "Couldn't confirm the agent started", even though the agent was running.

Now Orca writes the note twice: once the moment the agent's tab exists, and again once the prompt's fate is known. A restart in between finds the running agent. The note says the prompt is "unconfirmed" rather than "not sent", because Orca can't tell whether a paste cut off by the restart arrived, and "not sent" would invite you to send it twice.

The desktop app itself could not use this protection at all, because Orca had no way to name it as a caller. Now Orca gives every caller an identity based on how it is connected, and the desktop gets one too.

What Changed

Before: if the host restarted while a launch waited for the agent to be ready (the prompt waits up to a minute before it is pasted), the launch was recorded as "unknown". The agent kept running in its terminal, but every retry from the phone was refused, and the phone said "Couldn't confirm the agent started".

After: the record already holds the running agent. What a retry gets depends on the caller:

  • A caller that can read the new prompt word (the desktop, and the CLI, which ships with the host) gets the running agent's tab, with the prompt marked unconfirmed: don't resend.
  • Every other caller, including today's phone app, gets the same agent_session_operation_unknown answer as before. It is never told "not delivered", which would invite a duplicate turn. This PR adds no new on-screen notice.
  • Either way, nothing new starts.

Before: a replayed terminal launch carried the recorded handle (term_…). Usually that handle is still good after a restart: the local terminal daemon and the SSH relay both keep each PTY's handle, and the restarted app takes it back. But it doesn't always take it back. It drops the old handle when the PTY's process was replaced, and it keeps a different handle when it had already issued one for that pane before it read the daemon's list. In those cases the recorded handle points at nothing, even though the pane is still running.

After: a replay looks the pane up by its recorded pane key in the running host and returns the handle that host uses for it now. Where the handle survived, which is every live run on macOS, that is the same handle. If the pane is gone, the recorded handle comes back, and terminal.read on it reports the terminal as exited, which is true. Shipped phones reject a reply with no handle, so it can't be left out.

Before: if the final write failed (database busy, disk full), the row kept whatever the first write said. With "not delivered" that told a retrying phone to resend a prompt that had already arrived.

After: the row keeps unconfirmed, answered per caller as above.

Before: a desktop launch naming an operation id would be refused with agent_session_identity_required, because the desktop window arrives without a paired device. Nothing sends one yet; #25624 (step 3 of the unification) does, for the desktop AI buttons.

After: the desktop is admitted under its own identity and gets the same "retry never starts a second agent" guarantee as the phone.

Before: admitting any replay-safe launch, even a plain terminal one, first built the whole chat host (Codex and Claude adapters, the host, the model catalog) just to reach the launch record.

After: admission opens only the record store. The chat host, when something needs it, is built on that same store, so there is still exactly one copy of it.

Mechanism, in four parts:

  1. One caller identity, assigned by the host. The dispatcher stamps caller on every request from what the connection proved: the runtime socket is the local CLI, the desktop's IPC channel is the desktop, a paired socket is its device. Request params never set it. A connection that declares a client but can't name it gets no identity, and replay safety refuses it as before. The launch record's namespace and "whose screen moves" both read this one identity.
  2. The launch record is written twice.
    • The first write happens when the executor publishes the tab. It records the launch as it stands: surface, workspace, mode and warning. Its prompt result says only what creating the surface settled: handed-to-terminal if the prompt rode the launch command, not-delivered for a draft (the host never delivers drafts), and unconfirmed for a submit still owed.
    • It is fired without waiting, so the prompt is never held up by bookkeeping. The record store runs its writes in order, so the final write after delivery still lands last.
    • The row shape is unchanged.
  3. A replay is presented for its caller.
    • A terminal handle is re-derived from the pane key in the running host.
    • An unconfirmed prompt reaches only a caller that advertises agent.launch.prompt-unconfirmed.v1; every other caller is refused with agent_session_operation_unknown.
    • A live (non-replayed) answer never says unconfirmed.
  4. The record store has its own slot. It opens without the chat host, and the chat host is built on it. Stopping the runtime closes it whether or not a chat host was ever built.

What a retry answers, by where the host stopped:

  • after admission, before the tab was recorded: "unknown", as before (a pane may exist that nothing recorded)
  • after the tab was recorded, during the readiness wait or the paste, or after the paste but before the final write:
    • the running agent, with the prompt unconfirmed, to a caller that reads it
    • "unknown" to every other caller
    • when the prompt rode the launch command, the running agent with handed-to-terminal, to everyone
  • after the final write: the final answer
  • any time, when only the reply was lost: the same answer, and the launch never runs twice

Wire:

  • One new optional prompt word, unconfirmed, sent only to callers that advertise the new capability agent.launch.prompt-unconfirmed.v1. Hosts advertise it too.
  • The desktop's own IPC advertises it. The CLI is treated as reading it because it ships with the host.
  • No params, required fields or protocol version change. An older build that reads a row holding the new word rejects it and answers agent_session_operation_unknown, the same answer it gave before.

Why

The common pattern:

  • records a start before, or as, the thing it starts comes to exist, then records its progress, so a restart finds what was started instead of losing it;
  • keeps "was in flight when the process ended" distinct from "never attempted";
  • takes the caller's identity from the authenticated connection, never from the request.

This PR moves the launch record and the caller identity onto that pattern.

Alternatives considered:

  • Report a cut-off prompt as not-delivered (this PR's first version): rejected, because a host that stopped mid-paste can't know that. Today's phone app reads "not delivered" as "prompt wasn't sent" and offers to resend, which can make a duplicate turn.
  • Send unconfirmed to every caller: rejected. Shipped phones validate the prompt word and would reject the whole reply. They get the answer they always got for this state instead.
  • Omit the handle when the pane is gone: rejected. Shipped phones require a non-empty handle, so the reply would fail validation.
  • One identity per desktop window: rejected, because a reload or an app restart would change the caller and break the replay.
  • Keep admission on the chat host and only cache it: rejected, because the record doesn't belong to the chat host, and terminal launches have no use for the chat host.

How this differs from the common pattern:

  • Intended: records are kept per caller, while the common pattern keys receipts by id alone. The per-caller limit of 512 rows needs the split, and it also stops one device's guessed id from reading another device's answer.
  • Intended: the first write lands just after the tab exists, not in the same transaction that creates it. A tab is a terminal or a chat process, not a database row, so the two can't commit together. The short gap answers "unknown", which is true.
  • Intended: a launch interrupted by a restart is not resumed. Pasting the prompt into a pane after a restart, without proof the agent is in front, is the blind paste this plan rules out. The caller is told the prompt is unconfirmed instead.
  • Intended: only callers that read unconfirmed get the running agent after a restart mid-delivery. Today's phone app keeps getting "unknown". It gets the new answer once it advertises the capability and has UI for it, which is not part of this plan.
  • Intended: the desktop is one identity for every window. The local desktop has no separate sessions, and per-window identities would break replay across a reload.
  • Intended: a reused terminal (reuseTerminal) replays the handle its caller sent, since that launch minted no pane to look up.
  • Intended: two record-store edges exist only once quit has begun, the one production caller of stop:
    • a stop whose chat-host teardown fails leaves that host's store open while the slot is emptied;
    • a store opened after stop is closed only by process exit.
    • They are noted in the slot module.
  • Temporary: "no declared client means the local CLI" is how the runtime socket, the SSH remote CLI bridge and browser automation all get the CLI's identity, and how the CLI is assumed to read unconfirmed. A new transport that forgets to stamp its caller would inherit it. Unchanged from the base, but this PR makes it the identity model. Removed before plugins ship (plan §4): the runtime socket stamps local-cli itself, and no stamp means no identity.
  • Temporary: the gap between admission and the first write answers "unknown". Removed by feat(agent-launch): show an agent's tab at once, where the caller asked #25430 (step 2 of the unification), which creates the tab under the caller's id before the spawn, so admission can record the pane up front.
  • Temporary: agentSession.* still keys phones by their rotating credential rather than this identity. Moving it re-partitions rows already saved on disk, so it needs a migration. Removed by an agentSession.* caller-key migration PR after the unification; it has no ticket yet.

Plan deviations for this step:

Where this PR sits. This is step 1 of the agent-launch unification, built directly on main: every way of starting an agent goes through one host launch point. The steps:

  1. This PR (feat(agent-launch): host-assigned caller identity and a launch record written when the tab exists (unification 1 of 3) #24934): one host-assigned caller identity, and a launch record written when the surface exists.
  2. feat(agent-launch): show an agent's tab at once, where the caller asked #25430: placement and the instant tab.
  3. feat(agent-launch): the desktop AI buttons start their agent through agent.launch #25624: the desktop AI buttons go through agent.launch. Note for that step, not built here:
    • Every desktop window shares one identity, so all desktop launches share one 512-row, 24-hour cap. A chat launch uses at least two rows: the launch, plus the attach inside it.
    • A capacity refusal there must not block the click.

This PR does not depend on #24080 (the first prompt on the agent's command line) or #24081 (worker briefs on the launch line). Both are parked and come back later as their own PRs on top. A launch delivers its prompt exactly as main does today (#24257's rule: a short single line rides the command line, anything else is pasted once the agent is proven in front), and this PR only records it.

Scope: the host's launch record, the caller identity, how a replay is presented, and where the record store opens. No renderer change: the desktop does not call agent.launch until #25624.

Linked Issue

Part of the agent launch plan; no separate issue.

Visual Proof

N/A: no UI changes. What changes is the answer a retried launch gets after a host restart, which the tests below cover.

Testing

Rebuilt on main (2026-10-06). This PR was previously stacked on #24081. It is now this PR's own changes only (the 4 commits b9adf88, 0d7d9b5, c83f44d and 39d15d5, 32 files) applied fresh to main 842c6c6. Two conflicts, both from main's own changes:

  • agent-launch-prompt-delivery.ts: the doc table keeps main's "line fits" row and adds the unconfirmed row.
  • structured-agent-session-runtime.ts: main now opens the journal database in install(). The chat host now takes the record store, and the journal database it sits on, from the slot, which opens it with main's own openStructuredAgentSessionJournalDatabase. A failed install leaves the store to the slot, which the runtime's stop closes, as before.

Checks on the rebuild (82544cb):

  • tc:node, tc:web and tc:cli pass.
  • Every root test file that imports a changed module, plus tests/e2e/cross-version-wire: 404 files, 3,668 passed, 3 skipped by their own guards, 0 failed.
  • The 6 mobile tests that import a changed module: 70 passed.
  • oxlint, oxfmt and the changed-lines quality gate are clean on the 32 files.
  • The RPC params catalog and the reliability-gate manifest are unchanged. The lockfiles are untouched.

The live QA below ran on the stacked base. On main, more prompts are pasted (anything over 512 bytes), so the restart-mid-paste row is being re-run on this rebuild.

Live QA, base vs this PR, on dev builds with stub agents (macOS; admission timing also on Windows; details in #24934 (comment)):

  • Restart during the readiness wait: the CLI gets the running agent with the prompt unconfirmed. A phone-shaped caller without the new capability gets agent_session_operation_unknown, as on the base. No second agent, and nothing typed after the restart. On the base every caller got "unknown".
  • A reply lost three times: one agent per caller, the same handle every time.
  • A spoofed identity in params: ignored. Each caller gets its own agent, and the record keys come from the connection.
  • First-launch admission: 52–63 ms median on both machines, inside the base's spread. It doesn't explain the Windows +2.5 s first launch.
  • Gone pane: the replay returns the recorded handle, which reads as exited.
  • Handles across a restart: on macOS with the terminal daemon, every surviving pane kept its handle, so re-deriving it from the pane key changed nothing visible there.
  • Not reached live: the "pasted, but the final write hadn't landed" window, which only the tests below cover.

New tests run against the real on-disk launch record. A "restart" means the store is reopened from disk and a new runtime takes over. The tests make that runtime issue a new handle for the surviving pane, which is the case where it can't take the old one back.

  • Host dies mid-paste:
    • a caller that reads unconfirmed gets the running agent under the new host's handle, with the prompt unconfirmed, and nothing new starts
    • a caller that doesn't is refused with agent_session_operation_unknown, as before; the same row still answers the upgraded caller
  • Host dies after the paste returned, before the final write: unconfirmed, or "unknown"; no second paste
  • The restarted host no longer has the pane: the recorded handle comes back
  • Host dies before the tab was recorded: "unknown", nothing starts
  • Host dies after delivery: the final answer, under the new host's handle
  • The final write fails: the live answer is still handed-to-terminal, a retry is unconfirmed or "unknown", never "not delivered"
  • Both writes queued before either commits: they land in order, and the final one stands
  • The first write fails: the launch still completes, and the final write lands
  • The desktop reads unconfirmed after a restart, through its real IPC capability list
  • Retries:
    • a reply lost three times (twice on the same host, once across a restart) → one agent
    • three retries while the first is still running → one agent
  • Identity:
    • the desktop's IPC options are admitted under trusted-local:desktop and replay
    • a paired device can't set its identity through params
    • a connection that can't name its caller is refused
  • Record store:
    • a terminal launch opens the record store, not the chat host
    • one row per launch, still retained from admission
    • the desktop is held to the same 512-row cap
    • the slot: shared with the chat host, closed by stop, refuses a second profile, retries after a failed open
  • Executor: publishes before delivery; a submit is recorded unconfirmed and a draft not-delivered

Each key test was ablated in production code and goes red. Round 2:

  • replay keeps the recorded handle → 5 red
  • first write records not-delivered (this PR's first version) → 8 red
  • unconfirmed sent to every caller → 3 red
  • first write fired late, after the final write → 7 red
  • desktop stops advertising the new capability → the desktop IPC restart test and the capability-list pin go red

Round 1:

  • no first write → 4 red
  • transport identity ignored → red
  • admission installs the chat host → red
  • unnamed connection trusted → red
  • dispatcher reads the identity from params → red
  • no record lookup on retry → 12/12 red
  • no per-caller cap → red
  • IPC stops naming the desktop → red
  • chat host opens its own store → red
  • stop leaves a store-only slot open → red
  • failed open blocks retries → red

Measured admission cost on this Mac, fresh profile, 15 rounds × 3 runs (medians):

  • full chat host: 6.02–6.19 ms
  • record store alone: 5.78–5.86 ms
  • Opening the journal database is most of it either way.
  • Building the chat host costs ~0.2–0.4 ms on macOS, which does not explain the +2.5 s first launch seen on Windows. Step 6's first task, measuring where that time goes, still stands.

Checks run:

  • Root named-file run: every test that imports a changed module, plus orca-runtime.test.ts and tests/e2e/cross-version-wire. 399 files: 4,980 passed, 4 skipped, 0 failed.
  • Mobile:
    • every test that touches agent launch, replay or the host dispatcher: 24 files, 259 passed
    • mobile tsc passes, and the tests-typecheck ratchet is OK
    • the mobile lockfile is untouched
  • Typechecks: tc:node, tc:cli and tc:web pass.
  • Lint and catalogs: oxlint and oxfmt are clean, the changed-lines quality gate has 0 findings on 32 files, and the RPC params catalog is unchanged.
  • First push: it failed CI's mobile verify because two mobile suites still put the record store inside the chat host. That was the test harness, not the phone:
    • a real paired phone is authenticated by its device token;
    • the WebSocket handler passes that device's id to the dispatcher (runtime-rpc-websocket-dispatch.ts), so the record key is the same device id as before this PR;
    • a mobile test drives a create through the real WebSocket handler and checks that key.

AI Disclosure

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • SSH: an SSH workspace's launch is admitted on the local host, as before. The SSH-relayed CLI keeps its existing identity and its saved records.
  • Mobile: shipped phones get either a normal result they already read, or the same "unknown" refusal as before. They are never sent the new word.
  • Mixed versions: one new capability and one new optional prompt word, sent only to callers that advertise it. The record row keeps the shape older builds validate. An older build reading a row with the new word answers "unknown", as it did before.
  • Windows: no platform-specific code.

Author: @BrennanKB5

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)

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Live QA: base (#24257 4523a4b3c4e) vs this PR (c83f44dd724)

What was tested: an Orca restart during a launch, a lost reply, a closed pane, caller identity, and first-launch timing. Rows 1–5 ran on macOS; timing ran on macOS and Windows.

Setup:

  • Hidden dev builds, each with its own isolated profile and home folder.
  • A stand-in Claude agent that can delay its ready screen (no real agent).
  • Each request was sent as the local CLI, or as a paired phone-shaped device over the real paired connection, with or without agent.launch.prompt-unconfirmed.v1.
  • To restart, I force-killed the app process, then relaunched it on the same profile.

Results

Row Base This PR Verdict
1. Orca restarts while the launch waits for the agent to be ready (2 KB prompt, so it's pasted after readiness) every retry gets agent_session_operation_unknown CLI retry gets the running agent: live handle, prompt unconfirmed. Phone without the capability gets agent_session_operation_unknown, as on base. The same device advertising it gets the running agent PASS: one agent per launch, nothing pasted after the restart, on both
1b. Same, but the prompt rode the launch line handed-to-terminal to every caller same same as base
2a. Restart after the final write recorded answer, same live handle same PASS, same as base
2b. Restart 0–1.6 s after the agent drew its ready screen the still-waiting launch: unknown. The launch whose prompt was already pasted: handed-to-terminal still-waiting launch: unconfirmed (CLI), unknown (phone without the capability). Pasted launch: handed-to-terminal PASS. The "pasted but not yet recorded" moment was never hit (6 tries)
3. Reply lost 3 times, same operation id retried 1 agent per caller, same handle every time same PASS
4. Pane closed after the restart, then retried recorded handle comes back; reading it returns status:"exited" same PASS, but it reads "exited", not "not found" as this body says
5. Two paired devices + the CLI, same operation id; params claiming device A's identity separate record and agent per caller; the claim is ignored same PASS
6. First-launch admission time on a brand-new profile (median ms) macOS 75 cold / 126 first real launch; Windows 58 / 162 macOS 52 / 125; Windows 63 / 160 Same within noise. With a 450-row ledger it's still under 110 ms. Admission doesn't explain the Windows +2.5 s first launch.
7. Phone path Only a scripted phone-shaped client, before and after a restart (rows 1–5). The phone app's UI was not driven
SSH BLOCKED (no accepted key)

Different from what the body says

  • Handles survive a real restart, on both arms. After force-killing the app on macOS, every surviving pane kept its term_… handle, and base's replay returned a live one. So looking the handle up again by pane key made no visible difference here. The tests model a restart as new handles; this setup didn't produce that. Not checked on Windows or SSH.
  • A pane that's gone reads back as exited, not "not found". Both arms behave the same way, and it's still truthful.

What a user would notice (same on both arms)

After a restart during the readiness wait, the agent keeps running with no prompt, and the desktop shows nothing about it. On this PR, a CLI retry now says unconfirmed. In every run here the prompt really hadn't arrived.

Not verified

  • A paste that landed before the final write.
  • A final write that fails.
  • The desktop identity: nothing on this stack calls it yet.
  • Rows 1–5 on Windows or SSH.
  • The phone app's UI.
  • Real agents.

Machine state: the real config files are unchanged on Windows. On the M4Air, ~/.codex/config.toml changed once during the run (15:30). No Codex ran in the rig, and other sessions' Codex processes were live on the box. It was not touched.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 4, 2026 23:31

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

Reviewed changes

  • Host-assigned caller identity — new src/main/runtime/rpc/rpc-caller-identity.ts. The dispatcher stamps caller from the authenticated transport on both unary and streaming paths; the launch-ledger namespace (agentLaunchOperationCallerKey) and caller navigation read it instead of clientKind/pairedDeviceId. The desktop IPC is admitted as trusted-local:desktop, which previously could not name itself.
  • A launch record written when the surface exists — onSurfacePublished now carries a complete provisional AgentLaunchResult; settledAtCreation writes handed-to-terminal (prompt rode the launch command), not-delivered (draft), or unconfirmed (submit still owed). Admission exposes record beside settle, fired un-awaited but ordered by the store's serialized transactions.
  • Replay presentation — an unconfirmed prompt reaches only callers advertising agent.launch.prompt-unconfirmed.v1 (the desktop; the CLI is assumed to read it); every other caller keeps agent_session_operation_unknown, never not-delivered. A replayed terminal handle is re-derived from its pane key, falling back to the recorded handle.
  • Record-store slot — the ledger opens on its own without the chat host; the host is later built on the same store; stop closes it exactly once.
  • Tests — new restart-replay and slot suites cover host death mid-paste and after the paste, a failed first/final write, spoofed identity in params, and the desktop's capability path.

ℹ️ The slot header documents two quit-phase edges; a third interleaving is latent

Technical details
# Slot release during `stop`'s await

## Affected sites
- `src/main/runtime/agent-session-record-store-slot.ts:9-12` — header documents (a) a failed host teardown leaving a store open in an emptied slot and (b) a store opened after stop. It does not note a third: an admission or `ensureStructuredAgentSessionHost` that begins during `stop`'s `await pending` still sees the old `opening`, gets the old store, and (for install) can build a host that `stop`'s `outstanding` snapshot does not contain.
- `src/main/runtime/structured-agent-session-runtime.ts:188-204` — `stop` captures `installing`/`pendingTeardown` before awaiting, then releases the slot. A runtime registered after that snapshot is invisible to the identity guard, so the store can be closed under a live host or left open until process exit.

## Required outcome
- No behavior change required for this PR: `stopStructuredAgentSessionRuntime` has a single production caller (`main-process-quit.ts`), so the interleaving is only reachable once quit has begun, like the two edges already documented.
- If `stop` ever gains another caller, either serialize it (re-entrancy flag) or re-check the slot/registry after the await.

## Open questions for the human
- Is it worth documenting this third quit-phase interleaving alongside the existing two, so a future caller knows the invariant?

Pullfrog  | 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
brennanb2025 added a commit that referenced this pull request Oct 5, 2026
brennanb2025 added a commit that referenced this pull request Oct 5, 2026

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

Reviewed changes

Since the prior Pullfrog review at 39d15d5, the branch absorbed its base branch's new head in three sync merges (13de37c8, de6df623, c153690). The only authored change in that delta is a capability-list reconciliation:

  • Reconciled the desktop capability test with main's restructure — main's #24204 now advertises the structured-chat capabilities to paired hosts, so LOCAL_ONLY_BY_DECISION in desktop-renderer-runtime-capabilities.test.ts was reduced to just this branch's AGENT_LAUNCH_PROMPT_UNCONFIRMED_RUNTIME_CAPABILITY, and the local-only assertion now pins exactly that (the production capability list is unchanged since the prior review).
  • Absorbed base/main changes — the merges bring in the base branch's worker-brief/Grok readiness work and main's agent.launch opencode-model guard, getTitleDisplayClear, and error-code propagation. Per-file git diff against the prior review confirms the launch-record/identity production code (rpc-caller-identity.ts, agent-session-record-store-slot.ts, agent-launch-replay.ts, structured-agent-session-runtime.ts, agent-launch.ts, agent-launch-executor.ts) was not authored by these merges and merged cleanly.

ℹ️ The one red check is inherited from main, not this PR

static analysis and typecheck fails at verify:localization-coverage, flagging src/renderer/src/lib/worktree-creation-hook-preparation.ts:26 and :51 — a local helper named confirm trips the checker on its 'setup'/'vmRecipe' arguments. That file is byte-identical on the base tip (f9bbc16b4b) and main (40f9e7a3fcd) and is not touched by this PR; the regression comes from main's #25472. It needs clearing on main or the base branch before this PR's verify can go green.

Technical details
# Localization-coverage false positive inherited from main

## Affected sites
- `src/renderer/src/lib/worktree-creation-hook-preparation.ts:26` — `await confirm('setup')`
- `src/renderer/src/lib/worktree-creation-hook-preparation.ts:51` — `await confirm('vmRecipe')`
- The local `confirm` helper is defined at line 17; its name collides with the checker's `USER_VISIBLE_FUNCTION_NAMES`, so the non-copy literal args are flagged.
- `config/localization-coverage-allowlist.json` has no entry for this file, on the base tip or on `main`.

## Required outcome
- No change required in this PR. Renaming the local helper (or adding a reviewed allowlist entry) on main/base clears `verify:localization-coverage`.

## Open questions for the human
- Should the fix land on main (#25472's regression) or on the base branch `brennanb2025/agent-launch-host-prompt-delivery` so this PR can go green?

Pullfrog  | 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
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
brennanb2025 changed the base branch from brennanb2025/agent-launch-host-prompt-delivery to brennanb2025/worker-brief-on-launch-line October 5, 2026 15:40
brennanb2025 added a commit that referenced this pull request Oct 5, 2026
brennanb2025 added a commit that referenced this pull request Oct 5, 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
brennanb2025 force-pushed the brennanb2025/agent-launch-identity-record branch from 7d98ac4 to 82544cb Compare October 6, 2026 17:34
@brennanb2025
brennanb2025 requested a review from Jinwoo-H as a code owner October 6, 2026 17:34
@brennanb2025
brennanb2025 changed the base branch from brennanb2025/worker-brief-on-launch-line to main October 6, 2026 17:34
@coderabbitai

coderabbitai Bot commented Oct 6, 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: c58eaf26-08f6-4502-a10d-3f9e6857dc32
📥 Commits

Reviewing files that changed from the base of the PR and between 82544cb and 757d7af.

📒 Files selected for processing (1)
  • src/main/agent-launch/agent-launch-executor.test.ts

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


📝 Walkthrough

Walkthrough

Agent launches now record a complete creation-time result before prompt delivery. Replay uses transport-assigned caller identity and can report an unconfirmed prompt outcome to callers with the required capability. The runtime shares a process-wide agent-session record store between launch admission and structured-session installation. Tests cover restart replay, caller identity, prompt outcomes, and record-store lifecycle.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 757d7

No merge-blocking issue was identified; the change is ready for normal merge checks.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 46 functions across 32 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 describes the two main changes: host-assigned caller identity and recording a launch when the tab exists. It is lengthy, but remains specific and understandable.
Description check ✅ Passed The description is detailed and covers the change, rationale, testing, visual-proof status, compatibility, and checklist. It states that there is no separate linked issue; the template permits this fo…
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 added a commit that referenced this pull request Oct 6, 2026
…ed 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.
@brennanb2025 brennanb2025 reopened this Oct 6, 2026
@brennanb2025 brennanb2025 changed the title feat(agent-launch): host-assigned caller identity and a launch record written when the tab exists (step 5 of 7) feat(agent-launch): host-assigned caller identity and a launch record written when the tab exists (unification 1 of 3) Oct 6, 2026
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Ready for review: live QA passed, and CI is green on 757d7af146b.

This is step 1 of 3 of the agent-launch unification, built directly on main (#24934, then #25430, then #25624). It doesn't depend on #24080 or #24081. Prompt delivery is unchanged from main: #24257's rule puts a short single line on the agent's command line and pastes anything else once the agent is proven in front. This PR only records what happened.

Live QA ran on a macOS box (main 842c6c667a8 vs this PR at 82544cbb3df; the later merge of main touched no launch code). Each arm was a hidden dev app with its own profile. The stub agent takes 45 s to draw its ready screen. There were two callers: the real CLI over the runtime socket, and a real paired, end-to-end-encrypted phone connection carrying the shipped mobile capability list.

R1: the host restarts mid-paste. The prompt was 2,064 B, so it is pasted after ready on both arms. The host was killed about 7.7 s into the readiness wait, then relaunched, and the same operation ids were replayed.

Check main this PR
CLI replays its launch agent_session_operation_unknown the running agent, under the same live handle, with the prompt unconfirmed
Phone replays, shipped capabilities agent_session_operation_unknown agent_session_operation_unknown (unchanged)
The same phone advertising agent.launch.prompt-unconfirmed.v1 agent_session_operation_unknown the running agent, with the prompt unconfirmed
Agents started one per launch one per launch
Typed or pasted after the restart (watched until 20 s past ready) nothing nothing

R2: identity can't be spoofed. Two paired devices and the CLI sent the same operation id. Then two of them sent params claiming to be the third (caller, pairedDeviceId, deviceId, callerKey, clientKind). On both arms, each caller got its own agent, and the records are keyed by the real connection. Same as main.

Nothing was found worse than main.

Not verified live:

  • the window where the paste landed but the final write hadn't (unit tests only);
  • a failed final write (unit tests only);
  • a restarted host issuing a new handle for a surviving pane (handles survived here, so the pane-key lookup gave the same handle);
  • Windows, SSH, the phone app's own UI, and real agents.

CI on 757d7af146b: all green. Static analysis and typecheck, unit shards 1 to 5, cross-version wire, package (Linux and Windows), native-smoke (Windows and Ubuntu), and verify. 21 passed, 0 failed.

@brennanb2025
brennanb2025 merged commit e1e9d1c into main Oct 6, 2026
37 checks passed
brennanb2025 added a commit that referenced this pull request Oct 6, 2026
…t-launch-instant-tab

Conflicts resolved against #24934's rebuild (82544cb), which this branch was built on, so each
keeps main's squashed #24934 plus this branch's change. agent-launch-floating-workspace.test.ts takes
main's #22302 routing (a structured preference opens a Codex chat) with this branch's host-derived
viewMode on the terminal branch. The record-store slot test gets the resolveLaunchArgs dep #25721 made
required, the same line as main's fix.
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.
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