Skip to content

feat(dev): supervise every runtime in headless mode - #2138

Open
tejaskash wants to merge 1 commit into
refactorfrom
feat/headless-multi-agent
Open

feat(dev): supervise every runtime in headless mode#2138
tejaskash wants to merge 1 commit into
refactorfrom
feat/headless-multi-agent

Conversation

@tejaskash

Copy link
Copy Markdown
Contributor

What

--mode headless without --agent now supervises every runtime in the project, streaming attributed [name] output to the terminal, the same behavior browser mode gets from the Inspector. Follows the discussion on #2086: multi-agent terminal supervision (useful for A2A testing) was the interim behavior on refactor between #2041 and #2086, and this restores it as a first-class mode instead of requiring --agent.

  • --mode headless (no --agent): all runtimes start eagerly, output is [name]-attributed, one agent crashing leaves the others running, and the command exits non-zero only when nothing is left running and something failed.
  • --mode headless --agent <name>: unchanged, the direct single-runtime path where a crash fails the command with a real exit code (scripts and CI rely on this).
  • Browser mode: unchanged.

Verification

  • Handler suite covers the new path: attributed output and per-runtime OTEL env for two supervised runtimes, crash isolation, SIGINT exits 130 and closes the collector, all-agents-failed exits non-zero.
  • End to end against a real 2-runtime project: project dev --mode headless started both agents eagerly on distinct ports with [bob]/[alice] attributed output, and SIGTERM tore both down.
  • Full suite (2163 pass), typecheck, format clean. The two oxlint errors are pre-existing on refactor (DataTable.tsx, usePagedList.tsx).

--mode headless without --agent now runs all of the project's runtimes at
once with attributed output, matching the supervised behavior browser mode
gets from the Inspector. One agent crashing leaves the others running, and
the command exits non-zero only when nothing is left running and something
failed. --agent keeps the direct single-runtime path where a crash fails
the command.
@github-actions github-actions Bot added the size/m PR size: M label Aug 28, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added agentcore-harness-reviewing AgentCore Harness review in progress claude-security-reviewing Claude Code /security-review in progress labels Aug 28, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Aug 28, 2026
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.22%. Comparing base (fadad5a) to head (45a092e).

Additional details and impacted files
@@             Coverage Diff              @@
##           refactor    #2138      +/-   ##
============================================
+ Coverage     97.19%   97.22%   +0.02%     
============================================
  Files           471      471              
  Lines         28731    28740       +9     
============================================
+ Hits          27925    27942      +17     
+ Misses          806      798       -8     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@agentcore-devx-automation agentcore-devx-automation 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.

AgentCore Harness Review

Verdict: Looks good

The change cleanly extends headless mode to supervise every runtime by reusing DevSupervisor instead of restricting to a single agent. I traced the new loop in src/handlers/project/dev/index.ts (lines 225–240) against DevSupervisor.launch/pump/events, and the phase/event ordering holds up:

  • entry.phase = "starting" is set synchronously in launch() before any await, so the initial Promise.allSettled(runtimes.map(start)) guarantees no agent looks "idle" when the loop first iterates.
  • Failure events are pushed after entry.phase = "failed", so snapshot() on each iteration is consistent with what the user just saw rendered.
  • The every(!starting && !running) + some(failed) predicate correctly avoids tearing down peers when one agent dies but others are still running, and only escalates to SilentCLIError when the whole cohort is down and at least one failed.
  • Single-runtime --mode headless without --agent now falls into this path and exits gracefully when the lone runtime stops — a strict improvement over the previous hard-error.
  • Cleanup: since the throw only fires once no agent is still starting/running, there are no orphan children when SilentCLIError propagates and the finally block closes the collector (verified by state.closed === 1 in the new test).

Test coverage looks appropriate: stayingRunner is a good addition and models a real dev server better than captureRunner for these cases, the removed --mode headless requires --agent and --port with several runtimes is rejected assertions are correctly folded into the parameterized cases / new tests, and the new tests exercise attributed output, per-runtime OTEL env rewrite, partial-failure survival, and total-failure exit.

One minor UX nit worth being aware of (not a blocker): when both agents fail nearly simultaneously and both failure events land in the supervisor queue before the outer loop drains it, the break can fire after the first yield and the second agent's failure line is never rendered. Users still get non-zero exit and the first failure message, but if you wanted symmetry you could drain remaining failed-phase events from supervisor.snapshot() before throwing. Fine to defer.

Nothing here that needs to change before merging.

@agentcore-devx-automation agentcore-devx-automation Bot removed the agentcore-harness-reviewing AgentCore Harness review in progress label Aug 28, 2026
const starts = Promise.allSettled(
runtimes.map((runtime) => supervisor.start(runtime.name)),
);
for await (const { agentName, event } of supervisor.events()) {

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.

I think normal Ctrl-C gets reported as a crash in this supervised headless path. The production process runner rejects with the child signal's UserCancellationError, so the supervisor emits Agent 'orders' crashed: Operation cancelled by user before the command exits 130. Could the supervisor treat an error from an already-aborted child signal as a normal stop? A test where the runner throws input.signal.reason on abort would cover the production behavior better than stayingRunner, which returns cleanly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants