New Threads & Where Work Happens v2

Every new thread makes it obvious where work happens and what it starts from — isolated by default wherever there's no local checkout in front of the user, visible and explicit where there is.

Revision of the original proposal · July 2026 · Migration/compat still out of scope

What changed from v1, and why

V1's thesis was "safe by default, explicit by exception," implemented as: every ordinary new thread gets a fresh worktree from a just-fetched origin/main, everywhere, with a blocking fetch at send time and a hard-fail dialog when the remote is unreachable. Four things move in this revision:

  1. The default is derived from the surface, not universal. Safety was never about worktrees per se — it was about the user never being surprised. A hidden worktree is itself a surprise when the user's editor is open on the checkout.
  2. Freshness is a window, not a blocking fetch. "Latest" means fetched within the last 30 seconds, made nearly-always true by prefetching when the composer opens. When we do have to wait, a first-class loading state carries it.
  3. Failure splits into fail-visible and fail-closed. A slightly stale base proceeds with provenance shown. Only genuinely stale or absent data — and any fallback to the current checkout — stops the world.
  4. Worktree ↔ thread association is a first-class record we own. Deterministic naming, tracked server-side, so lifecycle/GC (deferred) is attributable by construction. The four-scope settings architecture also decouples into its own phase so the behavior ships on today's settings.

Defaults by surface

One rule generates every default — no per-surface policy table to memorize:

The attention test. Is the user's attention attached to a local checkout they can see? If yes, work there — visibly. If no, isolate in a fresh worktree pinned to the latest remote default branch.

SurfaceDefaultRationale
Desktop app, opened on a local project Current checkout The user's editor is open on this folder. Changes landing in a hidden worktree read as "nothing happened" — more confusing than non-isolation is dangerous.
CLI, launched inside a repo Current checkout Same posture: the user is standing in the checkout. Matches every other CLI tool's mental model.
CLI, outside a repo / driving remote threads Fresh worktree No checkout in view; the CLI is acting as a launcher.
Web (app.t3.codes) Fresh worktree There is no local checkout in front of the user. Isolation is the only coherent option.
Mobile Fresh worktree Same as web.
Remote / cloud environments Fresh worktree Same as web; the environment owns the workspace entirely.

No onboarding question. Asking "worktrees or current directory?" during onboarding collects a coin flip from exactly the people it's meant to help — they haven't formed the concept yet. Instead, the composer's intent label is the teaching moment: on a user's first-ever thread per surface, the label gets slightly more visual weight so the choice is legible right when it's meaningful, and flippable in one click.

V1's invariant "New thread means the same thing across site, app, and CLI" is retired. Uniformity was standing in for predictability; the intent label delivers predictability directly. The replacement invariant: the composer always shows where work happens and what it starts from, on every surface.

The ordinary flow

  1. Composer opens. No prompts. Intent label shows the surface default. In worktree mode, a background git fetch fires immediately (deduped per remote — concurrent drafts share one in-flight fetch).
  2. User types. By the time they hit send, the prefetch has almost always landed.
  3. Send. Freshness check: if the last successful fetch is within the freshness window (default 30s), pin the SHA immediately — zero added latency. If stale, fetch now behind a visible loading state.
  4. Workspace materializes. Branch + worktree created from the pinned commit, under deterministic thread-derived names. Setup stages render inline in the thread: fetching → pinning → creating worktree → running setup commands. The wait is accounted for, never hidden.
  5. Agent starts only after setup completes. The intent label resolves from promise to fact: "Worktree · pinned at a1b2c3f (origin/main, 12s ago)".

Latency is acceptable here because the loading state is first-class. The freshness window and prefetch exist to make the common case instant, not to hide the work when it does happen.

Composer opens prefetch origin (background, deduped) Send Fetched within last 30s? yes Pin SHA create worktree no Fetch now visible loading state success fails Last good fetch within usable horizon (~1h)? yes Fail-visible proceed on last-known base, provenance banner shown no Fail-closed dialog nothing started · checkout untouched · options: retry · inspect · last-known (confirm) · CWD (confirm) Agent starts label shows pinned SHA

Send-time flow for worktree-mode threads. The green path is the overwhelmingly common case thanks to the prefetch.

Composer UX

The intent label sits beside the send button — descriptive, not a stateful toggle. It reads differently per surface default:

Start a new task. The workspace will be prepared on first send.
Fresh worktree · latest origin/main Change…
Web / mobile / remote — worktree default
Start a new task in this folder.
This folder · not isolated · uncommitted changes present Change…
Desktop app / CLI-in-repo — current-checkout default, dirty state disclosed inline

Failure behavior: fail-visible, then fail-closed

V1 had one absolute: no silent fallback, enforced by a modal whenever the remote was unreachable. That conflates two very different situations. The invariant splits:

Absolute, forever: never silently fall back to the current checkout. Crossing the isolation boundary always requires explicit confirmation.

Graduated: staleness of the base is a disclosure problem, not a stop-the-world problem — until it isn't.

Worktree ↔ thread association

We track this ourselves, as a first-class server-side record — not inferred from git config or reconstructed by scanning paths.

Phasing

Phase 1 — this pass ships the default experience

  • Surface-derived defaults via the attention test; retire sticky per-draft mode inheritance.
  • Freshness window (30s) + prefetch-on-composer-open + per-remote fetch dedup; inline setup-stage loading state.
  • Composer intent label (promise → fact), exception picker with draft-only paths + "Always for this project" escape hatch, dirty-state disclosure, first-send emphasis.
  • Fail-visible / fail-closed ladder.
  • Worktree registry + deterministic thread-derived naming.
  • Built on today's settings — extend defaultThreadEnvMode to per-surface resolution and fold newWorktreesStartFromOrigin into the freshness model. No new settings architecture required.

Phase 2 — settings scopes decoupled

  • V1's four-scope architecture (Account / Device / Environment / Project) with resolution + provenance stands as designed — it's a project of its own and the new-thread behavior must not wait on it.
  • Scope guidance carries over: secrets stay on the environment; policy syncs on the account; "Always for this project" writes from phase 1 slot cleanly into project scope when it lands.
  • Keep provenance UI proportionate: one resolved answer, scope shown on hover/inspection — resist per-control provenance chrome everywhere.

Back of mind — worktree lifecycle deferred, enabled now

  • GC policy, disk ceilings, merged-thread cleanup. Not this pass; the registry and deterministic naming from phase 1 are the down payment.

Invariants

  1. The composer always shows where work happens and what it starts from — on every surface. ("New thread means the same thing across site, app, and CLI"legibility replaces uniformity)
  2. No silent fallback to the current checkout, ever. Crossing the isolation boundary requires explicit confirmation.
  3. Worktree bases are pinned SHAs with visible provenance — including how fresh the base was and whether the fetch succeeded.
  4. Exceptions are draft-only. Defaults change only through settings, and only explicitly — the composer offers that path but never takes it for you.
  5. Every worktree is attributable to a thread by construction, via the registry and thread-derived naming.
  6. The agent starts only after the workspace is ready; the wait is shown as setup stages, never hidden; success is never claimed early.
  7. The connected environment remains the single server-side authority for validating, fetching, pinning, and creating workspaces.

Definition of done