App exit’s overall cap (spec §11 O3), the leftover-shell sweep included:
every agent closes concurrently under it. The launcher waits longer than
this for srv to exit on every quit (SRV_EXIT_WAIT, quiesce_srv), so
it force-kills srv only if the teardown overran.
Setting: does Stop keep the agent’s run_in_background tasks (dev
servers) running when the caller doesn’t say? Per user, default true,
today’s behaviour (spec §11 O2).
Every teardown and window close in flight. Each runs as its own task, so
a caller that stops waiting can’t cut it short: an HTTP handler whose
client hung up (the host gives CloseWindow 2 s) or srv’s servers
stopping on quit. app_exit waits for all of them.
App exit (main.rs, after the servers stop): tear down every live agent
with Policy::app_exit(), concurrently, and wait for every close already
in flight, all under APP_EXIT_CAP. Then stop any Shell() session left
over (one whose agent is already gone, or whose teardown hit the cap).
Returns how many controllers it closed.
The per-agent close (SPEC_AGENT_PANE_CLOSE_GRACEFUL_SHUTDOWN_2026_09_18.md
§4.2), then the verify step (agent teardown spec §6.2 step 8). Returns the
survivors.
Running Docker Compose containers started from the agent’s working
directory (docker compose up -d): they belong to the Docker daemon, not
the agent’s process tree, so no tracker ends them. Reported, not stopped:
a person may be using them too. Skipped when the agent works in / or
the home directory, where every project would match.
Drop a block’s controller and process tracker at once, with no graceful
phase: for a controller that isn’t an agent to wind down (a PtyShell
drawer the user closed, a /btw throwaway, a spawn being rolled back) and
as the backstop after a close that already ran the teardown (idempotent;
finds nothing left).
Live processes tagged with block_id (process_tracker::scan) that
aren’t already in known. Two scans ESCAPE_SETTLE apart: one the
tracker’s kill is still ending shows in the first only.
Release the agent’s work claims, so the items go back to the pool now
rather than when the 120 s lease expires, and list the crons that target
it (kept: a cron is a deliberate schedule that resumes when the agent is
reopened). SQLite reads and writes, off the async workers.
The replace policy’s entry, for resync_controller (synchronous, below
the saga layer, and already holding the old controller): stop its own CLI
process only (Policy::replace).
Stop (agent.stop, FleetBulkStop, a deferred Stop action):
Policy::stop. stop_background is the caller’s answer to “also stop
its background tasks?”; None takes the user’s setting
(SETTING_STOP_KEEPS_BACKGROUND). Errors with NOT_RUNNING when the
block has no controller, as agent.stop always has.
Stop a container agent’s container (spec §6.7), unless another live pane
runs the same agent in it. Docker’s own SIGTERM → SIGKILL grace is bounded
by what’s left of the teardown’s deadline.
Policy::stop_cli_only’s entry, for callers with no AppState (the
watchdog, the pane’s AgentStop command): controller.stop() only.
Errors with NOT_RUNNING when the block has no controller.
Ask everything in the block’s tree to exit and wait until it has, or the
grace passes. Returns at once where the tracker has no graceful signal
(Windows), nothing is left, or the deadline has passed (the release’s
kill follows straight away).
Spec §6.2 step 8: wait for every process in the snapshot to be gone. Any
still running (same PID and start time) gets one forced kill of its tree;
whatever is left after that is returned. Never kills a PID the OS has
reused: survivors re-checks the start time right before the kill.