This repository has been archived on 2026-08-23. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Federico Jaramillo Martinez c2b7cce2d3 feat(sessions): inherit thinking level in spawned sessions
spawn_session and spawn_subsession now forward the dispatching session's
current thinking level to the new session, clamped by pi to the spawned
model's capabilities, instead of falling back to the configured default.
2026-07-30 12:54:24 +02:00

947 lines
57 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Configure PI WEB — config files, uploads, paths, and session tools</title>
<meta
name="description"
content="Configure PI WEB config files, external path access, manual upload defaults, session daemon tools, plugins, shortcuts, and runtime environment variables."
/>
<link rel="canonical" href="https://pi-web.dev/config" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="PI WEB" />
<meta property="og:title" content="Configure PI WEB" />
<meta
property="og:description"
content="Reference for PI WEB config files, path access allowlists, manual upload defaults, session daemon options, plugins, shortcuts, and environment variables."
/>
<meta property="og:url" content="https://pi-web.dev/config" />
<meta property="og:image" content="https://pi-web.dev/assets/pi-web-banner.png" />
<meta property="og:image:alt" content="PI WEB browser UI for persistent Pi Coding Agent sessions" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Configure PI WEB" />
<meta
name="twitter:description"
content="Reference for PI WEB config files, path access allowlists, manual upload defaults, session daemon options, plugins, shortcuts, and environment variables."
/>
<meta name="twitter:image" content="https://pi-web.dev/assets/pi-web-banner.png" />
<link rel="icon" type="image/svg+xml" href="assets/favicon.svg" />
<script>
(() => {
const theme = window.localStorage.getItem("pi-web-theme");
if (theme === "light" || theme === "dark") document.documentElement.dataset.theme = theme;
})();
</script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=IBM+Plex+Sans:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<header class="site-header">
<nav class="container nav" aria-label="Main navigation">
<a class="brand" href="./" aria-label="PI WEB home">PI WEB</a>
<div class="nav-links">
<div class="nav-pages">
<a href="remote-first">Remote-first</a>
<a href="machines">Fleet</a>
<a href="install">Install</a>
<a href="config" aria-current="page">Config</a>
<a href="plugins">Plugins</a>
<a href="faq">FAQ</a>
</div>
<div class="nav-actions">
<a class="github-link" href="https://github.com/jmfederico/pi-web" aria-label="PI WEB on GitHub">
<svg class="github-icon" viewBox="0 0 16 16" aria-hidden="true">
<path
fill="currentColor"
d="M8 0C3.58 0 0 3.67 0 8.2c0 3.63 2.29 6.7 5.47 7.79.4.08.55-.18.55-.4 0-.2-.01-.85-.01-1.55-2.01.38-2.53-.5-2.69-.96-.09-.24-.48-.96-.82-1.16-.28-.16-.68-.56-.01-.57.63-.01 1.08.59 1.23.84.72 1.24 1.87.89 2.33.68.07-.53.28-.89.51-1.09-1.78-.21-3.64-.91-3.64-4.04 0-.89.31-1.62.82-2.2-.08-.2-.36-1.03.08-2.16 0 0 .67-.22 2.2.84A7.4 7.4 0 0 1 8 3.94c.68 0 1.36.09 2 .28 1.53-1.06 2.2-.84 2.2-.84.44 1.13.16 1.96.08 2.16.51.58.82 1.31.82 2.2 0 3.14-1.87 3.83-3.65 4.04.29.26.54.76.54 1.53 0 1.1-.01 1.99-.01 2.27 0 .22.15.49.55.4A8.12 8.12 0 0 0 16 8.2C16 3.67 12.42 0 8 0Z"
/>
</svg>
<span>GitHub</span>
</a>
<button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle light and dark theme">
<span data-theme-icon aria-hidden="true"></span>
<span data-theme-label>Theme</span>
</button>
</div>
</div>
</nav>
</header>
<main>
<section class="page-hero">
<div class="container">
<p class="eyebrow"><span class="pulse"></span> Configuration reference</p>
<h1>Configure PI WEB where your agents work.</h1>
<p>
PI WEB configuration covers the machine-local and project-local settings you usually need: bind address,
trusted development-host settings, UI preferences, PI WEB plugin enablement, file-explorer path access,
manual upload defaults, upload limits, Pi-compatible agent profiles and companion CLIs, and session-daemon tools.
</p>
</div>
</section>
<section class="section compact">
<div class="container doc-layout">
<aside class="toc" aria-label="Config page contents">
<strong>On this page</strong>
<a href="#files">Config files</a>
<a href="#deployment-paths">Deployment paths</a>
<a href="#precedence">Precedence and reloads</a>
<a href="#global-config">Global config</a>
<a href="#project-config">Project config</a>
<a href="#keys">Config matrix</a>
<a href="#path-access">External path access</a>
<a href="#manual-uploads">Manual uploads</a>
<a href="#agent-runtime">Agent profile and companion CLI</a>
<a href="#pi-extension-provider-baseline">Pi extension providers</a>
<a href="#catalog-refresh">Model catalog refresh</a>
<a href="#session-tools">Session tools</a>
<a href="#extension-dialogs">Extension dialogs</a>
<a href="#prompt-completions">Prompt completions</a>
<a href="#completion-tools">Completion tools</a>
</aside>
<div class="doc-content">
<section id="files">
<h2>Config files</h2>
<p>PI WEB uses a global config file for machine-local settings and a project-local file for repository settings.</p>
<ul>
<li><strong>Global config:</strong> <code>$PI_WEB_CONFIG</code>, or <code>$XDG_CONFIG_HOME/pi-web/config.json</code>, or <code>~/.config/pi-web/config.json</code>.</li>
<li><strong>Project config:</strong> <code>&lt;project&gt;/.pi-web/config.json</code> for commit-able project settings.</li>
</ul>
<p>
Each PI WEB machine has its own config. When using Fleet/machine federation, Settings uses the selected
machine for config that affects work running there: the Pi-compatible agent profile and companion CLI,
session daemon tools, PI WEB plugin enablement, external path access, and upload defaults. Gateway/browser-only settings stay local to the gateway:
keyboard shortcuts, remote machine registry/tokens, and gateway host/port/allowed-hosts. Remote servers
that do not advertise selected-machine settings support report those settings as unavailable instead of
silently falling back to the gateway.
</p>
<p>
Pi package settings are separate from PI WEB config. They live in Pi's package-manager settings on the
target machine and are managed by Pi (<code>pi install</code>, <code>pi remove</code>, <code>pi update</code>) or
<strong>Settings → Pi packages</strong>. In a federated setup, <strong>Settings → Pi packages</strong>
targets the currently selected machine. The PI WEB <code>plugins</code> config key only enables or
disables discovered PI WEB browser plugins on the machine whose config you are editing; it does not
install, remove, or update Pi packages.
</p>
<p>
If you installed services with a custom config path, rerun
<code>pi-web install --config /path/to/config.json</code> after changing that path or after upgrading from
a version that only applied the custom path to the web service. This regenerates service files so the
web/API and session daemon use the same <code>PI_WEB_CONFIG</code>.
</p>
</section>
<section id="deployment-paths">
<h2>Reverse-proxy deployment paths</h2>
<p>
The deployment path is not a PI WEB config-file key or environment setting. The published client is
portable: one build works at <code>/</code> and at canonical trailing-slash prefixes such as
<code>/ai/</code> or <code>/test/ai/</code>.
</p>
<p>
For a nested deployment, redirect the slashless prefix to the trailing-slash URL, strip the prefix
before forwarding to PI WEB, and proxy authenticated HTTP and WebSocket traffic through the same
location. Relative browser and PWA URLs then stay within that prefix. See the
<a href="install#reverse-proxy-prefix">reverse proxy deployment example</a> for complete Nginx
configuration.
</p>
</section>
<section id="precedence">
<h2>Precedence and reloads</h2>
<p>Machine-global runtime values are resolved in this order:</p>
<div class="code-card">
<pre><code>defaults → global config file → environment overrides</code></pre>
</div>
<p>
Supported project-local settings are then applied for that project's workspaces. For upload defaults,
<code>&lt;project&gt;/.pi-web/config.json</code> overrides the global value.
</p>
<p>
Environment overrides include <code>PI_WEB_HOST</code>, <code>PI_WEB_PORT</code> / <code>PORT</code>,
<code>PI_WEB_ALLOWED_HOSTS</code>, <code>PI_WEB_MAX_UPLOAD_BYTES</code>, <code>PI_WEB_AGENT_COMMAND</code>,
<code>PI_WEB_AGENT_DIR</code>, <code>PI_WEB_AGENT_SESSION_DIR</code>, <code>PI_CODING_AGENT_DIR</code> /
<code>PI_CODING_AGENT_SESSION_DIR</code> for Pi compatibility, <code>PI_WEB_SPAWN_SESSIONS</code>,
<code>PI_WEB_SUBSESSIONS</code>, and <code>PI_WEB_ASK_USER</code>.
</p>
<ul>
<li><code>host</code> / <code>port</code>: restart the gateway web/API service or process.</li>
<li><code>maxUploadBytes</code>: restart both the web/API process and the session daemon on that machine.</li>
<li><code>agent.command</code> / <code>agent.dir</code> / <code>spawnSessions</code> / <code>subsessions</code> / <code>askUser</code> / <code>extensionDialogsTimeoutMs</code>: restart the session daemon on that machine.</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>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 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>
</ul>
</section>
<section id="global-config">
<h2>Global config example</h2>
<p>
<code>pi-web install</code> creates the initial file. You can also save PI WEB config settings from
<strong>Settings → General</strong>, <strong>Settings → PI WEB plugins</strong>,
<strong>Settings → Keyboard</strong>, and <strong>Settings → Session daemon</strong>. Machine-affecting
Settings fields target the selected machine; gateway host/port/allowed-hosts and keyboard shortcuts stay
local. Pi package operations live separately under <strong>Settings → Pi packages</strong>.
</p>
<div class="code-card">
<div class="copy-row">
<strong>Example config.json</strong>
<button class="copy-button" data-copy="#global-config-example">Copy</button>
</div>
<pre id="global-config-example"><code>{
"host": "127.0.0.1",
"port": 8504,
"pathAccess": {
"allowedPaths": ["~/SDKs", "/opt/reference"]
},
"uploads": {
"defaultFolder": ".pi-web/uploads"
},
"maxUploadBytes": 67108864,
"agent": {
"command": "pi",
"dir": "~/agent-profiles/research"
},
"spawnSessions": true,
"subsessions": false,
"askUser": true,
"extensionDialogsTimeoutMs": 300000,
"plugins": {
"workspace-tasks": { "enabled": true },
"updates": { "enabled": true },
"info": { "enabled": false }
},
"shortcuts": {
"core:view.chat": "mod+1",
"core:session.stop": null
}
}</code></pre>
</div>
</section>
<section id="project-config">
<h2>Project-local config</h2>
<p>
Project-local config lives at <code>&lt;project&gt;/.pi-web/config.json</code>. Use it for settings that should
follow a repository.
</p>
<div class="code-card">
<div class="copy-row">
<strong>.pi-web/config.json</strong>
<button class="copy-button" data-copy="#project-config-example">Copy</button>
</div>
<pre id="project-config-example"><code>{
"version": 1,
"pathAccess": {
"allowedPaths": ["~/SDKs", "/opt/reference"]
},
"uploads": {
"defaultFolder": "manual/uploads"
}
}</code></pre>
</div>
<p>
Project-local <code>pathAccess.allowedPaths</code> entries are merged after the global list and deduplicated.
Paths must still be host-absolute or <code>~</code>-prefixed; relative roots are not supported.
</p>
<p>
Project-local <code>uploads.defaultFolder</code> overrides the global upload destination for workspaces in
that project. Current PI WEB servers include this workspace-effective value on local and federated
workspace responses; older remote servers may omit it and the browser falls back to the global/default
upload folder.
</p>
<p>
Plugins may own separate project files, such as <code>.pi-web/tasks.json</code> for the built-in Workspace
Tasks plugin.
</p>
</section>
<section id="keys">
<h2>Config matrix</h2>
<p>
Use this table as the quick reference for where a setting can live, which environment variable overrides
it, and whether project-local config overrides or merges with global config. Rows with JSON key
<code></code> are runtime-only environment variables, not config-file keys. <code>Global</code> means
machine-global. In Settings, selected-machine-safe global keys (<code>pathAccess</code>, <code>uploads</code>,
<code>maxUploadBytes</code>, <code>agent</code>, <code>spawnSessions</code>, <code>subsessions</code>, <code>askUser</code>, and <code>plugins</code>)
are edited for the selected machine; gateway host/port/allowed-hosts, keyboard shortcuts, and machine
registry/tokens stay local.
</p>
<div class="table-scroll" role="region" aria-label="PI WEB configuration matrix" tabindex="0">
<table class="config-matrix">
<thead>
<tr>
<th scope="col">Config</th>
<th scope="col">JSON key</th>
<th scope="col">Env var</th>
<th scope="col">Scope</th>
<th scope="col">Project-local behavior</th>
<th scope="col">Applies / restart</th>
</tr>
</thead>
<tbody>
<tr class="table-section"><th scope="rowgroup" colspan="6">Config-file keys</th></tr>
<tr>
<td>Web/API bind host</td>
<td><code>host</code></td>
<td><code>PI_WEB_HOST</code></td>
<td>Global</td>
<td>Not supported locally</td>
<td>Restart web/API</td>
</tr>
<tr>
<td>Web/API port</td>
<td><code>port</code></td>
<td><code>PI_WEB_PORT</code>, <code>PORT</code></td>
<td>Global</td>
<td>Not supported locally</td>
<td>Restart web/API</td>
</tr>
<tr>
<td>Dev-server allowed hosts</td>
<td><code>allowedHosts</code></td>
<td><code>PI_WEB_ALLOWED_HOSTS</code></td>
<td>Global</td>
<td>Not supported locally</td>
<td>Restart dev web/UI</td>
</tr>
<tr>
<td>External filesystem roots</td>
<td><code>pathAccess.allowedPaths</code></td>
<td></td>
<td>Global + project</td>
<td><strong>Merges:</strong> global roots first, then project roots; duplicates removed</td>
<td>Next file request; refresh existing views if needed</td>
</tr>
<tr>
<td>Manual file upload default folder</td>
<td><code>uploads.defaultFolder</code></td>
<td></td>
<td>Global + project</td>
<td><strong>Overrides:</strong> project value wins for workspaces in that project; otherwise global/default applies</td>
<td>New Upload dialogs and direct drag/drop batches after config/workspace refresh</td>
</tr>
<tr>
<td>Upload/body limit</td>
<td><code>maxUploadBytes</code></td>
<td><code>PI_WEB_MAX_UPLOAD_BYTES</code></td>
<td>Global</td>
<td>Not supported locally</td>
<td>Restart web/API and session daemon on that machine</td>
</tr>
<tr>
<td>Companion CLI command</td>
<td><code>agent.command</code></td>
<td><code>PI_WEB_AGENT_COMMAND</code></td>
<td>Global/session daemon</td>
<td>Not supported locally</td>
<td>Restart session daemon on that machine; affects doctor/status/update checks</td>
</tr>
<tr>
<td>Agent profile state directory</td>
<td><code>agent.dir</code></td>
<td><code>PI_WEB_AGENT_DIR</code> (<code>PI_CODING_AGENT_DIR</code> for Pi compatibility)</td>
<td>Global/session daemon</td>
<td>Not supported locally</td>
<td>Restart session daemon on that machine; affects auth, models, settings, sessions, Pi packages, and Pi-package-backed PI WEB plugins</td>
</tr>
<tr>
<td>Agent can spawn sessions</td>
<td><code>spawnSessions</code></td>
<td><code>PI_WEB_SPAWN_SESSIONS</code></td>
<td>Global/session daemon</td>
<td>Not supported locally</td>
<td>Restart session daemon on that machine</td>
</tr>
<tr>
<td>Tracked subsessions (beta)</td>
<td><code>subsessions</code></td>
<td><code>PI_WEB_SUBSESSIONS</code></td>
<td>Global/session daemon</td>
<td>Not supported locally; also requires <code>spawnSessions</code></td>
<td>Restart session daemon on that machine</td>
</tr>
<tr>
<td>Agent can post question forms</td>
<td><code>askUser</code></td>
<td><code>PI_WEB_ASK_USER</code></td>
<td>Global/session daemon</td>
<td>Not supported locally</td>
<td>Restart session daemon on that machine</td>
</tr>
<tr>
<td>Extension dialog auto-cancel timeout</td>
<td><code>extensionDialogsTimeoutMs</code></td>
<td></td>
<td>Global/session daemon</td>
<td>Not supported locally</td>
<td>Restart session daemon on that machine</td>
</tr>
<tr>
<td>PI WEB plugin enablement/settings</td>
<td><code>plugins.&lt;id&gt;.enabled</code>, <code>plugins.&lt;id&gt;.settings</code></td>
<td></td>
<td>Global</td>
<td>Not core local config; plugins may read their own project files</td>
<td>Reload browser tab</td>
</tr>
<tr>
<td>Keyboard shortcuts</td>
<td><code>shortcuts.&lt;actionId&gt;</code></td>
<td></td>
<td>Global</td>
<td>Not supported locally</td>
<td>Applies after settings save/config refresh</td>
</tr>
<tr>
<td>Project config version</td>
<td><code>version</code></td>
<td></td>
<td>Project</td>
<td>Project-local only; must be <code>1</code> when present</td>
<td>Next project-config read</td>
</tr>
<tr class="table-section"><th scope="rowgroup" colspan="6">Runtime-only environment variables</th></tr>
<tr>
<td>Global config file path</td>
<td></td>
<td><code>PI_WEB_CONFIG</code> (<code>XDG_CONFIG_HOME</code> affects the default path)</td>
<td>Process/env</td>
<td>Selects the global config file; not a project config</td>
<td>Restart services/processes after changing env</td>
</tr>
<tr>
<td>Managed data directory</td>
<td></td>
<td><code>PI_WEB_DATA_DIR</code></td>
<td>Process/env</td>
<td>Not supported locally</td>
<td>Restart services before changing; existing state stays in the previous data directory</td>
</tr>
<tr>
<td>Session daemon socket</td>
<td></td>
<td><code>PI_WEB_SESSIOND_SOCKET</code></td>
<td>Web/API + session daemon env</td>
<td>Not supported locally</td>
<td>Restart daemon and web/API; both must match</td>
</tr>
<tr>
<td>Session daemon TCP port</td>
<td></td>
<td><code>PI_WEB_SESSIOND_PORT</code></td>
<td>Session daemon env</td>
<td>Not supported locally</td>
<td>Restart session daemon; set <code>PI_WEB_SESSIOND_URL</code> for web/API too</td>
</tr>
<tr>
<td>Session daemon TCP host</td>
<td></td>
<td><code>PI_WEB_SESSIOND_HOST</code></td>
<td>Session daemon env</td>
<td>Not supported locally</td>
<td>Restart session daemon</td>
</tr>
<tr>
<td>Web-to-daemon URL</td>
<td></td>
<td><code>PI_WEB_SESSIOND_URL</code></td>
<td>Web/API env</td>
<td>Not supported locally</td>
<td>Restart web/API</td>
</tr>
<tr>
<td>Projects storage file</td>
<td></td>
<td><code>PI_WEB_PROJECTS_FILE</code></td>
<td>Web/API + session daemon env</td>
<td>Not supported locally</td>
<td>Restart services; advanced state override</td>
</tr>
<tr>
<td>Remote machines storage file</td>
<td></td>
<td><code>PI_WEB_MACHINES_FILE</code></td>
<td>Web/API env</td>
<td>Not supported locally</td>
<td>Restart web/API; advanced state override</td>
</tr>
<tr>
<td>Agent profile session storage directory</td>
<td></td>
<td><code>PI_WEB_AGENT_SESSION_DIR</code> (<code>PI_CODING_AGENT_SESSION_DIR</code> for Pi compatibility)</td>
<td>Session daemon env</td>
<td>Not supported locally</td>
<td>Restart session daemon; env-only session storage override</td>
</tr>
<tr>
<td>Agent profile state directory</td>
<td></td>
<td><code>PI_WEB_AGENT_DIR</code> (<code>PI_CODING_AGENT_DIR</code> for Pi compatibility)</td>
<td>Web/API + session daemon env</td>
<td>Not supported locally</td>
<td>Restart services</td>
</tr>
<tr>
<td>Skip update checks</td>
<td></td>
<td><code>PI_WEB_SKIP_VERSION_CHECK</code>, <code>PI_WEB_OFFLINE</code>, <code>PI_SKIP_VERSION_CHECK</code>, <code>PI_OFFLINE</code></td>
<td>Web/API env</td>
<td>Not supported locally</td>
<td>Restart web/API after env changes</td>
</tr>
<tr>
<td>Offline mode</td>
<td></td>
<td><code>PI_WEB_OFFLINE</code>, <code>PI_OFFLINE</code></td>
<td>Web/API + session daemon env</td>
<td>Not supported locally</td>
<td>
Restart session daemon and web/API after env changes; also disables the
<a href="#catalog-refresh">background model catalog refresh</a>
</td>
</tr>
</tbody>
</table>
</div>
</section>
<section id="path-access">
<h2>External path access</h2>
<p>
<code>pathAccess.allowedPaths</code> grants PI WEB's file explorer and absolute <code>@</code> path
completions access to specific filesystem roots outside the current workspace. By default,
workspace-relative file reads stay inside the workspace and absolute paths are denied.
</p>
<p>Accepted root forms:</p>
<ul>
<li>Unix absolute paths, for example <code>/opt/reference</code>.</li>
<li>Home-relative paths, for example <code>~/SDKs</code>.</li>
<li>Windows absolute paths on Windows hosts, for example <code>C:\Users\dev\SDKs</code>.</li>
</ul>
<p>
When an absolute request is served, PI WEB expands <code>~</code>, canonicalizes configured roots with
<code>realpath</code>, requires roots to be existing directories, and rejects symlink escapes outside the
allowed roots.
</p>
<p>
In <strong>Settings → General</strong>, external filesystem roots are saved on the selected machine.
Gateway host, port, and allowed-hosts fields stay on the gateway config.
</p>
<div class="callout warning">
This is not a sandbox for the underlying Pi Coding Agent or your OS user. It only controls PI WEB UI/API
file exposure outside a workspace. Add only roots you trust PI WEB to list and read through the browser UI.
</div>
</section>
<section id="manual-uploads">
<h2>Manual upload defaults</h2>
<p>
The Files panel can upload files by dropping them onto the panel or by using the toolbar
<strong>Upload</strong> button. <code>uploads.defaultFolder</code> sets the workspace-effective default
destination. The built-in default is <code>.pi-web/uploads</code>; a project-local value overrides the
global value for workspaces in that project.
</p>
<p>
The value must be a non-empty workspace-relative folder. PI WEB normalizes repeated separators and
backslashes to <code>/</code>, and rejects absolute paths or <code>..</code> traversal. In the upload
dialog only, clearing the destination field uploads that batch to the workspace root.
</p>
<p>
Manual uploads use the workspace file-write path: paths stay workspace-relative, parent folder creation is
enabled by default, and overwrite is disabled by default. Browser-owned XHR progress is shown per
batch/file, and conflicts or errors stay visible in the upload progress UI.
</p>
<p>
For machine federation, Settings saves the global upload default on the selected machine. Current remote
PI WEB servers also return workspace-effective upload defaults on workspace responses; older remote
servers may omit them and the browser falls back to the global/default upload folder.
</p>
</section>
<section id="agent-runtime">
<h2>Pi-compatible agent profile and companion CLI</h2>
<p>
<code>agent.command</code> selects the Pi-compatible companion CLI used by <code>pi-web doctor</code> and,
when it can be generated safely, package-managed update commands. It defaults to <code>pi</code>. This
setting does <strong>not</strong> replace the embedded runtime: every session continues to use PI WEB's
bundled Pi SDK.
</p>
<p>
<code>agent.dir</code> selects the Pi-compatible state profile used for auth providers, models, settings,
sessions, Pi packages, and Pi-package-backed PI WEB plugin discovery. It defaults to
<code>~/.pi/agent</code> only for a canonical Pi companion command. The directory must use the data layout
supported by the bundled Pi SDK; PI WEB does not load or convert incompatible fork formats, migrate
profile data, or repartition PI WEB-managed archives when the profile changes.
</p>
<div class="code-card">
<pre><code>{
"agent": {
"command": "pi-lab",
"dir": "/opt/pi-profiles/lab"
}
}</code></pre>
</div>
<p>
An alternate command always requires an explicit state directory. The command must be a safe bare
executable name such as <code>pi-lab</code> or a host-absolute executable path such as
<code>/opt/pi/bin/pi</code>; relative paths, shell expressions, and launcher strings are rejected. The
state directory must be host-absolute or start with <code>~</code>. In a federated save, the gateway
transports Unix and Windows absolute paths without reinterpreting them, and the target machine validates
and returns the persisted profile.
</p>
<p>
Environment variables take precedence over the config file. <code>PI_WEB_AGENT_COMMAND</code> selects the
companion CLI, <code>PI_WEB_AGENT_DIR</code> sets the profile state directory, and
<code>PI_WEB_AGENT_SESSION_DIR</code> overrides session storage separately from <code>agent.dir</code>. The
legacy <code>PI_CODING_AGENT_DIR</code> and <code>PI_CODING_AGENT_SESSION_DIR</code> names apply only to a
canonical Pi companion command; PI WEB never derives ambient environment-variable names from an arbitrary
command. Use the explicit <code>PI_WEB_AGENT_*</code> names for alternate commands.
<code>PI_WEB_AGENT_DIR</code> is an unconditional override, while a legacy
<code>PI_CODING_AGENT_DIR</code> override stops applying when Settings selects an alternate command so the
command and directory can transition together.
</p>
<p>
The session daemon resolves the persisted desired values plus its environment once at startup. That
secret-free active profile stays fixed for the daemon lifetime. <strong>Settings → Session daemon</strong>
saves command and directory together as desired configuration and shows whether the profile is active,
needs a restart, or cannot be compared. Until the daemon restarts, sessions, Pi package operations,
Pi-package-backed PI WEB plugin discovery, status/install detection, and update planning continue to use
the daemon-owned active profile; a web/API restart recovers that same active profile instead of applying
the newly saved values.
</p>
<div class="callout warning">
If the session daemon cannot report a valid active profile, profile-dependent Pi package and PI WEB plugin
operations report unavailable instead of falling back to independently resolved config. A package-managed
update command is shown only when PI WEB can preserve the active profile with a recognized, safe Pi
companion CLI; otherwise the command is omitted. Remote profile editing likewise requires advertised
support, and the gateway rejects a remote save if the target does not return the requested profile.
Restart the session daemon on the selected machine to establish the next active profile.
</div>
</section>
<section id="pi-extension-provider-baseline">
<h2>Pi extension provider baseline</h2>
<p>
This policy applies to <strong>Pi runtime extensions</strong>, not PI WEB browser plugins. Pi extensions
are runtime modules loaded by the session daemon and can call <code>pi.registerProvider(...)</code>;
PI WEB plugins are browser-side UI modules and never run in the session daemon.
</p>
<p>
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
(<code>agent.dir</code>), 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
(<code>pi.registerProvider("id", config)</code>) and native-provider registrations
(<code>pi.registerProvider(provider)</code>), alongside Pi built-ins, environment credentials, and
providers from the active agent directory's <code>models.json</code>.
</p>
<p>
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 <code>session_start</code>, and
<code>/reload</code>. Non-provider Pi extension features continue to load and reload 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>
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.
</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>
Configure providers before the daemon starts: use the active agent directory's
<code>models.json</code>, or install the Pi extension globally in that agent profile. Project Pi extensions
and project-level <code>models.json</code> files cannot add providers to PI WEB's shared baseline.
</p>
<div class="callout warning">
<strong>Restart required:</strong> after updating PI WEB—or after installing, removing, or updating a
global Pi extension that registers providers—manually restart <code>pi-web-sessiond.service</code>
(<code>systemctl --user restart pi-web-sessiond</code>). Restarting only the web/API service and running
<code>/reload</code> do not rebuild the baseline.
</div>
</section>
<section id="catalog-refresh">
<h2>Background model catalog refresh</h2>
<p>
PI WEB shares one model runtime across all sessions, and provider model catalogs are refreshed over the
network only on the session daemon's own background schedule. Requests never start a catalog fetch of
their own, so a slow or unreachable provider cannot stall opening the model selector, starting a session,
or the auth dialogs on its own account.
</p>
<p>
A refresh that is <em>already</em> in flight can still briefly delay starting or opening a session,
because the shared runtime is read while that refresh is running. PI WEB says so while you wait: the
session's activity line names the startup step it is on and adds
<code>provider model lists are refreshing</code> when a background refresh is running at the same time.
That note reports what is happening concurrently, not a proven cause.
</p>
<p>The session daemon runs the refresh:</p>
<ul>
<li>
<strong>15 seconds after the daemon starts</strong>, then <strong>hourly</strong>. Pi treats stored
catalogs as fresh for four hours, so most hourly ticks make no network request at all; the shorter tick
only makes sure a due refresh is not delayed to the next tick.
</li>
<li>
<strong>Immediately after a provider login or logout</strong>, bypassing that freshness window, because
the cached catalog is known to be wrong.
</li>
</ul>
<p>
Each run is bounded: it is aborted after <strong>60 seconds</strong>, and a run that times out or cannot
reach a provider earns <strong>one retry after five minutes</strong>; a provider that answers with an
error status is retried on the next scheduled refresh instead. Failures never clear the stored catalogs
— the last successfully fetched models stay in use and the daemon log records what failed. A refresh in
flight is also aborted when the daemon shuts down.
</p>
<p>
Models fetched by a background refresh appear the next time a client asks for the model list, so a model
selector left open across a refresh may need to be reopened.
</p>
<p>
To turn the background refresh off entirely, set <code>PI_WEB_OFFLINE</code> or <code>PI_OFFLINE</code> in
the session daemon's environment and restart it. In offline mode PI WEB performs no provider catalog
network requests, including after logins, and sessions use the catalogs already stored in the agent
profile. The <code>PI_WEB_SKIP_VERSION_CHECK</code> and <code>PI_SKIP_VERSION_CHECK</code> keys do
<strong>not</strong> affect this refresh; they only suppress PI WEB release checks.
</p>
</section>
<section id="session-tools">
<h2>Session daemon tools</h2>
<h3><code>spawnSessions</code></h3>
<p>
Boolean. Controls whether agents receive the <code>spawn_session</code> tool. Defaults to
<code>true</code>. Set it to <code>false</code> if you do not want an agent to start independent PI WEB sessions.
</p>
<p>Environment override: <code>PI_WEB_SPAWN_SESSIONS=0|1|true|false</code>.</p>
<h3><code>subsessions</code></h3>
<p>
Boolean. Beta. Controls whether agents receive the tracked-subsession tools:
<code>spawn_subsession</code>, <code>list_subsessions</code>, <code>check_subsession</code>,
<code>read_subsession</code>, and <code>yield_to_subsessions</code>. Defaults to <code>false</code> and also
requires <code>spawnSessions</code> to be enabled.
</p>
<p>
Tracked subsessions are join-oriented. Calling <code>spawn_subsession</code> returns immediately, so the
parent can continue independent work while the child runs. Work whose result the parent does not need to
join belongs in the fire-and-forget <code>spawn_session</code> tool instead.
</p>
<p>
At a join point, after finishing its independent work, the parent calls
<code>yield_to_subsessions</code> alone as the final action in its tool batch. Pi ends a tool batch early
only when every result in that batch is terminating. If any tracked child is still working, the action
ends the current agent run so the parent becomes idle. If none are working, it does not end the run and
clearly reports that there is nothing to wait for.
</p>
<p>
A completion notice wakes an idle parent or queues behind in-flight work. Each notice lists any other
tracked children still working, so the parent can continue work or call
<code>yield_to_subsessions</code> again at the next join point. Further notices arrive automatically; do
not poll. The notice includes the child's final output when it fits. If that output is too long, PI WEB
omits it entirely instead of adding a truncated duplicate to the parent's context and directs the parent
to retrieve it with <code>check_subsession</code>.
</p>
<p>
<code>list_subsessions</code>, <code>check_subsession</code>, and <code>read_subsession</code> never yield
or change control flow. They are for deliberate inspection or recovery, not completion polling. While a
child works, agent-facing <code>check_subsession</code> and <code>read_subsession</code> withhold partial
output and direct the parent to continue independent work or yield at the join point. Output becomes
available when the child stops. Included output and transcripts follow a labeled marker and come last,
after PI WEB guidance.
</p>
<p>
Both <code>spawn_session</code> and <code>spawn_subsession</code> accept an optional
<code>model</code> parameter, given as an exact <code>provider/model-id</code> such as
<code>anthropic/claude-sonnet-4-5</code>. When set, the new session starts on that model instead of
inheriting the dispatching session's model. The match is strict: an unknown or malformed value is
rejected with an error. A <code>#provider/model-id</code> reference in the prompt (see
<a href="#prompt-completions">Prompt completions</a>) is how users ask for a specific model; agents
forward that reference as this parameter. The new session also inherits the dispatching session's
thinking level, clamped to its model's capabilities.
</p>
<p>
In <strong>Settings → Session daemon</strong>, these keys are saved on the selected machine. Restart the
session daemon on that machine after changing them.
</p>
<p>Environment override: <code>PI_WEB_SUBSESSIONS=0|1|true|false</code>.</p>
<h3><code>askUser</code> and <code>ask_user</code></h3>
<p>
<code>askUser</code> controls whether agents receive the core <code>ask_user</code> tool. It defaults to
<code>true</code>; set it to <code>false</code>, or set <code>PI_WEB_ASK_USER=false</code>, to remove the
tool. The environment override accepts <code>0|1|true|false</code> and takes precedence over the config file.
</p>
<p>
Use <strong>Settings → Session daemon → Allow agents to ask questions</strong> to change
<code>askUser</code> on the selected machine. An environment override makes the toggle read-only.
</p>
<p>
The tool accepts one set of 120 questions. Each question has a unique <code>id</code>, its
<code>question</code> text, optional supporting <code>detail</code>, up to 12 options with stable values and
user-facing labels, and an optional <code>multiple</code> flag. The browser always adds a
<strong>Custom</strong> free-text answer, including when the model supplies no options. No question is
required: the user may leave any of them unanswered.
</p>
<p>
Calling <code>ask_user</code> posts the whole set as one browser form and ends the current agent run
instead of waiting for the user. The open form is owned by the session daemon, so it survives a browser
disconnect, browser reload, or web/API restart while that daemon keeps running. When the user submits,
the answers arrive as a follow-up that wakes the session; each question is reported with its selected
option values or free text, or explicitly as unanswered.
</p>
<p>
PI WEB confirms a partial submission before sending it and names the unanswered questions. Only one ask
can be open per session: a later <code>ask_user</code> call supersedes the earlier one, reports that fact
and its unanswered questions to the model, and turns the earlier card into a read-only transcript record.
Submitted and cancelled asks likewise remain readable in the transcript.
</p>
<p>
Sending an ordinary chat message while a form is open voids the form: the card closes as cancelled and
the model is told its questions went unanswered as part of the turn the message itself starts.
</p>
<div class="callout warning">
<strong>Restart required:</strong> restart the session daemon after changing <code>askUser</code> or after
upgrading PI WEB to a version that introduces this tool. For the systemd user service, run
<code>systemctl --user restart pi-web-sessiond</code>.
</div>
</section>
<section id="extension-dialogs">
<h2>Extension dialogs</h2>
<p>
Pi extensions can ask the user questions from <code>ctx.ui.confirm()</code>,
<code>ctx.ui.select()</code>, and <code>ctx.ui.input()</code> — including from
<code>session_start</code> hooks and in-flight <code>tool_call</code> hooks. PI WEB renders these dialogs
inline in the session transcript and answers them through a dedicated session-daemon channel, never the
prompt queue, so a dialog parked inside a <code>tool_call</code> hook cannot deadlock the run. Dialog
support is always on; there is no enable flag. See
<a href="plugins#pi-extension-dialogs">Pi extension dialogs in PI WEB</a> for behavior details and author
guidance.
</p>
<p>
<code>extensionDialogsTimeoutMs</code> is the unattended-dialog safety valve: how long the session daemon
waits for an answer before settling the dialog with its kind's cancel value (<code>false</code> for
confirm, <code>undefined</code> for select and input). It defaults to <code>300000</code> (5 minutes);
set it to <code>0</code> to wait forever. An extension's own <code>timeout</code> option still applies,
and the effective deadline is the sooner of the two.
</p>
<div class="callout warning">
<strong>Restart required:</strong> <code>extensionDialogsTimeoutMs</code> is edited directly in the global
config file. Restart the session daemon after changing it — for the systemd user service, run
<code>systemctl --user restart pi-web-sessiond</code>.
</div>
</section>
<section id="prompt-completions">
<h2>Prompt completions</h2>
<p>The chat composer opens completion menus on three trigger characters:</p>
<ul>
<li><code>/</code> at the very start of the draft completes session commands.</li>
<li>
<code>@</code> completes file paths: <code>@</code> for tracked files, <code>@ </code> (at, then
space) or <code>!@</code> for all files. Picking one inserts an <code>@path</code> reference into the
draft, quoted automatically when the path contains spaces.
</li>
<li>
<code>#</code> completes the models available to the session, filtered case-insensitively as you type
(at most 12 entries). Picking one inserts a <code>#provider/model-id</code> reference into the draft,
which tells agents the request should run on that model — for example as the <code>model</code>
parameter of <code>spawn_session</code>.
</li>
</ul>
</section>
<section id="completion-tools">
<h2>Optional completion tools</h2>
<p>
File and path <code>@</code> completions work without extra tools. If <code>fzf</code> is available on the
PI WEB server's <code>PATH</code>, PI WEB uses it to improve completion filtering and ranking; otherwise it
falls back to built-in ranking.
</p>
<div class="doc-actions">
<a class="button primary" href="install">Install guide</a>
<a class="button" href="config.md">Markdown reference</a>
</div>
</section>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<span>PI WEB docs</span>
<div class="footer-links">
<a href="./">Home</a>
<a href="remote-first">Remote-first</a>
<a href="machines">Fleet</a>
<a href="install">Install</a>
<a href="config">Config</a>
<a href="plugins">Plugins</a>
<a href="faq">FAQ</a>
<a href="https://github.com/jmfederico/pi-web">GitHub</a>
</div>
</div>
</footer>
<script src="site.js"></script>
</body>
</html>