Skip to content

feat(do): terminal-free background-agent spawn backend ($FLOW_TERM=bg)#74

Merged
anshulsao merged 6 commits into
mainfrom
bg-spawn-backend
Jun 10, 2026
Merged

feat(do): terminal-free background-agent spawn backend ($FLOW_TERM=bg)#74
anshulsao merged 6 commits into
mainfrom
bg-spawn-backend

Conversation

@anshulsao

@anshulsao anshulsao commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

What

A first-class $FLOW_TERM=bg spawn backend so users who live in Claude Code's Agent View (claude agents) can have flow do launch sessions as terminal-free background agents instead of opening a terminal tab.

Why

A claude alias that injects --bg makes claude ignore flow's pre-allocated --session-id and mint its own. flow records a phantom id, and the next flow do re---resumes a session that never existed. Rather than fight the alias, flow now spawns claude --bg deliberately (via Go exec, which never applies the user's interactive shell alias) and captures the real, harness-minted session id from the agent registry — restoring the DB-authoritative binding contract.

How

  • spawner.IsBackground()$FLOW_TERM=bg selector (+ a test override). bg is its own predicate, not a Detect() terminal backend: it bypasses SpawnTab entirely.
  • harness.BackgroundLauncher — new optional capability interface (SpawnBackground / ResumeBackground / BackgroundAgents). Only the claude adapter implements it; $FLOW_TERM=bg against a codex/gemini-pinned task fails cleanly — no silent tab fallback.
  • claude adapterclaude --bg --name "<project>/<task>" <prompt>, parse the backgrounded · <shortId> · <name> banner, then resolve the full session id via one claude agents --json --all lookup (no polling — the banner only prints after the session is registered). The spawn runs in the task's work_dir (cmd.Dir), so the agent starts in the right repo.
  • do.go — bg branch parallel to --auto: capability gate; spawn/resume protocol; record the captured real session id + harness.
  • flow show — live bg_status: <status> / <state> (pid …). flow list — bg agents with a live pid fold into the [live] marker.
  • transcript — resolves a bg session's jsonl by globbing the globally-unique session id (~/.claude/projects/*/<sid>.jsonl), so it works even when a bg session relocates into a git worktree.

Spawn / re-run / resume protocol

Re-running flow do <task> is idempotent and mirrors the Agent View:

State Action
No session yet (or --fresh) Fresh --bg spawn; capture + record the real id
Session live in the registry (process up) Don't spawn/resume — just report it's open in the Agent View
Session not running (stopped/failed/done) or removed claude --bg --resume <oldid> forks a fresh process seeded from the saved transcript; capture + re-record the new id (history inherited)

claude --bg --resume does not preserve the id (verified against the live CLI + Agent View docs): --bg manages its own id. Plain claude --resume keeps the id but isn't a background agent, so it can't be used in bg mode. Hence flow re-records the new id — the task never points at a dead session.

--with / --with-file

Work on bg spawn and resume — the instruction rides via opts.Inject and is appended behind the shared [via flow do --with] marker, exactly like the interactive path.

Testing

