feat(#2290): converge unification cleanup — pull preamble into lifecycle

Collapse the scattered ensure_agent_runtime_dir calls into the lifecycle
functions themselves so callers have a single responsibility:

- lifecycle::spawn: calls ensure_agent_runtime_dir before write_dropins.
  Callers (handle_spawn, ensure_root_agent) no longer need a separate
  preamble step.

- lifecycle::rebuild_no_meta spawn path: calls ensure_agent_runtime_dir
  before write_dropins. apply_commit / merge_config_pr flows no longer
  need a manual ensure_agent_runtime_dir.

- run_create (job-queue): drops ensure_agent_runtime_dir + register_agent.
  The tail Reconcile's converge_start_preamble handles the runtime dir
  and mcp_sockets::spawn_poll handles the listener. Create stays purely
  'provision + create', not 'create + start'.

- handle_spawn (server.rs): drops manual preamble; lifecycle::spawn owns it.
  Drops unneeded unregister_agent on failure (supervisor handles listener).

- ensure_root_agent (auto_update.rs): drops manual ensure_agent_runtime_dir.

- actions.rs apply_commit / merge_config_pr: drop manual
  ensure_agent_runtime_dir; rebuild_no_meta's spawn path handles it.

Result: ensure_agent_runtime_dir lives in exactly two places —
lifecycle::spawn (direct spawn) and converge_start_preamble (start/reconcile
path). All other callers are clean call sites.
This commit is contained in:
atlas 2026-07-08 23:35:08 +02:00
commit afdd8c6c9f
5 changed files with 15 additions and 19 deletions

View file

@ -202,14 +202,12 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
/// registration and notifying the manager on failure.
async fn handle_spawn(coord: &Arc<Coordinator>, name: &str) -> Result<HostResponse> {
tracing::info!(%name, "spawn");
// Create the bind-mount source dir (pure filesystem, no Coordinator dep).
// MCP listener registration happens eagerly here (not deferred to the
// supervisor) so the socket is ready before the harness's first turn.
lifecycle::ensure_agent_runtime_dir(name)?;
coord.register_agent(name)?;
let agent_dir = Coordinator::agent_dir(name);
let hive = coord.hive_env();
let paths = Coordinator::agent_paths(name, agent_dir);
// lifecycle::spawn creates the runtime dir internally before start, so
// no manual ensure_agent_runtime_dir here. MCP listener registration is
// handled by mcp_sockets::spawn_poll on its first post-start tick.
match lifecycle::spawn(name, &hive, &paths).await {
Ok(()) => {
if let Err(e) = coord.power.set(name, crate::power::Wanted::Up) {
@ -225,7 +223,6 @@ async fn handle_spawn(coord: &Arc<Coordinator>, name: &str) -> Result<HostRespon
tokio::spawn(lifecycle::sync_tmpfiles());
}
Err(e) => {
// Roll back socket registration if container creation failed.
coord.unregister_agent(name);
coord.notify_manager(&hive_sh4re::HelperEvent::Spawned {
agent: name.to_owned(),