Skip to content

Wire up model passthrough in spawn() #389

@jahala

Description

@jahala

Relay: Wire up model passthrough in spawn()

The SpawnPayload protocol type already defines model?: string but it's vestigial — never reaches the spawned CLI process. Three small changes close the gap.

Current State

SpawnPayload.model?    ✅  Defined in protocol/src/types.ts:405
SDK client.spawn()     ❌  Not in options interface
spawn-manager.ts       ❌  Doesn't forward payload.model to AgentSpawner
spawner.ts             ✅  Already reads model from agent config files
buildClaudeArgs()      ✅  Already passes --model to CLI
mapModelToCli()        ✅  Already maps friendly names → CLI variants

The entire downstream pipeline works (model → --model flag → CLI). The signal just doesn't flow from spawn() call through to the spawner.

Changes Required

1. SDK client — expose model in spawn options

File: packages/sdk/src/client.ts ~line 795

Add model to the options interface:

async spawn(
  options: {
    name: string;
    cli: string;
    task?: string;
    cwd?: string;
    team?: string;
    model?: string;  // ← ADD THIS
    interactive?: boolean;
    // ... rest unchanged
  },
  timeoutMs = 30000
): Promise<SpawnResult>

And include it in the payload (~line 854):

payload: {
  name: options.name,
  cli: options.cli,
  task: taskWithContext,
  team: options.team,
  cwd: options.cwd,
  model: options.model,  // ← ADD THIS
  spawnerName: options.spawnerName,
  // ... rest unchanged
}

2. Spawn manager — forward model to AgentSpawner

File: packages/daemon/src/spawn-manager.ts ~line 98

const result = await this.spawner.spawn({
  name: payload.name,
  cli: payload.cli,
  task: payload.task,
  team: payload.team,
  cwd: payload.cwd,
  model: payload.model,  // ← ADD THIS
  spawnerName: payload.spawnerName ?? spawnerName,
  // ... rest unchanged
});

3. Spawner — use model param with precedence over agent config

File: packages/bridge/src/spawner.ts ~line 946

The spawner currently resolves model only from agent config files. The spawn payload's model should take precedence:

if (isClaudeCli) {
  const agentConfig = findAgentConfig(name, this.projectRoot);
  const modelFromProfile = agentConfig?.model?.trim();
  const modelFromPayload = payload.model?.trim();  // ← ADD THIS

  // Payload model takes precedence over profile
  const effectiveModel = modelFromPayload || modelFromProfile;  // ← CHANGE THIS

  const cliVariant = effectiveModel
    ? mapModelToCli(effectiveModel)
    : mapModelToCli(); // defaults to claude:sonnet

  const configuredArgs = buildClaudeArgs(name, args, this.projectRoot);

  // If payload specified model, ensure it overrides whatever buildClaudeArgs set
  if (modelFromPayload) {                          // ← ADD THIS BLOCK
    const modelIdx = configuredArgs.indexOf('--model');
    if (modelIdx !== -1) {
      configuredArgs[modelIdx + 1] = modelFromPayload;
    } else {
      configuredArgs.push('--model', modelFromPayload);
    }
  }

  args.length = 0;
  args.push(...configuredArgs);
}

Also need to add model to the spawner's internal SpawnOptions interface (likely in the same file or an imported type).

Model Mapping Reference

mapModelToCli() in packages/utils/src/model-mapping.ts already handles these:

Input Output
opus claude:opus
sonnet claude:sonnet
haiku claude:haiku
claude-opus-4 claude:opus
claude-sonnet-4 claude:sonnet

Use Case

We need to set agent model at spawn time from our server, and switch models mid-session by releasing and re-spawning:

// Spawn with specific model
await client.spawn({ name: 'Facilitator-abc123', cli: 'claude', model: 'opus', task: '...' });

// Later: switch to cheaper model
await client.release('Facilitator-abc123');
await client.spawn({ name: 'Facilitator-abc123', cli: 'claude', model: 'haiku', task: '...' });

The agent hydrates session state from storage on startup, so no conversation context is lost across re-spawns.

Testing

  • Spawn agent with model: 'opus' → verify --model opus appears in CLI args
  • Spawn agent with model: 'haiku' → verify --model haiku
  • Spawn agent with no model + agent config file → verify config file model still works (backward compat)
  • Spawn agent with model + conflicting agent config → verify spawn payload wins

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions