Skip to content

Add per-invocation environment overrides to the spawn SDK #393

Description

@kamilio

Summary

The public poe-code spawn SDK does not provide a per-invocation environment option. Consumers that need a custom environment for one spawned agent must mutate global process.env around spawn(...).

That workaround is unsafe for parallel spawns and commonly forces callers to serialize otherwise-independent agent runs.

This issue contains the complete investigation; no external consumer repository or audit document is required to implement it.

Verified API gap

Verified on June 9, 2026 against installed poe-code 3.0.230 and the current poe-platform/poe-code checkout.

The public SDK SpawnOptions in src/sdk/types.ts exposes prompt, cwd, model, mode, args, MCP servers, skills, hooks, resume, logging, runtime, and related settings, but no env field.

The lower-level packages/agent-spawn/src/types.ts SpawnOptions also has no caller-provided env field.

The CLI spawn path in packages/agent-spawn/src/acp/spawn.ts already constructs a child environment from process.env, MCP-derived environment values, and mode-derived environment values before launching the process. The ACP path similarly supports an internal environment input. The missing piece is a public per-run caller override propagated into both paths.

Reproduction

A consumer that needs a different PATH, PYTHONPATH, workspace variable, or credential set for each run currently has to do something like:

async function spawnWithEnv(env: NodeJS.ProcessEnv) {
  const original = new Map<string, string | undefined>();
  for (const [key, value] of Object.entries(env)) {
    original.set(key, process.env[key]);
    if (value === undefined) delete process.env[key];
    else process.env[key] = value;
  }

  try {
    return await spawn("codex", { prompt: "...", cwd: "..." });
  } finally {
    for (const [key, value] of original) {
      if (value === undefined) delete process.env[key];
      else process.env[key] = value;
    }
  }
}

Two concurrent calls can observe each other's temporary environment. Serializing all calls avoids the race but prevents safe parallel agent execution.

mcpServers.<name>.env does not solve this because the spawned agent process itself also needs the per-run environment, and callers may want the agent and its MCP children to inherit the same values.

Expected capability

Add a public per-invocation environment override, for example:

await spawn("codex", {
  prompt: "...",
  cwd,
  env: {
    PATH: workspacePath,
    PYTHONPATH: workspacePythonPath,
    WORKSPACE_ID: workspaceId,
  },
});

The supplied values should apply only to that spawned run and should be merged into the child environment without mutating the parent process environment.

Suggested implementation locations

  • Add the public option to src/sdk/types.ts and packages/agent-spawn/src/types.ts.
  • Forward it through src/sdk/spawn.ts into CLI and ACP spawn calls.
  • Merge it into child-process environment construction in the CLI and ACP spawn paths.
  • Decide and document whether undefined removes an inherited variable or whether the public type accepts strings only.
  • Ensure runtime backends receive the same resulting per-run environment.

Acceptance criteria

  • Public SDK SpawnOptions and autonomous spawn options accept documented per-run environment overrides.
  • CLI and ACP spawn paths merge overrides into the child process environment.
  • The parent process's process.env is never mutated for caller-provided overrides.
  • Parallel spawns with conflicting values remain isolated.
  • MCP servers launched by the agent inherit the resulting agent environment unless their own explicit server env overrides it.
  • Runtime backends receive the resulting per-run environment consistently.
  • Tests cover conflicting parallel spawn environments and deletion/unsetting semantics if supported.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is not workingreleased

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions