Skip to content

Configuration reference

Most configuration happens at runtime in the app (see the Configuration guide). This page documents the process-level settings that live in .env — every one has a built-in default, so the file is optional.

The split is deliberate and enforced by a test: anything you can change in Settings is stored in the database and has no environment twin, so there is exactly one place to set it — bar two deliberate exceptions, noted at the foot of this page. .env keeps only what has to be known before the database exists — bind address, database URL, data directory, ticker cadences.

Copy .env.example to .env and uncomment only what you want to override.

Server

VariableDefaultDescription
PRECURSOR_HOST127.0.0.1Bind address. Keep it on loopback unless you front the app with your own auth.
PRECURSOR_PORT8000The URL you open. In --dev, the Vite UI runs here and the backend moves to PORT + 1. A busy port auto-bumps to the next free one — and precursor service install writes the port it settled on back to the instance .env so it stays put.
PRECURSOR_LOG_LEVELinfouvicorn/app log level.
PRECURSOR_LOG_FILE_MAX_BYTES5242880Size at which logs/precursor.log rotates. The app writes its own log file, so this is what caps it — nothing else prunes it.
PRECURSOR_LOG_FILE_BACKUPS3How many rotated generations (precursor.log.1, …) to keep.
PRECURSOR_SHUTDOWN_GRACE_SECONDS3Seconds to wait for in-flight requests (e.g. SSE streams) before force-closing on Ctrl-C, so the port is released promptly.
PRECURSOR_CORS_ORIGINS(empty)Comma-separated list of extra allowed origins. Empty means same-origin only, which is what a local-first single-user app wants.

CLI flags mirror several of these: --port, --api-port, --host, --strict-port (fail instead of bumping a busy port), --port 0 (any free port), --open (open the browser when ready), --dev, and --no-frontend.

Database & data directory

VariableDefaultDescription
PRECURSOR_DATABASE_URLsqlite+aiosqlite:///./precursor.dbAsync SQLAlchemy URL. Point at Postgres for production.
PRECURSOR_DATA_DIR.precursor (checkout) / user data dir (installed)On-disk working directory. Holds workspace clones (workspaces/), content-addressed attachment blobs (blobs/), the self-hosted draw.io editor (drawio/), the agents runtime's Copilot home (agents/copilot-home/), and the background app's runtime.json + logs/. Relative paths resolve against the process working directory.

The two defaults above depend on how Precursor was installed. A source checkout keeps its state beside the code, so every clone and worktree is an isolated sandbox. An installed wheel has no such home — and is typically started by a login item whose working directory is / — so it resolves to a per-user directory instead:

PlatformUser data dir
macOS~/Library/Application Support/Precursor
Linux$XDG_DATA_HOME/precursor, else ~/.local/share/precursor
Windows%APPDATA%\Precursor

Setting either variable explicitly overrides both behaviours.

bash
# SQLite (default — no setup)
PRECURSOR_DATABASE_URL=sqlite+aiosqlite:///./precursor.db

# PostgreSQL (needs the `postgres` extra for asyncpg)
PRECURSOR_DATABASE_URL=postgresql+asyncpg://user:pass@localhost:5432/precursor

Background app & updates

Used by the background app — the supervisor, the menu-bar tray, and the in-place updater.

VariableDefaultDescription
PRECURSOR_UPDATE_REPOlrivallain/precursorRepository the update check reads releases from. Point it at a fork to track your own builds.
PRECURSOR_UPDATE_CHANNEL(empty)stable (tagged releases) or nightly (rolling build of main). Empty means "match the running build": a dev version follows nightly, a tagged one follows stable.
PRECURSOR_UPDATE_EXTRASkanbanComma-separated extras carried across a self-update, so precursor service update doesn't silently drop the plugins you installed with. Add tray if you use the menu-bar icon. This is unioned with uv's install receipt; prefix a name with - (-kanban) to drop one instead.
PRECURSOR_UPDATE_CHECK_TTL_SECONDS900How long an update-check result is cached before GitHub is asked again.
PRECURSOR_UPDATE_NOTIFYpromptWhat the tray does when a background check finds a new build. prompt raises a notification with an Update and restart button (macOS osascript, Linux notify-send --action); notify is a plain toast; off stays quiet and leaves it to the menu's status line. Anything else is read as prompt.

