feat: unify installation model catalog

This commit is contained in:
Codex
2026-09-02 18:45:33 +02:00
parent ae053961a3
commit 7b7927bfe5
169 changed files with 3696 additions and 4572 deletions
+128 -177
View File
@@ -1,207 +1,158 @@
# Local Pi model configuration
# Installation Model Catalog
Pi orchestrates the NL-to-SQL workflow and resolves built-in models and OpenAI-compatible providers
declared in the local catalog. ThothII applies a stricter rule than Pi: code in
`harness/.pi/extensions/` cannot register a provider or custom model. Endpoints, protocols,
compatibility settings, and model identifiers belong exclusively in the local files
`deploy/pi/models.json` and `deploy/pi/settings.json`.
ThothII has one operator-authored model source: `modelCatalog` in
`deploy/<installation-id>/thothii-installation.yaml`. It declares models used by interactive Pi
sessions, metadata generation, and the internal embedding service. Workspace descriptors never
declare providers, model allowlists, defaults, embeddings, dimensions, or vector-store settings.
> **ThothII operator note:** ThothII runs Pi only in Docker Compose. Paths under
> `~/.pi/agent/` in this document describe Pi's container-side behavior. Operators edit
> `deploy/pi/models.json` and `deploy/pi/settings.json` in the ThothII project root and use
> the protected host credential file selected by `PI_AUTH_FILE`; they do not edit files inside
> the running container.
Do not edit `deploy/pi/models.json`, `deploy/pi/settings.json`, files under `generated/`, or
provider/model environment defaults. Those former sources are retired.
## Credentials in the backend container
## Minimal catalog
In production, configure one generic source: the `THT_SECRETS_FILE` bundle with the
`THT_MODEL_API_KEY` entry, which is the default for the unified distribution, or
`THT_MODEL_API_KEY_FILE` as an absolute secret-file path. Do not put the key value in `.env`.
For each process, `PiProcessManager` rereads and validates the source, normalizes the selected
provider, and passes only the appropriate native variable to the Pi child
(`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `ZAI_API_KEY`, and so on). It removes the
generic path, keys for unselected providers, and the old `PI_PROVIDER_API_KEY` from the child
environment. For a custom provider, the backend derives the variable name from the declarative
`apiKey` field in `models.json`; a literal value means the catalog is self-contained. Provider-name
exceptions are not compiled into the code. A missing or insecure secret fails before spawn with a
sanitized error.
```yaml
schemaVersion: 2
modelCatalog:
defaults:
session: zai/glm-5.3
metadataGeneration: zai/glm-5.3
The generic source supports only single-key providers: `ant-ling`, `anthropic`,
`cerebras`, `deepseek`, `fireworks`, `github-copilot`, `google` (including the `gemini` alias),
`google-vertex` in API-key mode, `groq`, `huggingface`, `kimi-coding`, `minimax`, `minimax-cn`,
`mistral`, `moonshotai`, `moonshotai-cn`, `nvidia`, `openai`, `opencode`, `opencode-go`,
`openrouter`, `together`, `vercel-ai-gateway`, `xai`, the four `xiaomi*` providers, `zai`, and
`zai-coding-cn`.
embedding:
id: ollama/qwen3-embedding:0.6b
dimensions: 1024
The composite providers `amazon-bedrock`, `azure-openai-responses`, `cloudflare-workers-ai`, and
`cloudflare-ai-gateway` cannot be represented by one file. Selection fails before spawn, including
when models are listed. AWS, Azure, and Cloudflare environment credentials are still removed. A
future provider-specific configuration will be needed to support these bundles unambiguously.
## Model sources
### 1. Built-in (compiled into Pi)
Pi ships with a list of known models (`models.generated.js` in the `@earendil-works/pi-ai` package):
Anthropic, OpenAI, Google, and third-party providers with well-known public APIs such as DeepSeek.
These require **no configuration** beyond credentials, provided through an environment variable or
`pi auth`.
`deepseek/deepseek-v4-pro` is one of these models. It is included in Pi because `api.deepseek.com`
is a documented public API, not an internal endpoint.
### 2. User-level `models.json` (`~/.pi/agent/models.json`)
For an **OpenAI-compatible** endpoint that is not built in but needs no special transport logic,
declare it with its baseUrl, apiKey, and model list. This file exists **only at user level**; there is
no project-level equivalent, and `./.pi/models.json` is not read.
In ThothII-managed installations, the file must be entirely declarative. ThothII recursively
rejects any JSON value that starts with `!`, including values inside `headers`, `models`,
`modelOverrides`, `compat`, arrays, or fields it does not yet know. Pi 0.80.3 would treat that
prefix as a shell command when making a request, so managed model catalogs do not allow it. The
returned error is fixed and contains no command, path, or secret.
For secrets, use an environment reference such as `"$ZAI_API_KEY"` or `"${ZAI_API_KEY}"`. The
backend can populate the native variable for the selected provider by reading
`THT_MODEL_API_KEY_FILE`, or from the `THT_SECRETS_FILE` bundle (`THT_MODEL_API_KEY`). Credentials
can also come from the protected file mounted through `PI_AUTH_FILE`, with `apiKey` omitted from
`models.json`. There is no direct secret-file reference syntax in `models.json`. With `THT_MODEL_*`
sources, ThothII reads the file and turns it into the Pi child's environment variable; `PI_AUTH_FILE`
is mounted instead as a protected Pi credential store. For a literal leading exclamation mark, Pi's
declarative syntax is `$!`, not `!`.
Real example in use on this machine: GLM (provider `zai`):
```json
{
"providers": {
"zai": {
"baseUrl": "https://api.z.ai/api/coding/paas/v4",
"api": "openai-completions",
"apiKey": "$ZAI_API_KEY",
"models": [
{
"id": "glm-5.2",
"name": "GLM-5.2",
"reasoning": true,
"contextWindow": 200000,
"maxTokens": 131072
}
]
}
}
}
providers:
zai:
endpoint:
baseUrl: https://api.z.ai/api/coding/paas/v4
authentication:
mode: secret_env
apiKeyEnv: ZAI_API_KEY
session:
mode: openai_compatible
metadataGeneration:
litellmProvider: openai
models:
glm-5.3:
label: GLM 5.3
session:
reasoning: true
contextWindow: 200000
maxTokens: 131072
metadataGeneration: {}
```
Because it is user-level, GLM is visible to **every project**.
Provider and model entries are maps. The keys form the canonical identity
`<provider-key>/<model-key>`; `label` is only display text. A model is eligible for a use only when
it contains that use block:
### Extension providers: not allowed in ThothII
- `session` makes it selectable for interactive sessions;
- `metadataGeneration` makes it selectable for description generation;
- `embedding` is a single installation-level model rather than a selectable list.
Pi technically supports providers registered by JavaScript extensions, but ThothII does not use
that capability. Project extensions are reserved for the workflow and gates; they must not contain
`registerProvider(...)`. An endpoint that cannot be described by the OpenAI-compatible catalog is
not supported by this installation until the declarative contract is extended generically.
`defaults.session` is required. `defaults.metadataGeneration` is required exactly when at least
one metadata-generation model exists. A session manifest pins its canonical identity, so removing a
model never silently changes an existing session: resume fails with `model_unavailable`.
## Summary table (current machine state)
## Session adapters
| Model | Level | Why | Visibility |
|---|---|---|---|
| `deepseek/deepseek-v4-pro` | Built-in Pi | Known public API, already in the build | All projects |
| `deepseek/deepseek-v4-flash` | Built-in Pi | Known public API, already in the build | All projects |
| `zai/glm-5.3` | `deploy/pi/models.json` | Custom OpenAI-compatible endpoint | ThothII installation |
| `local-qwen/qwen3.6-35b-a3b` | `deploy/pi/models.json` | Locally configured OpenAI-compatible endpoint | ThothII installation |
Use `pi_builtin` for a model whose technical definition ships with Pi:
## Choosing the right level for a new model
1. **Is the endpoint a public API already known to Pi?** Enable the exact identifier in `deploy/pi/settings.json`.
2. **Is it OpenAI-compatible but not built in?** Declare it in `deploy/pi/models.json`, then enable it in `deploy/pi/settings.json`.
3. **Does it require provider-specific transport code?** Do not add a provider-specific extension. The provider is unsupported until a generic declarative capability exists.
---
## User versus project scope: general summary
In addition to models, Pi loads other resources from two parallel trees: `~/.pi/agent/` (user) and
`<cwd>/.pi/` (project, resolved from the directory where `pi` is launched).
| File/Directory | Livello utente | Livello progetto | Auto-discovery | Precedenza |
|---|---|---|---|---|
| `models.json` | `~/.pi/agent/models.json` | not supported | no | user only |
| `settings.json` | `~/.pi/agent/settings.json` | `./.pi/settings.json` | no | project overrides user |
| `extensions/` | `~/.pi/agent/extensions/` | `./.pi/extensions/` | yes (`.ts`/`.js`) | merged (project + user) |
| `prompts/` | `~/.pi/agent/prompts/` | `./.pi/prompts/` | yes (`.md`) | merged |
| `themes/` | `~/.pi/agent/themes/` | `./.pi/themes/` | yes (`.json`) | project preferred |
| `skills/` | `~/.pi/agent/skills/` | `./.pi/skills/` | yes (`.md`) | merged |
### Real example: ThothII
```
harness/.pi/
├── extensions/
│ ├── tht-gate.js # human-in-the-loop gate
│ └── gate/
│ ├── core/ # shared gate enforcement and utilities
│ ├── disambiguation/ # F1/F3 policy
│ └── memory/ # F2/F8 policy
├── settings.json # optional override of user settings
└── themes/
└── thothii-mono.json # project theme
```yaml
deepseek:
authentication:
mode: pi_auth
session:
mode: pi_builtin
models:
deepseek-v4-pro:
session: {}
```
### Behavior relative to cwd
Use `openai_compatible` for an explicit compatible endpoint. Each eligible session model must then
declare the technical limits Pi needs. `upstreamModel` is optional and is used only when the
endpoint expects a model name different from the catalog key.
The directory from which you launch `pi` determines which workflow extensions are found, but not
which models ThothII makes available. The catalog is mounted in the container's Pi agent directory.
Provider integrations remain declarative. Do not register providers from
`harness/.pi/extensions/`; those extensions implement the workflow and human gates only.
## Authentication
Every provider chooses one explicit mode:
- `secret_env` names an approved key in the protected ThothII secret bundle through `apiKeyEnv`;
- `pi_auth` uses Pi's protected authentication projection and is valid only for session-only
`pi_builtin` providers;
- `none` is valid only with an explicit keyless endpoint.
Secret values never belong in installation YAML, generated files, logs, CLI arguments, or browser
requests. The YAML contains only an environment-variable name or an authentication mode. Pi's
protected credential file remains selected by the installation authentication configuration.
## Generated runtime projections
Before Compose starts, `tht` validates the installation and atomically writes deterministic files
under `deploy/<installation-id>/generated/`:
```text
generated/
├── catalog.json
├── pi/
│ ├── models.json
│ └── settings.json
└── compose.model-catalog.yaml
```
The normalized catalog is consumed by the backend. The Pi files and Compose override are boundary
adapters. They are not configuration sources and are excluded from backup. Restore regenerates
them from the installation descriptor.
Run all lifecycle commands from the project root and select the descriptor explicitly when more
than one installation exists:
```bash
# From harness/: load the project gate and mounted local catalog
cd /path/to/ThothII/harness
pi --model local-qwen/qwen3.6-35b-a3b "..."
INSTALLATION=/absolute/path/deploy/example/thothii-installation.yaml
tht --installation "$INSTALLATION" start
tht --installation "$INSTALLATION" doctor
```
The ThothII backend always launches Pi with `cwd: harnessDir` for the workflow. Model availability
continues to depend only on `models.json`, `settings.json`, and local credentials.
After editing `modelCatalog` or provider credentials, reload the current Pi image. Restart validates
the YAML and regenerates projections before recreating `core`:
---
## Auto-discovery trap: `.mjs` is ignored
Pi's extension auto-discovery pattern is **`/\.(ts|js)$/`**; it does not include `.mjs`.
```
.pi/extensions/
├── my-extension.js ✅ auto-loaded
├── my-extension.ts ✅ auto-loaded
├── my-extension.mjs ❌ silently ignored (does not match the pattern)
└── shared-module.mjs ✅ suitable for helper modules (deliberately not loaded as an extension)
```
If an extension imports a shared module, use `.mjs` so Pi does not treat it as an extension on its own.
---
## Verifica
### List models
```bash
pi --list-models
tht --installation "$INSTALLATION" pi restart --yes --drain
tht --installation "$INSTALLATION" pi doctor
tht --installation "$INSTALLATION" pi test
```
This shows built-in models and models declared in `models.json`.
### Check the catalog used by the application
`tht pi update` changes the Pi version; it is not the configuration command. There is no
`tht pi configure` and no separate apply command.
## Migrating a legacy installation
The migrator reads the former installation `metadataGeneration` block and the two former Pi JSON
files, but never modifies them. Supply the facts that cannot be inferred safely and write a separate
candidate:
```bash
cd harness # or the project's relevant cwd
pi --mode rpc
# poi: {"type": "get_available_models", "id": "1"}
tht --installation /absolute/path/legacy-installation.yaml installation migrate \
--output /absolute/path/thothii-installation.v2.yaml \
--session-default zai/glm-5.3 \
--embedding-id ollama/qwen3-embedding:0.6b \
--embedding-dimensions 1024
```
The RPC response must include only built-in models or models declared in the local catalog.
---
Review the candidate, move the legacy source files out of the installation only after approval,
then select the v2 descriptor. Ambiguous aliases, endpoint conflicts, or missing authentication
facts produce field-level errors; the migrator does not guess.
## Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| `Model "X/Y" not found` | Provider/model missing from `models.json` or identifier missing from `enabledModels` | Fix the two local files and reload Pi |
| Project settings not applied | Project `settings.json` has a syntax error, or `pi` is launched from the wrong cwd | Validate the JSON and check the cwd |
| Symptom | Meaning | Action |
| --- | --- | --- |
| `migration_required` | A retired model source or installation schema is still present | Run the installation migrator and review its candidate |
| Unknown session or metadata default | The canonical ID is missing the corresponding use block | Correct the provider/model key or add the intended use block |
| Generated projection drift | Runtime files differ from the descriptor-derived bytes | Run `tht start` or `tht pi restart --yes --drain` |
| `model_unavailable` on resume | The session's pinned model is no longer session-eligible | Restore that catalog entry or keep the session unavailable; do not remap it |
| Provider smoke failure | Credentials, endpoint, or provider availability is invalid | Correct the protected credential or catalog endpoint, restart, then run `tht pi test` |