# Wall-E Session Model Preferences

## Problem

Wall-E coding sessions need model switches to behave like session state, not
global Wall-E configuration. A user changing one CTM Wall-E tab from DeepSeek to
Kimi must not change the model used by any other Wall-E tab, future Wall-E chat,
or the Wall-E brain default provider.

The current CTM paths keep only partial state:

- The browser stores the picker selection in `walleState`.
- Each Wall-E JSONL turn records the provider/model used for that turn.
- `startup_tasks.model_id` may preserve a model restore hint, but it has no
  provider column and is lifecycle cache rather than session truth.
- Wall-E brain metadata stores global defaults such as `walle_provider` and
  `walle_model`.

These are useful but not the right owner for a durable per-tab preference.

## Ownership

CTM owns the tab and its session-scoped model preference.

Wall-E owns provider registry, provider credentials, scorecards, global defaults,
and the chat runtime. Wall-E should receive an explicit provider/model when CTM
has a session preference, but CTM must not mutate Wall-E global defaults from the
session picker.

Wall-E JSONL owns audit history. It records which provider/model was used for a
turn, but it is not the preference source of truth.

`startup_tasks` owns crash restore lifecycle. It may mirror a model id for
backward compatibility, but it must not become the authoritative provider/model
mapping.

## Data Model

CTM persists Wall-E session preferences in `session_model_preferences`:

```sql
CREATE TABLE session_model_preferences (
  ctm_session_id TEXT PRIMARY KEY,
  agent_type TEXT NOT NULL DEFAULT 'walle',
  provider_type TEXT NOT NULL DEFAULT '',
  provider_id TEXT NOT NULL DEFAULT '',
  model_id TEXT NOT NULL DEFAULT '',
  registry_id TEXT NOT NULL DEFAULT '',
  scope TEXT NOT NULL DEFAULT 'session',
  source TEXT NOT NULL DEFAULT 'user',
  pinned INTEGER NOT NULL DEFAULT 1,
  created_at TEXT DEFAULT (datetime('now')),
  updated_at TEXT DEFAULT (datetime('now')),
  FOREIGN KEY (ctm_session_id) REFERENCES ctm_sessions(id) ON DELETE CASCADE
);
```

The source-of-truth columns are `ctm_session_id`, `provider_type`, and
`model_id`. `registry_id` and `provider_id` are diagnostic fields for exact
provider-route debugging; normal session picks leave them empty so provider
route policy remains owned by AI Providers.

## Resolution Order

When CTM sends a Wall-E prompt, model selection resolves in this order:

1. Explicit per-message override from the client.
2. CTM `session_model_preferences` for the `ctm_session_id`.
3. Hydrated in-memory session model fields.
4. Wall-E global default for new/unconfigured sessions.
5. Wall-E scorecard/provider fallback.

Pinned manual session preferences disable provider fallback for that turn unless
the user explicitly opts into fallback.

## Events

The Wall-E model picker emits a session-scoped `model-change` message with a
provider-native model id:

```json
{
  "type": "model-change",
  "id": "ctm-session-id",
  "agent_type": "walle",
  "model_id": "kimi-k2.6",
  "model_provider": "moonshot",
  "model_registry_id": "",
  "model_route_mode": "policy",
  "scope": "session"
}
```

The server validates and persists the preference, updates only that in-memory
session, refreshes the startup restore hint, and broadcasts `walle-model` to the
affected session clients.

`model_route_mode: "policy"` means Wall-E resolves the actual provider route at
runtime from AI Providers configuration. For example, a Claude model selected in
a session will still use the configured Anthropic route policy, including
Portkey, without exposing direct-vs-gateway as a session-level choice.

## Invariants

- Changing the model in Wall-E session A never updates Wall-E brain keys
  `walle_provider`, `walle_model`, or `walle_model_*`.
- Changing the model in Wall-E session A never changes session B's
  `session_model_preferences` row.
- Restore hydrates provider and model from CTM session preference before using a
  lifecycle hint or Wall-E global default.
- JSONL turn metadata can explain historical routing but does not overwrite the
  session preference.
- `startup_tasks.model_id` is a compatibility hint only; missing provider
  information there must not cause cross-provider routing.

## Tests

Coverage should include:

- DB preference upsert/read/delete with cascade behavior.
- Wall-E picker sends provider-native model id plus provider type in policy
  route mode.
- A manual selection persists in CTM and is reused on the next prompt.
- Restored Wall-E sessions hydrate provider/model from
  `session_model_preferences`, not only `startup_tasks`.
- Two Wall-E sessions can choose different providers without cross-session
  mutation.
- Wall-E global default keys remain unchanged by session picker changes.