Diagram editor

.drawio files open in a self-hosted draw.io editor. The webapp is downloaded on demand into <data_dir>/drawio/<version>/ the first time you open a diagram — it is deliberately not bundled in the wheel (~53 MB download, ~150 MB on disk). See Workspaces → Editing diagrams.

VariableDefaultDescription
PRECURSOR_DRAWIO_VERSIONv31.3.1draw.io release tag to install. Changing it installs the new release on next use and removes the old one.
PRECURSOR_DRAWIO_DOWNLOAD_URLGitHub release draw.warTemplate for the archive URL; {version} is substituted. Point it at an internal mirror on an air-gapped network.

LLM provider

The provider and its credentials are configured at runtimeSettings → Model — not via the environment. The GitHub providers fall back to your gh auth login session when no token is saved. See Configuration → Connecting a model.

VariableDefaultDescription
PRECURSOR_GITHUB_CLI_USER(empty)Which gh login supplies the token (gh auth token --user …). Set it when several accounts are signed in, so the resolved token doesn't depend on the CLI's active account — see GitHub authentication.

Backup

A periodic copy of the SQLite DB + attachment blobs into a plain folder (e.g. a OneDrive/Dropbox/iCloud-synced directory). Enable it, pick the target folder, and set snapshot retention at runtimeSettings → Backup. Only the scheduling knobs are env-level:

VariableDefaultDescription
PRECURSOR_BACKUP_INTERVAL_SECONDS86400Minimum time between successful backups.
PRECURSOR_BACKUP_POLL_SECONDS3600How often the ticker checks whether a backup is due.

Scheduler & reminders

The in-process ticker behind scheduled topics, agents and workflows and one-shot reminders. The cadence is process-level; each schedule's own recurrence is stored in the database.

VariableDefaultDescription
PRECURSOR_SCHEDULER_ENABLEDtrueMaster switch for every background ticker — the scheduler, reminders, and the two retention sweeps. Set false for a read-only or CI instance.
PRECURSOR_SCHEDULER_POLL_SECONDS30How often the scheduler looks for due runs.
PRECURSOR_SCHEDULER_CONCURRENCY2How many scheduled runs may execute at once.
PRECURSOR_REMINDER_POLL_SECONDS30How often the reminder ticker looks for due reminders.

Retention

Independent sweeps bound long-term database growth — see Storage & retention for the full picture and the on-demand cockpit. Each window is set at runtime in an AppSetting; every sweep runs on startup and repeats daily via a background ticker. Only the poll cadence is env-level.

SettingDefaultWhereDescription
tool_result_retention_days0 (keep forever)Settings → SystemDays before a large tool result's content is replaced in place with a short placeholder.
live_transcript_retention_days7Settings → LiveDays after a Live session ends before its transcript segments are deleted. 0 keeps them forever. Insights, notes and summary are preserved.
agent_event_retention_days30Settings → AgentsDays before an agent's archived timeline events are deleted. 0 keeps them forever. A running agent is never pruned, and every agent keeps its result, artifacts and messages.
agent_event_max_per_session2000Settings → AgentsHard ceiling on archived events per agent — only the newest are kept. 0 is unlimited.

Two levers govern agent timelines because agent traffic is bursty rather than aged: one long autonomous session can add tens of MB in a day, which a time window alone wouldn't touch for weeks.

Poll cadences (PRECURSOR_TOOL_RESULT_RETENTION_POLL_SECONDS, PRECURSOR_LIVE_TRANSCRIPT_RETENTION_POLL_SECONDS, PRECURSOR_AGENT_EVENT_RETENTION_POLL_SECONDS) default to 86400 (daily). Every sweep is gated by PRECURSOR_SCHEDULER_ENABLED.

