# ACP model-switch permission proof

Environment: Linux web client, two unmodified app commits, fresh T3 homes, no live database seed. Chromium viewport 1280x800, light theme. Hermes Agent release v0.22.5 runs as a local-command ACP provider. Its model backend is the attached synthetic OpenAI-compatible endpoint; no paid inference or private runtime model identifiers are used.

## Fixture

1. Extract Hermes v0.22.5 and install its ACP dependencies. The release dependency resolver has an incompatible Pillow/browser-harness pin; this run uses the already-installed Hermes Python dependency generation with the unmodified v0.22.5 source on PYTHONPATH.
2. Start `model-endpoint.py` on loopback port 18871. It serves synthetic models `proof-a` and `proof-b`, an assistant reply, and streaming usage of 1000 prompt tokens.
3. For each side, make an independent Hermes home with this config:

```yaml
model:
  provider: custom
  default: proof-a
  base_url: http://127.0.0.1:18871/v1
  api_key: synthetic
  switch_context_confirm_tokens: 1
custom_providers:
  - name: custom
    base_url: http://127.0.0.1:18871/v1
    api_key: synthetic
    models:
      - id: proof-a
      - id: proof-b
agent:
  max_turns: 2
```

4. Run each app with `T3CODE_DEV_SEED=0 vp run dev`. Configure a local-command ACP provider named Permission proof with the corresponding Hermes launcher and custom model list `["proof-a", "proof-b"]`. Unset unrelated provider credentials in the launcher so the picker only offers synthetic models. `acp-wire.py` optionally forwards stdin/stdout unchanged and records only model-control and permission messages.
5. Create a fresh empty git repository and add it using `t3 project add --base-dir .t3 <workspace>`. Seed each thread with `t3 thread start --base-dir .t3 --provider proof --model proof-a --title <title> --wait --json <workspace> "Reply briefly to establish history."`.

## Same UI interaction on both commits

Full access: open the seeded thread, select proof-b in the model picker, dismiss the picker, type “Reply after the model switch.”, and press Enter. Base fails turn start with a permission-handler error. Head completes “Synthetic reply complete.”.

Supervised: start another seeded thread, select Supervised in the permission picker, select proof-b, and send “Reply after the supervised switch.”. Head replies to the nested permission with selected deny; Hermes returns model_switch_cancelled. Base returns an ACP handler error. Both UIs show session-open failure because the switch did not succeed. Select proof-a again and send “Reply after returning to the previous model.”. Both complete.

Reopen: start another Full access thread, call the app's `threadEnvironment.stopSession` command with its thread ID (same command used by Disconnect agent session), select proof-b, and send “Retry the selected model after closing the session.”. The reopened session reapplies proof-b. Base returns a permission-handler error and fails session opening. Head selects allow_once and completes the reply.

No dedicated Retry button was present for these provider failures; the reopen recording submits the retry message through the composer.

The low confirmation threshold and synthetic usage intentionally replace a costly 100,000-token paid conversation. Hermes still executes its real context guard, model switch, ACP permission request, and session persistence; only inference is synthetic.

Recordings of the first Full access comparison mask the unrelated right-side environment details panel for privacy. Later recordings have that panel closed. Static captures are full client views with that panel closed; identical screenshot pairs were checked before selecting the stills.

AI-assisted; verified with the tests and tools listed above.
