Headless launch mode for orchestrators #316
Reference in New Issue
Block a user
Delete Branch "headless-launch-mode"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #315.
Summary
Adds
--headlesstocli.py start: a non-interactive launch path for orchestrators (Paseo), CI, and webhook dispatch.assume_yesparam threaded into the shared_launch_bottlecore).--bottle(repeatable) defaults to the agent's ownbottle:;--labeldefaults to the agent name and auto-uniquifies on slug collision (orchestrators fire-and-forget many bottles);--colordefaults to none.--headless --dry-runpreviews the resolved plan without launching.Implementation
_start_headless+_uniquify_label_headlessincli/start.py, and anassume_yesparam on_launch_bottle(reuses the entire existing launch core). New unit coverage intests/unit/test_cli_start_headless.py(11 tests). Full start/cli/launch sweep green (174 passed).`cli.py start` was interactive-only: TUI selectors (agent / bottle / name+color) plus a y/N preflight, then a blocking PTY attached to the controlling terminal. That shape can't be driven by an orchestrator (Paseo), CI, or webhook dispatch, and made spinning up a known agent+bottle more friction than necessary. Add a `--headless` path on `start`: - agent / bottles / label / color come from flags + manifest defaults; no TUI selectors, no y/N (auto-confirmed via a new `assume_yes` param threaded into the shared `_launch_bottle` core). - `--bottle` (repeatable) defaults to the agent's own `bottle:` when omitted; `--label` defaults to the agent name and auto-uniquifies on slug collision (orchestrators fire-and-forget many bottles); `--color` defaults to none. - the agent still execs on inherited stdio/PTY, so whatever allocates the PTY drives the live session — only the launch chrome went non-interactive. - `--headless --dry-run` previews the resolved plan without launching. Prerequisite for orchestrator integration, webhook dispatch, and remote spin-up. New unit coverage in tests/unit/test_cli_start_headless.py (11 tests); start/cli/launch sweep green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NkwFXLFff9PYPy4wgVBJp9We also need a way to provide a prompt in headless mode. How this should be provided to the agent harness will vary per agent, so we should add an abstract method to the AgentProvider. So:
headless_promptmethod on the agent providerclaude,codex, andpiagent providersGot it. A few clarifications before I implement:
Scope of
headless_prompt: Should this return agent CLI args (e.g.-p "<text>"for claude, a positional arg for codex), or should it handle writing the prompt to the container (i.e. replace/augmentprovision_prompt)? My read is that it returns the CLI args that deliver the initial task message — separate from the existing system-prompt/context mechanism (provision_prompt+prompt_args). Is that right?Pi: Pi's current headless invocation doesn't seem to have an obvious non-interactive initial-task flag. What's the intended behavior there — pass the prompt as a positional argument, or something else?
Interaction with the existing
prompt:manifest field: The manifest already supports an agent-levelprompt:key (system context). Should--promptin headless mode be an additional user-task message on top of that, or does it replace/subsume it for headless launches?Assuming my read on (1) is correct, my plan:
--prompt TEXT(required in--headless, error if absent) to_start_headlessheadless_prompt(prompt: str) -> list[str]abstract method toAgentProvider["-p", prompt][prompt](positional task arg)attach_agentas additional startup args@didericis-claude
Yes, think that's the best way to do it.
Looks like there's a non interactive mode prompt
-pwe can use (see https://pi.dev/docs/latest/usage)It should be placed on top of it/be an additional user-task message.
Implemented. Changes pushed to the branch:
AgentProviderABC — addedheadless_prompt(self, prompt: str) -> list[str]as a new abstract method.Provider implementations:
["-p", prompt][prompt](positional task arg)["-p", prompt](per https://pi.dev/docs/latest/usage)cli/start.py:--prompt TEXTargument--headlessnow dies if--promptis absent_launch_bottletakes a newheadless_prompt_text: str = ""param; when set it callsget_provider(template).headless_prompt(text)and appends the result tostartup_argspassed toattach_agentget_providerto the import fromagent_providerTests: updated
test_cli_start_headless.py(2 new tests:test_headless_without_prompt_dies,test_headless_prompt_forwarded_to_launch; all existing tests updated to pass--prompt). AddedTestClaudeHeadlessPrompt,TestCodexHeadlessPrompt,TestPiHeadlessPromptto the three contrib test files.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.