# Background Model Calls and OpenAI Codex Sessions

> This document was generated with the [Pi](https://pi.dev/) coding agent.

In pi-spark, the title and recap features call the model outside Pi's main agent loop. They send a small, independent conversation through `completeSimple()` and persist only the resulting text and usage.

On July 10, 2026, the day GPT-5.6 became available, I configured `openai-codex/gpt-5.6-luna` for title and recap generation while the main thread also used the `openai-codex` provider.

## Problem

Two failures appeared.

Without a session ID, GPT-5.6 Luna returned no text:

```text
Codex error: Model not found gpt-5.6-luna-free-1p-codexswic-ev3
```

Passing the main thread's session ID to `completeSimple()` fixed routing, but made the background call reuse the main Codex WebSocket. Its response changed the socket's server-side continuation state. The next main turn then failed:

```text
Codex error: Previous response with id 'resp_...' not found.
```

Using a non-cached WebSocket was not enough: it stopped the background call from writing a client-side continuation, but the physical socket was still shared by session ID.

## Root cause

For `openai-codex-responses`, the session ID serves two roles:

- Request-routing identity through `session-id` and `x-client-request-id` headers
- Client-side WebSocket cache key and response-chain identity

A background call therefore needs a valid Codex session ID, but it must not reuse the main thread's ID.

In our tests, Codex accepted independent session IDs generated by Pi's `uuidv7()`. UUIDv4 values from `crypto.randomUUID()` and IDs modified with prefixes or suffixes were instead routed to an unavailable internal model.

## Solution

`completeBackground()` applies special handling only to `openai-codex-responses`. Every call gets an independent Pi UUIDv7 session ID:

```ts
completeSimple(model, context, { ...options, sessionId: uuidv7() });
```

The background session resource is closed in `finally`. Non-Codex providers use an ordinary one-shot `completeSimple()` call without a shared session ID or transport override.

For OpenAI Codex, title, recap, and the main thread consequently have separate:

- WebSocket connections
- Response chains
- Session routing identities
- Prompt-cache identities

The main socket is never closed or modified by background work. Every background call uses a fresh ID, so overlapping title, recap, or retry calls cannot affect each other's response chains.

## Error handling

Provider failures from `completeSimple()` may arrive as assistant messages rather than thrown exceptions. Title and recap therefore check:

```ts
if (response.stopReason === "error") {
  throw new Error(response.errorMessage ?? "Background generation failed");
}
```

This prevents a provider error with empty content from being mistaken for an empty model response.

## Cache and performance

The independent-session design preserves the main thread's WebSocket reuse, `previous_response_id` continuation, and prompt-cache identity. Background generation pays for its own WebSocket handshake, but that work does not invalidate or reconnect the main thread.

Cache admission remains provider-controlled and noisy. In repeated GPT-5.6 Luna title A/B probes, isolated background calls caused no continuation errors and showed no negative main-thread cache trend. Main second-turn latency remained around the normal control range instead of paying the reconnect penalty seen when the shared main socket was closed.

Use the [`measuring-model-cache`](../.agents/skills/measuring-model-cache/SKILL.md) skill to repeat the comparison after changes to Pi, providers, session handling, title, recap, or other background model features.
