# Agnosticismo runtime / provider / modello

Contratto architetturale del framework Sophia. La sintesi normativa e' nella guida viva (`docs/mcp-skills-agents-development-guide.md`, §2.2.1); questo documento descrive dove vive ciascun livello e come aggiungere un runtime.

## Livelli

| Livello | Cosa contiene | Dove |
| --- | --- | --- |
| Core canonico | ruoli, intenti, policy di delega, capability richieste, hook per fase | `docs/agents/canonical-subagents.yaml`, `skills/`, `shared-agent-rules/`, `CANONICAL_HOOKS` |
| Runtime registry | runtime supportati, path dei profili agente, config MCP, stato delle capability | `scripts/runtime/runtime-registry.js` |
| Adapter host | nomi dei tool, sandbox, matcher ed eventi hook, formati dei profili | `RUNTIME_TOOL_ADAPTERS`, `writePolicyFor` (`scripts/portable-agents-lib.js`), `HOST_HOOK_MAPPINGS` (`scripts/hooks-generator-lib.js`), generatori `scripts/generate-*-hooks.js` |
| Proiezioni | output one-way del canonico | `.codex/`, `.claude/`, `.copilot/`, `.gemini/agents/`, regole Cursor, `gpts/` |
| Dati osservati | modello e provider usati in una sessione | adapter di `analytics-node`, rollout e hook log |

Runtime (harness) e provider del modello sono concetti separati. Un runtime puo' usare provider diversi (BYOK, gateway, provider custom), quindi il registry non contiene alcun campo provider o modello e `validateRuntimeRegistry` lo rifiuta. Il modello risolto e' stato di sessione: i ruoli usano `execution_policy.model_policy: inherit` e un override voluto va nella configurazione locale del runtime, non nel ruolo.

## Capability

Vocabolario (`CAPABILITIES` nel registry): `repo.search`, `filesystem.read`, `filesystem.write`, `shell.execute`, `web.search`, `web.fetch`, `browser.interactive`, `image.generate`, `user.question.structured`, `subagent.spawn`, `subagent.named_role`, `subagent.context_fork`, `hooks.session_start`, `hooks.user_prompt_submit`, `hooks.pre_tool`, `mcp.client`.

- Stati: `available`, `unavailable` e `unknown`. Sono marcati `available` o `unavailable` solo i valori verificati su un client reale. `unknown` non vale mai `available`, e un runtime sconosciuto restituisce `unknown` (`capabilityStatus`).
- Una skill condivisa nomina la capability e dichiara il fallback: per esempio `image.generate` -> `capability_unavailable:image.generate`, oppure `user.question.structured` -> domanda diretta in chat. Il nome del tool host compare solo in note host o sulla stessa riga della capability che realizza.
- I ruoli agente dichiarano `tool_capabilities`. Da questo campo gli adapter derivano i tool Copilot, Gemini e Claude (`toolsForRuntime`), mentre `writePolicyFor` ricava la write policy e il sandbox Codex. Il campo legacy `runtime_permissions` viene ancora letto, deve essere coerente con le capability ed emette un warning di migrazione.
- Copilot e Claude Code trattano `tools` come allowlist, MCP compresi, e non accettano un wildcard solo-MCP in quel campo: `mcp.client` viene concessa per ogni server del registry Sophia (`<server>/*` su Copilot, `mcp__<server>` su Claude Code). Un test verifica che nessuna capability dichiarata da un ruolo e `available` sul runtime resti senza mapping nell'adapter.

## Hook

`CANONICAL_HOOKS` contiene solo `event`, `script`, `timeout` e `statusMessage`. `HOST_HOOK_MAPPINGS` associa ogni script canonico a una voce esplicita per ciascun host:

- `nested`: matcher per Codex, Claude Code e Copilot;
- `antigravity`: matcher per Antigravity;
- `cursor`: evento Cursor.

`null` indica un N/A documentato, per esempio SessionStart su Cursor, e mai un'approssimazione. La vista `HOOKS` conserva la forma usata dai consumer.

## Enforcement

`node scripts/runtime/agent-contract-audit.js --strict`, eseguito anche da `test:routing` e dagli smoke runtime, classifica ogni riscontro per area:

- runtime-profile;
- canonical-config;
- normative;
- live-doc;
- historical-doc;
- test-fixture;
- parser-test;
- core-code.

Gravita' per categoria di riscontro:

| Riscontro | Gravita' |
| --- | --- |
| Model ID concreti, campi modello host-specifici, credenziali provider, tool host non gated | errore nelle aree runtime-profile, canonical-config e normative; warning nelle doc vive; dato nelle aree storiche e di test |
| `agent_type` non canonici e fork full-history | errore nelle aree normative |
| Import da `gpts/` nel codice core | errore ovunque: le proiezioni sono solo output |

Le eccezioni intenzionali sono registrate in `EXCEPTIONS` con una motivazione:

- note host `impeccable-codex.md` (tool `image_gen` come realizzazione di `image.generate`).

Graphify, dopo l'adozione del workflow read-only, non richiede più eccezioni.

Un'eccezione che non corrisponde piu' a nulla viene segnalata come inutilizzata.

I test di parita' in `scripts/test-user-runtime.js` (blocco `Test 0-provider-agnostic`) verificano che le liste locali dei moduli portabili coincidano con il registry. Questi moduli non possono importarlo: gli hook vengono copiati in directory temporanee e la GUI e' un bundle browser/UMD. Le liste coperte sono:

- `RUNTIME_LIST` della GUI;
- `RUNTIME_IDS` di `agent-catalog-runtime.mjs`;
- `HOOK_RUNTIME_IDS`;
- `SOPHIA_MCP_SERVER_IDS` di `portable-agents-lib.js` (parità con `MCP_SERVER_REGISTRY`, verificata in `tests/portable-agents.test.cjs`).

Gli stessi test verificano anche che:

- ogni hook canonico abbia un mapping esplicito per ogni host;
- le capability siano complete per ogni runtime.

## Aggiungere un runtime

1. Aggiungere la voce in `AI_RUNTIMES` con `supports`, `agentProfile`, `mcpConfig` e capability. Lasciare `unknown` tutto cio' che non e' stato verificato.
2. Aggiungere l'adapter: tool in `RUNTIME_TOOL_ADAPTERS` e hook in `HOST_HOOK_MAPPINGS` e nel generatore dedicato, se il runtime li supporta.
3. Aggiornare le liste locali dei moduli portabili. I test di parita' falliscono finche' non coincidono.
4. Aggiungere il blocco installer specifico (path, merge della config) e il fixture o test del formato.

Installer, availability manifest, scope MCP di progetto, snapshot delle skill e capability MCP derivano gia' dal registry.
