# Factory GPT Provider Runtime — request

audience: AI coding agents first.

**Goal:** Provide installed local ChatGPT Sol OpenAI endpoint, Pi provider catalog/session semantics, and Pro-default `factory-gpt` front door without modifying Factory producer code.

**Context:** Master contract: `docs/plans/2026-08-11-factory-gpt-model.md`. Design: `docs/specs/2026-08-11-factory-gpt-model-design.md`. Existing `solwebd` owns browser seats/tool protocol; Pi owns agent tool loop.

## Files

- Create `modules/gptbridge/bin/solwebctl` — installed service-control/identity/token CLI.
- Create `modules/gptbridge/bin/factory-gpt` — Pro-default fail-closed Factory wrapper.
- Modify `modules/gptbridge/solwebd.py` — health state/identity, client-session header, typed stream failures, context estimates, cancellation visibility.
- Modify `modules/gptbridge/translate.py` — optional client identity in session key; cumulative transcript reconciliation/accounting.
- Modify `modules/gptbridge/install.sh` — link `solwebctl` and `factory-gpt`; add fail-closed `--links-only` mode that skips dependency/vendor work and links every existing command.
- Modify `packaging/deploy-local.sh` — after landed deploy checkout, invoke gptbridge `--links-only`; include dry-run receipt so new commands always repoint to deploy clone.
- Modify `modules/harness/wrappers/sw.sh` — consume `solwebctl` while preserving headerless external contract and CCR behavior.
- Modify `modules/workstation/pi/agent/models.json` — register provider `gpt` and four Factory models.
- Create `modules/workstation/pi/agent/extensions/gpt-sol.ts` ONLY if deterministic kill gate proves static `openai-completions` cannot preserve session/error contract; replace static provider registration rather than duplicate it.
- Create `modules/gptbridge/tests/test_solwebctl.py` — enrollment, ownership, state, startup, token, topology contract.
- Modify `modules/gptbridge/tests/test_solwebd_loop.py` — typed blocking/stream failures, context, cancellation.
- Modify `modules/gptbridge/tests/test_translate.py` — session identity/collision/replay/accounting.
- Modify `modules/gptbridge/tests/test_processes.py` — PID/start-generation/startup serialization.
- Create `modules/gptbridge/tests/test_pi_provider.py` — installed Pi catalog/auth/header/tool/error/context compatibility kill gate.

## Contract

Pi provider entry:

```json
{
  "gpt": {
    "baseUrl": "http://127.0.0.1:8791/v1",
    "apiKey": "!solwebctl token",
    "api": "openai-completions",
    "authHeader": true,
    "headers": {"X-Overdeck-Client-Session": "$OVERDECK_PI_CLIENT_SESSION"}
  }
}
```

Each `sol-web-medium|high|xhigh|pro` model:

```text
reasoning=false
input=["text"]
cost={input:0,output:0,cacheRead:0,cacheWrite:0}
contextWindow=120000
maxTokens=16000
```

`factory-gpt`:

```text
factory-gpt [--effort medium|high|xhigh|pro] <adw> <prompt-or-path>
default effort: pro
preset: gpt-sol-<effort>
preflight: solwebctl ensure --json --seats 1
```

Preserve child exit code/signals. Unsupported effort or failed readiness exits before Factory session. No CCR, fallback, downgrade, direct ask-gpt, remote dispatch, or config generation.

`modules/gptbridge/install.sh --links-only` MUST perform no network/dependency/vendor mutation, refuse missing/non-executable source commands and non-symlink targets, and atomically point `ask-gpt|solwebd|solwebctl|factory-gpt|install-ccr-provider|sw` into current deploy clone. Full installer retains existing dependency/vendor behavior then uses same linker.

`solwebctl` IO:

```text
solwebctl enroll --current-profile --json
solwebctl ensure --json [--seats N]
solwebctl health --json
solwebctl token
solwebctl stop --json
```

Canonical private files under `~/.overdeck/gptbridge/`:

```text
identity.json = {expected_account, profile_dir, machine_id_sha256}
solwebd.token
runtime.json = {pid, process_start_ticks, instance_generation, version}
```

All must be regular, current-user-owned, mode 0600. Enrollment atomically pins current authenticated account via `chat.authenticated_account()`, canonical profile, and SHA-256 of exact `/etc/machine-id` bytes. Existing differing enrollment fails; deployment never rewrites it.

`ProviderHealth` and `ProviderFailure` exactly match master request. `ok=true` iff state ready, identity/process ownership match, authenticated/uncapped, required Factory model subset advertised, and seat minimum met. Legacy `sol-web-instant` remains advertised for non-Factory clients.

Session header:

```text
X-Overdeck-Client-Session: v1.<43 base64url SHA-256 chars>
```

Valid optional header joins content-derived session key. Invalid header rejects before body. Missing header preserves legacy key. Provider extension is required only if static Pi headers cannot interpolate per-process env or typed stream failures cannot survive.

Context estimate includes rendered input, visible assistant output, message/tool overhead. Reject when cumulative input + cumulative output + 16000 > 120000. Emit usage estimate marked `usage_estimated=true`; never claim billing.

Streaming and blocking errors preserve `kind`, `detail`, `retry_after_seconds`, `resume_at`. Client disconnect cannot cancel Marionette; seat releases by `TURN_TIMEOUT=300s` and no later client event is emitted.

## Behavior

- Localhost only; bearer auth every route.
- Startup serialized; stale PID distinguished with `/proc/<pid>/stat` start ticks and generation.
- `ensure --seats 1` accepts larger healthy daemon. Never restart active daemon to resize.
- Workstation enrollment mismatch fails. Never copy token/profile remotely.
- Header/session value is non-secret and may reach Pi tool children; token does not automatically enter their env.
- Keep protocol behavior: zero fenced blocks `Final(prose)`; one valid block tool call; malformed repaired once.

## Out of scope

- Any path under `modules/harness/factory/**`.
- Factory presets, retry policy, trace DB/UI.
- `ask-gpt` behavior or `/gpt-orchestrator`.
- New agent loop, command JSON, attachment packaging, strong same-user secret sandbox.

## Acceptance

Run clean:

```text
python3 -m pytest modules/gptbridge/tests/ -q
```

With temporary `PI_CODING_AGENT_DIR`, installed Pi binary, authenticated scripted Sol endpoint, and fake browser, prove:

- four models discover in default/account-composed Pi dirs;
- token command/auth header and session header interpolation;
- two identical concurrent client IDs remain isolated; same ID retries resume;
- keepalive → tool call/result → final;
- exact 429/503/502/timeout/cancellation metadata and retry count;
- context input+output threshold triggers compaction/replay;
- legacy headerless and `sol-web-instant` paths still pass.

Install through module installer. Verify `OVERDECK_DEPLOY_DRY_RUN=1 bash packaging/deploy-local.sh` names gptbridge link stage; installed `solwebctl health --json`; `factory-gpt` Pro/preset mapping against fake Factory; and every command symlink resolves into deployed source after deployment. Raw live Sol model probe forbidden.