Skills directory

The skills folder is resolved the way the Copilot CLI resolves its home: COPILOT_HOMEXDG_CONFIG_HOME/copilot~/.copilot, with a PRECURSOR_SKILLS_DIR override.

Agents

Agents mode is toggled at runtime (Settings → Agents), which is the only on/off control — there is no env-level default, because installing the agents extra is itself the opt-in. One env var affects which runtime it drives, and the agent orchestrator governance adds two process-level knobs:

VariableDefaultDescription
COPILOT_CLI_PATH(empty)Read by the Copilot SDK, and the first thing Precursor's runtime probe checks. Point it at a copilot binary to pin the runtime; otherwise Precursor falls back to the SDK's download cache and then to copilot on PATH. The probe never downloads — see agents mode.
PRECURSOR_AGENTS_MAX_CONCURRENT3Concurrency governor — the max agents the orchestrator lets execute a turn at once. Extra ready agents queue and are released as slots free up. 0 or negative disables the cap (unbounded).
PRECURSOR_AGENTS_RETRY_BACKOFF_SECONDS60Base backoff for auto-retry of a failed agent. Delay grows exponentially per attempt (base × 2ⁿ); the scheduler re-runs the agent once its retry time is due, up to the agent's max_retries.

Per-agent token budget and max retries aren't env vars — they're set in each agent's settings drawer (or baked into a blueprint).

MCP tool servers

Most MCP built-ins are toggled at runtime (Settings → MCP). A few env knobs affect the playwright and Agent 365 servers:

