3.7 KiB
status, system, requirements, created, owners
| status | system | requirements | created | owners | ||
|---|---|---|---|---|---|---|
| draft | platform |
|
2026-08-26 |
|
Agent process exit and stderr drain System Design
Purpose and boundaries
The agentctl process manager owns the child process, an explicitly managed stderr pipe, exit status, and safe recent-stderr diagnostics. This design changes the stderr pipe ownership and the order in which process exit and stderr completion are observed. It does not change agent protocol handling, stderr sanitization, or process-group cleanup.
Requirement mapping
| Requirement | Design section |
|---|---|
REQ-PLATFORM-AGENT-PROCESS-EXIT-DRAIN-001 |
Exit flow and Failure and recovery |
Components and responsibilities
internal/agentctl/server/process.Managerstarts the managed command and owns its lifecycle.Manager.startProcessPipescreates anos.Pipe, assigns its writer to the command, and retains the reader and writer for explicit lifecycle cleanup.Manager.readStderrdrains and sanitizes the current process generation's stderr stream, appending only the safe projection to the ring buffer.Manager.waitForExitwaits for the child process and then coordinates the generation-specificstderrDonechannel before publishing exit diagnostics.- The adapter update channel receives the existing exit error event and recent stderr fields.
Data and contracts
The process manager keeps processStderrDrainTimeout as the upper bound for a
reader that does not finish after process exit. stderrDone remains local to a
process generation so a delayed reader from an old command cannot close a
replacement command's completion channel. The command receives the owned
stderr writer as an *os.File, so Cmd.Wait can reap the child without
closing the manager's reader before the reader drains. The parent writer is
closed immediately after a successful start, and the reader is closed after a
normal drain or when the bounded drain expires.
No HTTP, WebSocket, database, or agent protocol payload changes are required.
Exit flow
- Process startup creates the stdin and stdout pipes plus an explicitly owned
stderr
os.Pipe, closes the parent stderr writer after the command starts, and startsreadStderrandwaitForExitconcurrently. readStderrcontinues draining while the child is alive, so a child that runs longer than the drain timeout does not create a timeout warning merely because it has not exited.waitForExitcallscmd.Wait()to observe the child exit.- After
cmd.Wait()returns,waitForExitwaits on the same generation'sstderrDonechannel for the bounded drain interval. Because the stderr reader is not aCmd.StderrPipe,Cmd.Waitdoes not close it first. - Exit classification, recent-stderr event construction, process-group reap, and final stopped status follow the existing paths.
Failure and recovery
If the reader finishes after process exit, final exit diagnostics include the
same sanitized recent stderr. If the reader remains blocked, the manager emits
timed out waiting for agent stderr to drain, closes its owned reader to
unblock the goroutine, and continues within the existing bound instead of
hanging process cleanup. That warning then indicates a real post-exit pipe-
drain delay.
An intentional stop still uses the existing intentional-stop classification. Non-zero process exits still retain their existing error event and exit code.
Observability
The stderr-drain warning remains at warning level, but its occurrence is now limited to the post-exit bounded wait. Existing process start, exit, and recent stderr log fields remain unchanged.