docs(config): describe the model list refresh exception

The Pi extension provider baseline section stated that every later
provider registration is a no-op, naming session_start as an example.
That is now the exact case that is allowed, so the shipped configuration
reference contradicted the behavior. docs/config.md is in the package
files allowlist, so this text reaches users.

Describe what stays frozen, the two conditions under which a known
provider's model list refresh is applied, and why function-valued fields
are always rejected. Record the accepted trade-offs: catalogs are shared
daemon-wide state with last-registration-wins, and a model entry's own
baseUrl/headers take precedence over the provider-level values. Split
the log behavior out and note that ignored mutations are de-duplicated
per provider while applied refreshes are always logged.

Also correct the reload-behavior bullet: a restart is needed to add or
remove a provider or change its connection settings, but not for a known
provider refreshing only its model list.
This commit is contained in:
Federico Jaramillo Martinez
2026-07-26 14:14:30 +02:00
parent 531ccf7b2a
commit 36d67262ec
2 changed files with 72 additions and 14 deletions
+52 -11
View File
@@ -177,7 +177,7 @@
<li><code>pathAccess</code>: applies on the next request; existing file views may need a browser refresh.</li> <li><code>pathAccess</code>: applies on the next request; existing file views may need a browser refresh.</li>
<li><code>uploads.defaultFolder</code>: applies to newly opened Files upload dialogs and new direct drag/drop batches after config/workspace refresh.</li> <li><code>uploads.defaultFolder</code>: applies to newly opened Files upload dialogs and new direct drag/drop batches after config/workspace refresh.</li>
<li><code>plugins</code>: reload the browser tab after changing PI WEB plugin enablement.</li> <li><code>plugins</code>: reload the browser tab after changing PI WEB plugin enablement.</li>
<li>Pi package install/remove/update: not a PI WEB config key; after a mutation, type <code>/reload</code> in each idle PI WEB session on the target machine to refresh ordinary Pi resources such as extensions, skills, prompt templates, themes, and context/system prompt files. Reload the browser page separately for PI WEB browser plugin changes. If a global Pi extension adds, removes, or changes a provider, manually restart <code>pi-web-sessiond.service</code>; <code>/reload</code> cannot change the startup provider baseline. See <a href="#pi-extension-provider-baseline">Pi extension provider baseline</a>.</li> <li>Pi package install/remove/update: not a PI WEB config key; after a mutation, type <code>/reload</code> in each idle PI WEB session on the target machine to refresh ordinary Pi resources such as extensions, skills, prompt templates, themes, and context/system prompt files. Reload the browser page separately for PI WEB browser plugin changes. If a global Pi extension adds or removes a provider, or changes a provider's connection settings, manually restart <code>pi-web-sessiond.service</code>; <code>/reload</code> cannot change the startup provider baseline. A known provider refreshing only its own model list is applied without a restart. See <a href="#pi-extension-provider-baseline">Pi extension provider baseline</a>.</li>
<li><code>shortcuts</code>: saved settings apply in the browser after config refresh/save.</li> <li><code>shortcuts</code>: saved settings apply in the browser after config refresh/save.</li>
</ul> </ul>
</section> </section>
@@ -628,18 +628,59 @@
providers from the active agent directory's <code>models.json</code>. providers from the active agent directory's <code>models.json</code>.
</p> </p>
<p> <p>
After startup capture, every later Pi extension provider registration, native registration, and After startup capture, a provider's connection settings are fixed for the daemon lifetime. Later
unregistration is a no-op, regardless of source or provider ID. This includes global extensions replayed attempts to add a provider, replace an existing provider's configuration, register a native provider, or
while sessions load, project extensions attempting to add or replace a provider, same-ID replacement or unregister a provider are no-ops, regardless of source or provider ID. This includes project extensions
unregistration, lifecycle callbacks such as <code>session_start</code>, and <code>/reload</code>. The attempting to add or replace a provider, lifecycle callbacks such as <code>session_start</code>, and
captured provider stays unchanged while non-provider Pi extension features continue to load and reload <code>/reload</code>. Non-provider Pi extension features continue to load and reload normally.
normally. </p>
<h3>Model list refresh for a known provider</h3>
<p>
One narrow update is applied after startup: a provider captured in the baseline may refresh
<strong>its own model list</strong>. Extensions that fetch an updated catalog typically re-send their
complete provider configuration, so PI WEB compares the incoming registration against the recorded
baseline and applies it only when both hold:
</p>
<ul>
<li>the provider ID is already in the startup baseline, and</li>
<li>
every field except the model list is unchanged — <code>name</code>, <code>baseUrl</code>,
<code>apiKey</code>, <code>api</code>, <code>streamSimple</code>, <code>headers</code>,
<code>authHeader</code>, <code>oauth</code>, and <code>refreshModels</code>.
</li>
</ul>
<p>
Anything else stays a no-op, including a provider that was not in the baseline and a known provider
whose credentials, base URL, or API surface differ from startup. Function-valued fields cannot be
compared by value, so a registration that supplies a new <code>streamSimple</code>,
<code>refreshModels</code>, or <code>oauth</code> implementation is treated as a change and ignored.
</p> </p>
<p> <p>
Ignored mutations are written to the session-daemon log once per operation and provider ID. The log entry An applied refresh becomes the new comparison point, so a provider can refresh repeatedly. Re-sending an
contains no provider configuration or credentials, and PI WEB does not show a session warning or unchanged model list is a replay rather than an update and is ignored. Refreshed models are visible to
notification. This prevents accidental provider, configuration, or credential contamination between sessions immediately; no restart and no network request is involved, because the extension has already
projects; it is not a security boundary because Pi extensions remain trusted daemon code. produced the catalog.
</p>
<p>
Model lists are shared daemon-wide state. If extensions in two workspaces register different model lists
for the same provider ID, the last registration wins. A model entry may also carry its own
<code>baseUrl</code> and <code>headers</code>, which take precedence over the provider-level values for
that model, so an accepted refresh can change where requests for those models are sent. Both are
accepted trade-offs: a catalog is treated as a property of the provider rather than of the project, and
Pi extensions are trusted daemon code.
</p>
<h3>Provider decisions in the daemon log</h3>
<p>
Ignored mutations are written to the session-daemon log once per operation and provider ID, so a
replaying extension cannot flood the log. Applied model list refreshes are logged every time, with the
resulting model count, because each one changes shared runtime state. Neither entry contains provider
configuration or credentials, and PI WEB does not show a session warning or notification.
</p>
<p>
This prevents accidental provider, configuration, or credential contamination between projects; it is
not a security boundary because Pi extensions remain trusted daemon code.
</p> </p>
<p> <p>
Configure providers before the daemon starts: use the active agent directory's Configure providers before the daemon starts: use the active agent directory's
+20 -3
View File
@@ -43,7 +43,7 @@ Process restarts depend on the key:
- `pathAccess`: applies on the next request; existing file views may need a browser refresh. - `pathAccess`: applies on the next request; existing file views may need a browser refresh.
- `uploads.defaultFolder`: applies to newly opened Files upload dialogs and new direct drag/drop batches after config/workspace refresh. - `uploads.defaultFolder`: applies to newly opened Files upload dialogs and new direct drag/drop batches after config/workspace refresh.
- `plugins`: reload the browser tab after changing PI WEB plugin enablement. - `plugins`: reload the browser tab after changing PI WEB plugin enablement.
- Pi package install/remove/update: not a PI WEB config key; after a mutation, type `/reload` in each idle PI WEB session on the target machine to refresh ordinary Pi resources such as extensions, skills, prompt templates, themes, and context/system prompt files. Reload the browser page separately for PI WEB browser plugin changes. If a global Pi extension adds, removes, or changes a provider, manually restart `pi-web-sessiond.service`; `/reload` cannot change the startup provider baseline. See [Pi extension provider baseline](#pi-extension-provider-baseline). - Pi package install/remove/update: not a PI WEB config key; after a mutation, type `/reload` in each idle PI WEB session on the target machine to refresh ordinary Pi resources such as extensions, skills, prompt templates, themes, and context/system prompt files. Reload the browser page separately for PI WEB browser plugin changes. If a global Pi extension adds or removes a provider, or changes a provider's connection settings, manually restart `pi-web-sessiond.service`; `/reload` cannot change the startup provider baseline. A known provider refreshing only its own model list is applied without a restart. See [Pi extension provider baseline](#pi-extension-provider-baseline).
- `shortcuts`: saved settings apply in the browser after config refresh/save. - `shortcuts`: saved settings apply in the browser after config refresh/save.
## Global config example ## Global config example
@@ -213,9 +213,26 @@ This policy applies to **Pi runtime extensions**, not PI WEB browser plugins. Pi
PI WEB shares one model runtime across all sessions. When the session daemon starts, before any project resources load, it initializes global Pi extensions from the active agent profile (`agent.dir`), including extensions supplied by globally configured Pi packages. Provider registrations made by synchronous or awaited asynchronous extension factories during this bootstrap join the shared baseline. PI WEB captures both config-form registrations (`pi.registerProvider("id", config)`) and native-provider registrations (`pi.registerProvider(provider)`), alongside Pi built-ins, environment credentials, and providers from the active agent directory's `models.json`. PI WEB shares one model runtime across all sessions. When the session daemon starts, before any project resources load, it initializes global Pi extensions from the active agent profile (`agent.dir`), including extensions supplied by globally configured Pi packages. Provider registrations made by synchronous or awaited asynchronous extension factories during this bootstrap join the shared baseline. PI WEB captures both config-form registrations (`pi.registerProvider("id", config)`) and native-provider registrations (`pi.registerProvider(provider)`), alongside Pi built-ins, environment credentials, and providers from the active agent directory's `models.json`.
After startup capture, every later Pi extension provider registration, native registration, and unregistration is a no-op, regardless of source or provider ID. This includes global extensions replayed while sessions load, project extensions attempting to add or replace a provider, same-ID replacement or unregistration, lifecycle callbacks such as `session_start`, and `/reload`. The captured provider stays unchanged while non-provider Pi extension features continue to load and reload normally. After startup capture, a provider's connection settings are fixed for the daemon lifetime. Later attempts to add a provider, replace an existing provider's configuration, register a native provider, or unregister a provider are no-ops, regardless of source or provider ID. This includes project extensions attempting to add or replace a provider, lifecycle callbacks such as `session_start`, and `/reload`. Non-provider Pi extension features continue to load and reload normally.
Ignored mutations are written to the session-daemon log once per operation and provider ID. The log entry contains no provider configuration or credentials, and PI WEB does not show a session warning or notification. This prevents accidental provider, configuration, or credential contamination between projects; it is not a security boundary because Pi extensions remain trusted daemon code. #### Model list refresh for a known provider
One narrow update is applied after startup: a provider captured in the baseline may refresh **its own model list**. Extensions that fetch an updated catalog typically re-send their complete provider configuration, so PI WEB compares the incoming registration against the recorded baseline and applies it only when both hold:
- the provider ID is already in the startup baseline, and
- every field except the model list is unchanged — `name`, `baseUrl`, `apiKey`, `api`, `streamSimple`, `headers`, `authHeader`, `oauth`, and `refreshModels`.
Anything else stays a no-op, including a provider that was not in the baseline and a known provider whose credentials, base URL, or API surface differ from startup. Function-valued fields cannot be compared by value, so a registration that supplies a new `streamSimple`, `refreshModels`, or `oauth` implementation is treated as a change and ignored.
An applied refresh becomes the new comparison point, so a provider can refresh repeatedly. Re-sending an unchanged model list is a replay rather than an update and is ignored. Refreshed models are visible to sessions immediately; no restart and no network request is involved, because the extension has already produced the catalog.
Model lists are shared daemon-wide state. If extensions in two workspaces register different model lists for the same provider ID, the last registration wins. A model entry may also carry its own `baseUrl` and `headers`, which take precedence over the provider-level values for that model, so an accepted refresh can change where requests for those models are sent. Both are accepted trade-offs: a catalog is treated as a property of the provider rather than of the project, and Pi extensions are trusted daemon code.
#### Provider decisions in the daemon log
Ignored mutations are written to the session-daemon log once per operation and provider ID, so a replaying extension cannot flood the log. Applied model list refreshes are logged every time, with the resulting model count, because each one changes shared runtime state. Neither entry contains provider configuration or credentials, and PI WEB does not show a session warning or notification.
This prevents accidental provider, configuration, or credential contamination between projects; it is not a security boundary because Pi extensions remain trusted daemon code.
Configure providers before the daemon starts: use the active agent directory's `models.json`, or install the Pi extension globally in that agent profile. Project Pi extensions and project-level `models.json` files cannot add providers to PI WEB's shared baseline. After updating PI WEB—or after installing, removing, or updating a global Pi extension that registers providers—manually restart `pi-web-sessiond.service` (`systemctl --user restart pi-web-sessiond`). Restarting only the web/API service and running `/reload` do not rebuild the baseline. Configure providers before the daemon starts: use the active agent directory's `models.json`, or install the Pi extension globally in that agent profile. Project Pi extensions and project-level `models.json` files cannot add providers to PI WEB's shared baseline. After updating PI WEB—or after installing, removing, or updating a global Pi extension that registers providers—manually restart `pi-web-sessiond.service` (`systemctl --user restart pi-web-sessiond`). Restarting only the web/API service and running `/reload` do not rebuild the baseline.