VariableDefaultDescription
PRECURSOR_MCP_IDLE_TTL_SECONDS600How long an idle MCP client session is kept warm before it's torn down. A later call re-attaches; lower it to hold fewer child processes open.
PRECURSOR_MCP_WARMUP_ENABLEDtrueConnect the enabled servers in the background shortly after startup, one at a time, so the first prompt doesn't pay connect + initialize + list_tools for all of them at once. Never blocks startup or a request, and never starts an interactive sign-in. Off means every server connects lazily on first use.
PRECURSOR_MCP_WARMUP_DELAY_SECONDS5Grace period before the warm-up touches the first server, leaving startup (schema migration, plugin hydration, the SPA's own first requests) the machine to itself.
PRECURSOR_MCP_WARMUP_GAP_SECONDS1Pause between servers during the sweep. Raise it to spread the cost further; the sweep is sequential either way.
PRECURSOR_GITHUB_MCP_TOOLSETScontext,repos,issues,pull_requests,usersWhich toolsets the bundled GitHub MCP server registers. Trimming it is the cheapest way to cut that server's schema cost, since tool schemas are re-sent every turn.
PRECURSOR_PLAYWRIGHT_BROWSERmsedgeBrowser channel the playwright server drives (--browser): one of msedge, chromium, chrome, firefox, webkit, or default. Defaults to Microsoft Edge so it can ride the corporate SSO/WAM broker for authenticated Entra scraping. Set chromium on machines without Edge. default omits --browser entirely — the escape hatch for an @playwright/mcp build that predates the flag (fails with unknown option '--browser'). Overridable at runtime in Settings → MCP → Playwright browser (the DB value wins).
PRECURSOR_WORKIQ_TENANT_ID(empty)Microsoft tenant GUID the Agent 365 servers (workiq-teams, workiq-user, workiq-planner, workiq-word, workiq-excel) address. Overridden by Settings → MCP → Microsoft 365 tenant; when both are empty Precursor reads the tenant off an existing WorkIQ sign-in. Entra rejects common / organizations here — it must be a GUID.
PRECURSOR_PLAYWRIGHT_PROFILE_DIR(empty)Browser profile the playwright server uses (--user-data-dir). Empty means reuse @playwright/mcp's own shared, machine-wide profile — so any Entra/SSO sign-in already onboarded there (incl. via other Playwright-MCP tools) carries over. Set a path to pin an isolated profile for Precursor instead.

WorkIQ / Agent 365 sign-in

These govern how the Entra credentials behind the WorkIQ and Agent 365 servers are kept alive. Defaults are tuned for an unattended install — a scheduled workflow has nobody at a browser.

VariableDefaultDescription
PRECURSOR_WORKIQ_KEEPALIVE_ENABLEDtrueRun the background ticker that silently refreshes WorkIQ tokens before they expire. Off means every lapse needs an interactive sign-in.
PRECURSOR_WORKIQ_KEEPALIVE_POLL_SECONDS60How often that ticker checks whether a token is near expiry. How far ahead of expiry it renews is not configurable: it is a quarter of the token's own lifetime (at least five minutes), which is what decides how many attempts fit before the token dies. GET /api/mcp/auth/diagnostics reports the resulting renewal_lead_seconds per credential.
PRECURSOR_WORKIQ_SILENT_REAUTH_ENABLEDtrueTry the hands-free (no-browser) re-authentication path before falling back to an interactive prompt.
PRECURSOR_WORKIQ_AUTO_REAUTH_ENABLEDtrueAutomatically start a re-authentication when a credential is found to have lapsed, rather than waiting for you to click the banner.
PRECURSOR_WORKIQ_CHAIN_REAUTH_ENABLEDtrueAfter a WorkIQ / Agent 365 sign-in succeeds, immediately retry the other Entra credential on the hands-free silent path while the SSO session is hot, so you get one prompt instead of one per credential. Set false to renew each credential only when it's independently needed.
PRECURSOR_WORKIQ_LOOPBACK_PORT_FALLBACKtrueWhen an OAuth sign-in's preferred loopback callback port (12798 / 12799 / 12800) is already in use, bind a free ephemeral port instead of failing. Entra ignores the port of a loopback redirect for public clients, so this is transparent and lets several windows sign in at once. Set false for the strict "port is busy — finish that other sign-in first" behaviour.
PRECURSOR_WORKIQ_KEEPALIVE_IDLE_AFTER_SECONDS21600 (6 h)Stop keeping a WorkIQ / Agent 365 credential warm once no server using it has been called for this long, so a server you never use doesn't nag you to sign in. Usage is tracked per credential and the clock is seeded at startup, so a restart doesn't leave everything cold. 0 disables the back-off and refreshes every signed-in credential indefinitely.
PRECURSOR_WORKIQ_KEEPALIVE_SURFACE_IDLE_LAPSEtrueLet the keep-alive surface an idle credential's lapse proactively: once an idle token has genuinely expired it probes once, and if the refresh token is dead it raises the sign-in banner instead of waiting for your next request to stall on the doomed handshake. A still-refreshable idle session recovers silently. Set false to keep idle credentials completely quiet until you touch them yourself.
PRECURSOR_WORKIQ_AUTH_LOG_LEVELdebugLevel of the dedicated precursor.mcp.auth channel that traces every decision taken about a WorkIQ credential — keep-alive verdicts, the Entra AADSTS… code that refused a silent refresh, each leg of a re-auth. Independent of PRECURSOR_LOG_LEVEL, and verbose by default because a sign-in lapse only happens in the wild and can't be reproduced on demand. The channel is quiet outside an auth episode. Use info for transitions only, or warning to silence it — GET /api/mcp/auth/diagnostics keeps the full in-memory trace either way.

Where a value comes from

Almost everything you can change in Settings is stored in the database and has no environment variable — the panel is the only place to set it.

The two exceptions are PRECURSOR_PLAYWRIGHT_BROWSER and PRECURSOR_WORKIQ_TENANT_ID: both describe the machine or tenant rather than a preference, so they're seedable from .env and overridden by the corresponding Settings → MCP field when one is saved.

Never read os.environ directly in app code — resolve settings through services/app_settings.py.

Released under the MIT License.