Skip to content

feat: add adopt API for externally spawned child processes - #98

Draft
masonc15 wants to merge 2 commits into
aliou:mainfrom
masonc15:feat/adopt-external-children
Draft

feat: add adopt API for externally spawned child processes#98
masonc15 wants to merge 2 commits into
aliou:mainfrom
masonc15:feat/adopt-external-children

Conversation

@masonc15

Copy link
Copy Markdown

Motivation

Extensions that run foreground child processes (most notably bash-tool overrides that let a user move a slow command to the background instead of killing it, Claude Code ctrl+b style) need somewhere to put the still-running child. pi-processes already has everything such a process needs — log capture, liveness watching, kill/stop, notifications, dock and /ps visibility — but ProcessRuntimeController.start() is the only entry point, and it insists on spawning the child itself.

This PR adds an adopt path so an already-running child can be handed over to the manager.

What's included

  • ProcessManager.adopt(name, command, cwd, child, opts?) — registers an externally spawned ChildProcess as a managed process. start() is refactored into spawn + shared register(); adopted and started processes go through identical registration, stdio wiring, and lifecycle handling.
  • opts.initialOutput — output the adopter captured before handover, appended to the logs ahead of any future stdio so nothing is lost across the transition.
  • opts.startTime — backdates the record to when the command actually began.
  • New event-bus channel processes:command:adopt (CommandAdoptPayload / CommandAdoptResult) so other extensions can adopt cross-extension. Payloads cross the bus by reference in-process, so the live ChildProcess handle arrives intact. The handler registers default notification config, matching started processes.
  • Already-exited edge case: if the child's close event fired before adoption (streams destroyed), the record is finalized immediately with the correct exit classification; if the child exited but streams are still open, the pending close finalizes it through the normal path.

Contract

The child must be spawned the way spawnCommand spawns: detached process group, piped stdio. This keeps group kill (killProcessGroup) and liveness polling (isProcessGroupAlive) working identically for adopted processes. Documented on the payload type and both adopt methods.

Testing

  • 9 unit tests in src/manager/index.test.ts (running adoption, backdated startTime, process_started emission, initialOutput ordering, exit detection, both already-exited variants, kill, missing-pid).
  • 2 e2e tests in tests/e2e/adopt.e2e.ts with real detached children: output captured across handover (pre-handover marker via initialOutput, post-handover marker via wired stdio), and process-group kill of an adopted child.
  • pnpm typecheck, pnpm lint, pnpm test (603), pnpm test:e2e (12) all green.
  • Validated live in a Pi session with a bash-override extension emitting processes:command:adopt: user-triggered and timeout-triggered backgrounding both landed in the dock//ps, output was preserved across the handover, and exit notifications reached the agent.

Notes

  • Windows is unaffected (extension already bails on Windows; the manager remains POSIX-only).
  • Marked as draft to discuss the API surface — happy to adjust naming, validation strictness, or move the channel handler elsewhere.

@aliou

aliou commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Hey @masonc15 ! Thanks for this PR! Note that I'm moving some stuff around (see the open prs/stack) and I think this will get some conflicts.

Could you explain in your words + with some real time examples how this would work? I'm not yet sure of the use case where this would be needed, in most of my use, Pi uses either bash or processes and rarely needs to have its process adopted?

However, something I'm investigating is doing something like Codex and Claude Code (see #56 and #81) where it's a unified tool that always runs the process in the bg (codex) or an override on the Bash tool that optionally can be started in the background (Claude).

Would either match what you have in mind?

@masonc15

Copy link
Copy Markdown
Author

Hi @aliou, sorry if this created any noise for you - I was brainstorming a sort of extension of pi-processes that allowed for similar behavior to Claude Code where you can press ctrl-b to background any currently running job. I had left it in draft mode to sort of iterate on, sorry if it's in a bit of a "slop"py state as a PR.

Even with pi-processes, I've run into a bunch of cases using pi where the first thing it does is a grep, find, mdfind, or something along those lines over a large root that blocks pi and takes over 200 seconds sometimes when the task itself should be finished much quicker. My ideal use case for this would be noticing a job like that, being able to press a key to have pi-processes adopt it, and then pi being able to continue and not be blocked.

I think the Claude-style Bash override mentioned in #81 would probably cover this, but I would want to make sure it handles the case where a command is unpredictably slow and can be backgrounded. Having a background: true flag relies on the slowness being predicted ahead of time, and I've found that having a universal ability to background commands is useful for the unpredictable cases.

I validated this branch with a small bash override extension I built. Press ctrl+shift+b in the middle of a command and the tool call resolves immediately with a message naming the adopted process ID, the process shows up in /ps and the dock (if the user has dock enabled), and the agent gets the normal completion notification later as standard. Output from before resolution comes along with initialOutput.

Looking at #81, adopt shouldn't be needed for that path since the manager should be aware of every command/process already. I'm happy to close this and contribute promotion logic to #81 if that would make more sense. Or if this adopt primitive still makes sense, seeing the conflicts with your #88 stack, I can wait until that lands and rebase after that. Just let me know, thanks!

@aliou

aliou commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Hi @aliou, sorry if this created any noise for you

no worries, not at all!

Looking at #81, adopt shouldn't be needed for that path since the manager should be aware of every command/process already. I'm happy to close this and contribute promotion logic to #81 if that would make more sense. Or if this adopt primitive still makes sense, seeing the conflicts with your #88 stack, I can wait until that lands and rebase after that. Just let me know, thanks!

I should have everything shipped this weekend, i'll ping you and you can rebase, and i'll play with your branch a bit to see how it behaves day to day :)

Could you also share your bash tool override so I can see the full "workflow"?

Thanks!

@aliou

aliou commented Aug 22, 2026

Copy link
Copy Markdown
Owner

@masonc15 Merged the full stack so feel free to go for it!

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.

2 participants