spawn-single-source

Spawn: single source of truth for {executor, model, endpoint, env, argv} per task

Metadata

Statusdone
Assignedagent-100
Agent identity3184716484e6f0ea08bb13539daf07686ee79d440505f1fdf2de0357707034c3
Created2026-04-26T20:00:02.787961078+00:00
Started2026-04-26T20:01:43.266979+00:00
Completed2026-04-26T20:28:00.399936952+00:00
Tagseval-scheduled
Eval score0.80
└ hallucination rate0.25
└ requirement coverage0.70
└ semantic match0.95
└ specificity match0.65

Description

Description

The proximate root cause of recent failure cascades: TWO competing decision-makers for 'what launches this task'. The daemon's executor-config and the per-task spawn-argv builder make decisions independently. Daemon log says executor=claude; per-spawn metadata says executor=native. They disagree because they read different parts of the merged config.

This must be unified. One struct, one function, one place where {executor, model, endpoint, env vars, command argv} for a task spawn is computed.

Spec

  1. Define a SpawnPlan struct (or similar; src/dispatch/plan.rs):

    pub struct SpawnPlan {
        pub executor: ExecutorKind,    // claude | native | shell | codex | ...
        pub model: ResolvedModel,      // bare alias OR provider/model OR pinned id
        pub endpoint: Option<EndpointConfig>,  // None for claude executor (claude CLI handles it)
        pub env: HashMap<String, String>,
        pub argv: Vec<String>,
        pub provenance: SpawnProvenance,  // why this combination was chosen (for logging)
    }
    
  2. One function builds it: pub fn plan_spawn(task: &Task, config: &MergedConfig) -> Result<SpawnPlan>. This is the ONLY place that decides what runs. Every spawn site (dispatcher tick, IPC spawn, manual wg spawn, chat-agent supervisor) calls it.

  3. Hard precedence rules (documented + tested):

    • Per-task explicit executor field (rare, but must win) → final
    • Local [dispatcher].executor (or [agent].executor) → final
    • Global same → final
    • Default (claude) → final
    • Model spec NEVER overrides executor. If executor=claude, model 'opus' resolves to claude CLI's bare opus alias. If executor=native, model 'opus' may need expanding or rejection — but executor wins as the floor.
  4. Endpoint resolution is executor-scoped:

    • executor=claude → no endpoint needed (claude CLI handles auth/url itself); never include --endpoint in argv
    • executor=native → endpoint required; resolve via merged config
    • executor=shell → no endpoint
    • This kills the 'opus + global openrouter is_default → spawn native --endpoint openrouter' route entirely.
  5. Provenance logging. Every SpawnPlan emits a one-line log entry: 'agent-N: executor=claude (from local [dispatcher]), model=opus (from local [agent].model, alias)' so every spawn is fully traceable to which config knob produced which value. End the silent-routing problem.

  6. Migrate all spawn sites:

    • src/commands/spawn_task.rs (the unified entry)
    • src/commands/service/mod.rs (dispatcher tick)
    • src/commands/service/ipc.rs (handle_spawn)
    • src/commands/service/coordinator_agent.rs (chat-agent supervisor's per-iteration spawn)
    • src/commands/spawn.rs (manual) None of these may construct argv independently; all call plan_spawn().
  7. Delete competing logic. The 'requires_native_executor' function (src/commands/service/coordinator.rs) becomes a sub-routine of plan_spawn or is deleted if redundant. The endpoint-routing in spawn-argv builders gets ripped out.

Verification

  • Failing test first: test_executor_floor_is_honored — task with model='opus' + global is_default=openrouter + local [dispatcher].executor=claude → SpawnPlan.executor MUST be claude (NOT native). The exact regression that bit the user.
  • test_no_endpoint_for_claude_executor — SpawnPlan.endpoint is None when executor=claude
  • test_provenance_traces_every_field — SpawnPlan.provenance lists which config source produced each field
  • test_all_spawn_sites_call_plan_spawn — grep src/ for direct argv construction outside plan_spawn → assert zero hits

Validation

  • All test_* above written first and failing
  • plan_spawn() implemented; all spawn sites migrated
  • cargo build + cargo test pass with no regressions
  • Manual smoke: in this exact repo state (local executor=claude, global openrouter is_default), retry any of the 6 previously-failed tasks; spawn metadata shows executor=claude, no --endpoint flag, no native-exec invocation
  • Daemon log contains a 'SpawnPlan' provenance line for every agent spawn that traces every field to its config source

Depends on

Required by

Log