TDD throughout, behind a BGCommandRunner seam fed banner/JSON captured from the live CLI; a package TestMain keeps the suite hermetic (no real claude; and spawner.BackgroundOverride=false so an ambient $FLOW_TERM=bg in the dev env doesn't leak into terminal-path tests). make test green, go vet clean.

Verified end-to-end against the real binary:

  • fresh spawn captures the real id (no phantom), bg_status live in flow show, [live] in flow list, agent runs in work_dir
  • re-run while live → "open in your Agent View" (no duplicate)
  • stop / claude rm → re-run resumes: forks, re-records the new id, registers in the Agent View, inherits the prior conversation (old marker present in the resumed transcript)
  • --with delivers on both spawn and resume (injection marker + obeyed instruction in the transcript)
  • flow transcript resolves the jsonl (incl. the worktree-relocation case)
  • non-claude harness → clean capability-gate error

Out of scope (per brief)

🤖 Generated with Claude Code

anshulsao and others added 6 commits June 9, 2026 23:17
Add a first-class `--bg` spawn backend so users who live in Claude Code's
Agent View can have `flow do` launch sessions as background agents instead
of opening a terminal tab.

The user's `claude` alias injects `--bg`, which makes claude ignore flow's
pre-allocated `--session-id` and mint its own — leaking a phantom id so
`flow do` re-resumes a session that never existed. This backend stops
fighting the alias: flow spawns `claude --bg` deliberately (via Go exec, so
the interactive alias never applies) and captures the REAL session id from
the registry.

- spawner.IsBackground(): $FLOW_TERM=bg selector (+ BackgroundOverride test
  seam). bg is its own predicate, not a Detect() backend — it bypasses
  SpawnTab entirely.
- harness.BackgroundLauncher: optional capability (claude implements it;
  codex/gemini don't → clean error, no silent tab fallback).
- claude bg impl: `claude --bg --name <title> <prompt>`, parse the
  `backgrounded · <shortId> · <name>` banner, resolve the full session id
  via one `claude agents --json` lookup (no polling — the banner only
  prints after registration). Resume by id; report already-running.
- transcript: resolve bg/worktree sessions by globbing the globally-unique
  session id (`~/.claude/projects/*/<sid>.jsonl`) when the cwd-encoded path
  misses — handles the worktree-relocation gotcha.
- do.go: bg branch parallel to --auto; capability gate; record the captured
  real session id + harness.
- flow show: live `bg_status:` (status/state/pid); flow list: bg agents
  fold into the [live] marker.

TDD throughout, behind a BGCommandRunner seam with banner/JSON captured
from the live CLI; a package TestMain keeps the suite hermetic (no real
claude). Validated end-to-end against the real binary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add bg mode to the "What you get" tab bullet, a dedicated "Background
agents (FLOW_TERM=bg)" subsection under how-it-works (spawn + real
session-id capture, idempotent resume / already-running, no-tab attach
via Agent View, live bg status in show/list, transcript glob, Claude-only
capability gate), and the supported-hosts line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…idance for present sessions

`claude --bg --resume <id>` does NOT preserve the session id (verified
against the live CLI and the Agent View docs): --bg manages its own id, so
resume starts a fresh background process that inherits the prior
conversation under a NEW id. The previous code ran `--bg --resume <oldid>`
but kept recording the old id — re-introducing the exact phantom-id leak
this feature exists to fix (flow pointed at a dead session; a real orphan
agent was observed in testing).

Corrected, doc-aligned behavior:

- BackgroundAgents() now queries `claude agents --json --all`, so exited /
  failed / stopped / completed sessions are visible — flow can tell
  "removed" from "present but not running".
- Re-run of a task whose session is PRESENT in the registry (any state)
  no longer spawns: a bg session is recovered by attaching in the Agent
  View (a stopped/failed one restarts from where it left off, keeping its
  id), so flow points the user there. Message distinguishes live
  ("Already running … attach") from exited ("in your Agent View
  (stopped) — attach to resume").
- Re-run when the session is ABSENT (rm'd / untracked) forks it back via
  `claude --bg --resume <oldid>`, and ResumeBackground now CAPTURES and
  returns the new minted id (history inherited) so flow re-records it —
  never pointing at the dead id again.
- liveSessionsForTasks counts a bg agent as [live] only when it has a
  running pid (>0); exited-but-listed sessions no longer false-positive.

ResumeBackground's signature changes to return (BackgroundAgent, error).
Spawn and resume now share launchAndCapture (banner parse → registry
lookup). Validated end-to-end on the real binary across all three
branches (live / stopped-present / removed). README bg section updated to
describe the real resume semantics. make test green, go vet clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rt / else→fork+recapture

Two fixes from live testing of the bg backend:

1. work_dir bug: the bg spawn exec'd `claude --bg` without setting the
   process cwd, so the agent started in whatever directory flow was invoked
   from (e.g. a worktree) instead of the task's work_dir — wrong repo,
   wrong CLAUDE.md, and (because bypassPermissions is accepted per-context)
   it blocked on a permission prompt even with --dangerously-skip-permissions.
   BGCommandRunner now takes a workDir and sets cmd.Dir; SpawnBackground /
   ResumeBackground take work_dir and run there (agents --json stays
   cwd-independent). The interactive and --auto paths already did this.

2. Resume model, simplified to match how the Agent View actually works
   (no attach — attach is interactive, flow can't drive it headlessly):
     - session still LIVE in the registry (process up, pid present) →
       don't spawn/resume; just report it's open in the Agent View.
     - session not running (stopped/failed/done) OR removed → fork it back
       with `claude --bg --resume <oldid>` and re-record the new minted id
       (history inherited). Plain `--resume` would keep the id but wouldn't
       be a background agent, so it can't be used in bg mode.

Verified end to end on the real binary: agent now runs in work_dir;
--with delivers on both spawn and resume (injection marker + obeyed
instruction in the transcript); a removed session's fork-resume inherits
the prior conversation (old marker present in the new session's jsonl) and
registers in the Agent View under the re-recorded id.

Also: app TestMain forces spawner.BackgroundOverride=false so the suite is
hermetic against an ambient $FLOW_TERM=bg in the dev environment. README bg
section updated. make test green, go vet clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… the way

The Install-section block recommended aliasing `claude` to `claude --bg`
"so flow's sessions land in the agents view" — now obsolete and harmful:
flow execs claude directly (the alias doesn't affect its bg spawns), and
on the terminal path the alias re-injects --bg, making claude ignore
flow's pinned --session-id (the phantom-id bug this feature fixes). The
dedicated "Background agents (FLOW_TERM=bg)" section already documents the
correct, flow-integrated mechanism, so the alias guidance is removed
entirely. Also align the capture description to `claude agents --json --all`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… timeout; newest transcript

Regressions caught by an independent review of the bg backend:

- HIGH: `flow list` and `flow show` spawned `claude agents --json --all` on
  EVERY invocation for ALL users — the registry lookup was gated only on
  "harness implements BackgroundLauncher", which every claude task does.
  Non-bg users paid ~230ms–2s + a subprocess on the hottest command, with
  no timeout (a stalled daemon could hang it). Now gated on
  spawner.IsBackground(): only bg-mode invocations consult the registry;
  FLOW_TERM-unset users get the exact pre-feature behavior (no subprocess,
  no bg_status, no bg-sourced [live]). Added a 5s timeout on the agents
  query so it can never hang flow show/list.

- MEDIUM: `claude agents --json --all` also lists interactive (terminal-tab)
  sessions; parseBackgroundAgents didn't filter, so an ordinary `flow do`
  tab session got a misleading `bg_status:` line in flow show. Now only
  `kind:"background"` entries are returned. Also stop printing `pid 0` for
  exited bg sessions (show formats via bgStateLabel, omitting pid when 0).

- LOW: transcript glob fallback now picks the most-recently-modified match
  when a session id appears under two project dirs (was lexicographic), so
  it resolves the live copy rather than a stale one.

Tests: kind-filter drop-interactive test; a regression guard asserting
non-bg mode issues no `claude agents` subprocess; existing bg tests opt
into bg mode via stubBGMode. make test green, go vet clean. Verified live:
non-bg list/show fast with no bg_status; interactive session not mislabeled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@anshulsao anshulsao merged commit 7713263 into main Jun 10, 2026
3 checks passed
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