93 KiB
Set up a local Data Formulator development environment
How to set up your local machine.
Prerequisites
- Python >= 3.11
- Node.js
- Yarn
- uv (recommended) or pip
Backend (Python)
Analyst Execution Defaults
AnalystExecutionConfig and ANALYST_EXECUTION_DEFAULTS in
agent_config.py own analyst loop limits.
Interactive chat and API callers share these defaults:
| Setting | Default | Scope |
|---|---|---|
max_actions |
10 | Action budget, accounting for completed steps on resume |
max_tool_rounds_per_action |
12 | Model rounds before each committing action |
empty_response_retries |
2 | Empty responses within each tool-loop invocation |
empty_response_backoff_seconds |
3 | Linear retry delay: 3, then 6 seconds |
stream_open_retries |
2 | Transient stream-opening errors, before consuming tokens |
stream_open_backoff_seconds |
1 | Exponential retry delay: 1, then 2 seconds |
outer_iteration_multiplier |
3 | Outer-loop safety bound relative to action budget |
min_outer_iterations |
12 | Minimum outer-loop safety bound per invocation |
The HTTP field max_iterations remains supported and overrides max_actions.
Interactive chat omits this field and uses the shared default of 10 actions.
Budgets must be positive integers. Retry counts and finite backoff durations
may be zero, but not negative. Invalid request budgets fail before execution.
No new upper cap, environment overrides, or user-facing controls are introduced.
Python callers and evaluations can pass execution_config=AnalystExecutionConfig(...)
to AnalystAgent; an explicit legacy max_iterations argument takes precedence
over its action budget. The configuration is immutable and resolved per instance.
Effective settings are recorded in the reasoning log's session_start event.
The legacy max_repair_attempts argument is accepted but ignored.
Empty-response retries still consume tool rounds; if no round remains, the agent reports the provider error without waiting. Stream-opening retries happen within one model round. Neither budget is a total token, cost, or wall-clock limit. Reasoning-effort settings remain independently configured in the same module.
Connector Timeouts
Connection validation and catalog discovery are separate requests. The connection
form creates a definition with connect_params: {} before validating it, avoiding
duplicate connection tests. Transport timeouts and retryable errors retain the
connector and trigger a status check; confirmed authentication failures retain
the existing cleanup behavior.
The catalog UI uses get-catalog-tree with background: true. Discovery runs on
the catalog-refresh executor; subsequent requests use poll: true. Progress and
completion state are stored in the user's catalog_discovery directory. A file
lock prevents duplicate discovery across workers sharing that directory. Empty
catalogs are cached too. Failed scans preserve the previous cache; retry: true
starts another attempt. Legacy callers without background remain synchronous.
UI polling has a 10-second per-request limit, up to three retries with backoff for transient transport errors, and a five-minute waiting window. Ending that window does not cancel the server scan; retrying checks the existing job. Worker restarts interrupt jobs and require retry. This is not a distributed durable job queue: multiple instances need a shared data directory for status and locking.
Azure Blob SDK requests use a five-second connection timeout, ten-second read timeout, and two retries with backoff. These are per-request limits, not an overall deadline for identity acquisition, pagination, or PyArrow file reads.
Kusto ambient authentication keeps one access token in memory per credential instance, reusing it only for matching scopes and token options while more than five minutes of validity remain. Refresh is serialized across concurrent calls; changed scopes, tenant, claims, or CAE options require another acquisition. Unknown options bypass and clear the cache. This avoids repeated Azure CLI token lookups during connection checks and catalog requests, but does not eliminate initial authentication. Tokens are not shared across loader instances or written to disk. Delegated and service-principal authentication paths are unchanged.
Agent Query Workers
Agent streaming runs lazily start a dedicated subprocess for remote file queries and reuse it until that run ends. Each query creates a fresh loader and connection; workers are not shared across runs or users. Other connector methods retain direct execution. Calls without an agent cancellation context are also unchanged.
DF_QUERY_MAX_WORKERS bounds live query workers per backend server process
(default 2). Additional runs wait for capacity, checking cancellation while
queued. A worker keeps its slot between queries until the run finishes. With
multiple server processes or replicas, multiply this limit by their count when
budgeting memory; this is not an instance-wide or distributed quota. DuckDB's
query memory limit is not a cap on the entire worker process.
DF_QUERY_MEMORY_LIMIT_MB sets the DuckDB memory limit of one file-scan query
(Azure Blob, S3, local folders). By default each query gets half of physical
memory divided by DF_QUERY_MAX_WORKERS, kept between 512 MB and 4 GB. Sorts,
aggregates, and joins spill to a temp directory instead of failing, but the
loaded result must fit. The agent is told the budget in the source's query
capabilities, so it narrows columns, rows, or aggregates to stay within it.
DF_QUERY_QUEUE_TIMEOUT_SECONDS limits waiting for capacity (default 300).
DF_QUERY_TIMEOUT_SECONDS limits worker startup and query response time after
capacity is acquired (default 300). Cancellation, disconnect, or a query timeout
terminates the worker; normal run completion also releases it. Ordinary query
errors leave it reusable. A worker crash fails the current query without taking
down the backend, and a subsequent query starts a replacement without replaying
the failed query. Worker processes are daemons and are terminated during normal
backend shutdown; runs are not durable across server restarts.
Sessions in Multiple Tabs
Each browser tab works on its own session, named in the URL (/app?session=<id>),
so sessions can be compared side by side, reloaded, bookmarked, or opened from the
Sessions list with Open in new tab (or Cmd/Ctrl/middle-click). The backend
scopes every request by the signed-in or local identity plus the tab's
X-Workspace-Id, so tabs share connectors, models, workflows, and schedules while
agent runs, workflow runs, and scratch state stay per session.
On start, a tab opens the URL's session from the backend rather than trusting
browser-restored state, which other tabs share. One tab edits a session at a time
(src/app/sessionTabs.ts): opening a session claims it over a BroadcastChannel;
a tab already editing it saves and becomes view-only with an Edit here action
before the claimer loads the latest state. This coordinates tabs in one browser;
concurrent edits to one session from different browsers or devices remain
last-writer-wins until session saves are versioned.
Unified Workspace Loading
Agent propose_data_operation uses the manual-import row/byte thresholds for
query-free source additions. Known large sources return result_references
using the existing external-reference identity and metadata format, without
fetching rows or creating Parquet. Small or unknown-size sources follow ordinary
loading. Reference results are persisted with the operation and workflow checkpoint;
the frontend upserts them into workspace state by source/table identity.
Supplying query, including {}, explicitly requests materialization. This
intent is persisted in the plan hash, and concrete queries never fall back to
virtual registration. Query execution also adds a virtual source reference when
the source/table is absent from the current reference inventory and local-table
provenance, regardless of source size. No separate preparation call is needed.
The source retains its catalog name rather than the query-result label. If the
query fails, registration and failure are returned separately; registration is
not evidence of query success. Native queries retain source association without
claiming verified row-level lineage. Native/aggregate limits and coverage rules remain unchanged.
Agent observations distinguish virtual (compute_ready: false, no local path)
from materialized (compute_ready: true, path and scope) outcomes. Workflow
registration is input preparation, not a computed deliverable.
Structured Aggregate Loading
Loads with group_by or aggregates call the connector's query_data_as_arrow,
and query_capabilities.aggregate_loading is supported only when a connector
implements it. Kusto, S3, Azure Blob, and local folders support it, as do the SQL
connectors PostgreSQL, MySQL, SQL Server, BigQuery, ClickHouse, and Athena. The
SQL connectors reuse the probe compiler (probe_utils.query_via_native_sql) and
run one generated SELECT on the source. It emits only quoted identifiers, escaped
literals, and the fixed aggregate vocabulary; agent-written SQL is never executed.
Each connector resolves the table as its ordinary load does (PostgreSQL database
routing, the SQL Server dbo default, BigQuery whole-path quoting, ClickHouse's
configured-database restriction). Unlike probes, durable loads fail on any filter
or ordering they cannot compile rather than dropping it, so a result never widens
past its requested scope. The 10,000-row aggregate limit and overflow rejection
apply unchanged. MongoDB, Databricks, Cosmos DB, and Superset remain unsupported.
Native KQL Loading
Kusto advertises query_capabilities.native_query_languages: ["kql"].
propose_data_operation accepts query.native with language: "kql" and
text, mutually exclusive with structured query fields except limit.
Prefer bounded ordinary loads followed by local Python; native queries are for
source-side reductions that cannot be expressed by structured loading or whose
raw inputs cannot reasonably be loaded. Other connectors reject native queries.
The Kusto adapter uses the query endpoint, never command dispatch, and prepends
an exact-table restrict access statement. Server request properties enforce
read-only/hardline execution and disable callouts, external data/tables, remote
entities, impersonation, and sandboxed execution. Agent text cannot contain
commands, semicolons, comments, or request-setting statements. These conservative
text restrictions are not the security boundary: Kusto permissions and request
properties are. Use least-privilege, read-only connector credentials in deployment.
Do not fall back to unrestricted execution if a server rejects these properties.
Requests have a 60-second server deadline, 16-MiB response cap, and at most
10,000 loaded rows. Partial failures fail the load. Without an explicit result
limit, a 10,001-row sentinel rejects overflow rather than publishing partial data.
Limits and sampling within native text still define partial coverage; native
results are labeled query_defined, not complete-population aggregates. Small
results do not bound scan cost. Cancellation is checked around the SDK call;
an in-flight server query may continue until its deadline.
Preview, publication, and refresh use the same guarded adapter. Native text is persisted in import metadata; never put credentials or secrets in query text. Selected source identity is retained, but lineage is marked unverified and does not inherit a verified single-source shelf group. No native query is executed through a local Python fallback.
Starter Questions for External References
/api/agent/derive-starter-questions accepts input_tables and optional
external_references. primary_table identifies either a loaded table name or
an external reference ID. Focusing a reference uses the existing starter-question
chips, even when no tables have been loaded. Cached questions are invalidated when
the reference metadata or preview changes.
The starter agent uses cached schema, descriptions, row counts, inspection limits, query intent, and at most five bounded sample rows through the shared reference normalizer. It does not query connectors or materialize data during automatic suggestion generation. The prompt distinguishes preview evidence from verified source coverage and avoids assuming recent dates or complete category coverage. Selecting a question sends the reference and its focus to the normal analyst flow, where connector inspection and scoped queries can run as needed.
Option 1: With uv (recommended)
uv is faster and provides reproducible builds via lockfile.
uv sync # Creates .venv and installs all dependencies
uv run data_formulator # Run app (opens browser automatically)
uv run data_formulator --dev # Run backend only (for frontend development)
Which command to use:
- End users / testing the full app:
uv run data_formulator- starts server and opens browser to http://localhost:5567 - Frontend development:
uv run data_formulator --dev- starts backend server only, then runyarn startseparately for the Vite dev server on http://localhost:5173
Option 2: With pip (fallback)
-
Create a Virtual Environment
python -m venv venv source venv/bin/activate # Unix # or .\venv\Scripts\activate # Windows -
Install Dependencies
pip install -r requirements.txt -
Configure environment variables (optional)
- copy
.env.templateto.envand fill in your values:- API keys: set
{PROVIDER}_API_KEY=...(and/or{PROVIDER}_API_BASE=...) and{PROVIDER}_MODELS=...for each LLM provider you want to use; a provider with models and a key or base URL is enabled automatically. See the LiteLLM setup guide for provider-specific fields. - Server settings:
DISABLE_DISPLAY_KEYS,SANDBOX, etc. - Azure Blob workspace (optional): see Azure Blob Storage Workspace below.
- API keys: set
- this lets Data Formulator automatically load API keys at startup so you don't need to enter them in the UI.
- copy
-
Run the app
# Unix ./local_server.sh # Windows .\local_server.bat # Or directly data_formulator # Opens browser automatically data_formulator --dev # Backend only (for frontend development)
Local Terminal Skill
The analyst and workflow agents can use the terminal skill as a data-acquisition
route alongside connected sources. They first ground the question in relevant
workspace inputs, then choose an available route for missing data. Local files,
installed clients, public endpoints, and existing CLI logins can supply bounded
datasets directly into scratch without setting up a connector. For example:
"Analyze my Azure usage with my existing CLI account." The agent discovers the
relevant source and scope, retrieves actual data, and registers one bounded reusable
workspace input with create_data and acquisition metadata before continuing
with Python analysis and chart/report tools. Parsing and normalization can happen
in the registration call; its returned input ID/path needs no extra inventory call.
These are agent-managed source tables, not user uploads or derived chart results.
Source, scope, optional query/limitations, server-recorded acquisition time, and
input file hashes persist with the table. Explicit refreshes use update_data
with its current hash, new file inputs, and new acquisition metadata. Discovery alone is not
acquisition, and acquisition alone does not complete an analysis request.
Connector forms remain appropriate when direct acquisition is unsuitable or the
user wants reusable connected access. Running a CLI does not register a connector
or publish a workspace table: acquired data stays in scratch and
workspace tools perform registration. Discovery-only metadata, response fragments,
and calculated intermediates stay in scratch; disposable caches use private runtime
storage outside workspace exports. Relevant non-tabular inputs
use create_file. Requests for uploads, authentication,
or clarification should address concrete blockers, not replace an available
authorized acquisition step.
Terminal access supports single-user local mode on macOS, Linux and Windows. The terminal is one of the agent's tools, alongside the user's MCP servers: all tools share one approval mode, and each has its own on/off switch. Both the analyst and workflow agents use the same server-owned policy:
- Off (
off): no terminal tool, skill catalog entry, or application-supplied terminal guidance. Forged or stale execution requests are rejected. Sandboxed Python analysis remains available. - Ask every time (
ask, the default): terminal tools and operating guidance are preloaded from the first turn. Each exact invocation opens a Run once / Reject dialog showing arguments, working directory, purpose, and host-access warning. Approval covers the entire invocation, including a shell script, not each individual line. - Auto when safe (
auto): the same preloaded tools and guidance, with immediate sandboxed execution, including writes allowed by the filesystem policy.dangerouslyDisableSandboxalways requires user approval and a reason.
Click Tools beside the chat input. The Tools dialog applies each change
immediately: the shared mode (Off / Ask every time / Auto when safe), the
Terminal switch (stored as overrides.terminal_enabled; off turns the terminal
off without changing the shared mode), and, in the Terminal row's details, the
sandbox write paths. This local-user setting is available without managed mode or
Administration access. Its dedicated endpoint (/api/configurations/terminal)
accepts only mode, enabled, sandbox and the configuration revision, requires
a same-origin local request, and preserves all other settings.
Alternatively, launch with DF_TERMINAL_MODE=off|ask|auto, which sets the shared
tools mode. The environment takes precedence and locks the selector; invalid environment values fail closed to Off.
Persisted configuration uses overrides.terminal_mode. Deployment restrictions
still apply. Chat text, workflow definitions, and tool arguments cannot set it.
Approvals are stored server-side for one invocation, bound to the identity, workspace, conversation, mode, and configuration revision. They expire after ten minutes or a backend restart. Configuration changes invalidate outstanding approvals, even if the mode is later restored. Execution rechecks current policy; a change while a command is running stops it at the next runner check. Resumes rebuild application capability guidance without erasing actual conversation history.
Commands normally run with OS-enforced filesystem write confinement: macOS uses
/usr/bin/sandbox-exec; Linux requires Bubblewrap (bwrap) and enabled user
namespaces. Writable areas include workspace scratch, private per-invocation
runtime storage (mode 0700), and the filesystem policy below. Children inherit
the restriction.
Without a sandbox (Linux without a working Bubblewrap, or Windows without WSL or
without Bubblewrap inside WSL), the terminal still works, but every command runs
unsandboxed with the user's normal access: proposals are marked
dangerouslyDisableSandbox with sandboxUnavailable, so each one asks for
approval even in Auto. The approval and Tools dialogs show a warning and
an install hint, and the agent is told. Installing a sandbox takes effect after a
backend restart.
On Windows, commands run in WSL (data_formulator/sandbox/wsl.py) when it has a
working default distribution: wsl.exe -e bwrap … -- <argv>, as on Linux, with
Windows paths bound as /mnt/<drive>/…, the DEFAULT_ALLOW_WRITE CLI-state paths
also bound in the WSL home when present, and WSL interop (/run/WSL,
WSL_INTEROP) hidden so a sandboxed command cannot start Windows programs. Command
variables reach WSL through WSLENV (paths translated). Unsandboxed commands run
as wsl.exe -e <argv>; without WSL they run natively and the agent is told to use
PowerShell. Process start, output streaming, interrupt and tree kill go through
data_formulator/analyst/process_io.py (process groups and a selector on POSIX; a
reader thread, CTRL_BREAK and taskkill /T on Windows). The Windows paths are
covered by simulated unit tests only.
Commands receive DF_SCRATCH_DIR for outputs and DF_RUNTIME_DIR for disposable
state. TMPDIR/TMP/TEMP, XDG_CACHE_HOME, UV_CACHE_DIR, PIP_CACHE_DIR,
npm_config_cache, YARN_CACHE_FOLDER, MPLCONFIGDIR, HF_HOME, and NUMBA_CACHE_DIR
point into runtime storage, removed on completion/cancellation and excluded from
workspace exports. These caches are disposable, not shared across commands.
The working directory does not grant write access. Missing or failing confinement
never silently falls back to unrestricted execution.
The built-in persistent sandbox.filesystem.allowWrite policy is:
| Paths | Purpose and scope |
|---|---|
~/.azure |
Existing Azure CLI state: command logs, MSAL token/HTTP caches, locks, atomic updates. Entire directory writable. |
~/.config/gcloud |
Existing gcloud state: token databases, SQLite journals, logs, configuration. Entire directory writable. |
~/.aws/cli/cache, ~/.aws/sso/cache, ~/.aws/login/cache |
AWS role, SSO, and login token caches. AWS credentials and config files remain read-only. |
~/.kube/cache, ~/.kube/http-cache |
Kubernetes discovery and HTTP caches. Kubeconfig remains read-only. |
Existing Azure/gcloud config-directory environment overrides replace their default paths only when inside the home directory and passing the same path checks. Outside-home overrides need a user-configured grant or reviewed bypass. Missing AWS/Kubernetes cache children are created under existing state roots; absent Azure, gcloud, AWS, or Kubernetes installations are not initialized. Unsafe or unavailable paths are skipped, not broadened. No automatic grants cover SSH files, keychains, Git config, package installation environments, or the entire home.
Users can replace the built-in list in the installation configuration's
overrides (an empty list disables all persistent grants):
{
"sandbox": {
"filesystem": {
"allowWrite": ["~/.azure", "~/.aws/sso/cache", "/absolute/custom-cli-state"]
}
}
}
Configured entries are literal absolute paths or ~/ paths, at most 64; no globs.
They must exist to be granted. Paths are re-resolved before every execution;
symlink redirects, nonregular/hard-linked files, root/home-wide grants, and paths
covering application configuration are rejected or skipped. Prefer directories
for lock files and atomic replacement; Linux file bind mounts cannot be renamed.
Directory grants allow modification/deletion of all contents, including
credentials and executable configuration, not just harmless refreshes. Preexisting
hard links inside a granted directory can alias other files; path grants are not
a content-level security boundary. These are explicit compatibility tradeoffs.
The local terminal access dialog shows resolved paths; settings changes invalidate
pending commands through the configuration revision. Agents cannot submit path
grants or change this policy through run_terminal.
For a necessary operation outside the policy, the agent submits the exact command
with dangerouslyDisableSandbox: true and a nonempty sandboxDisablingReason.
The dialog shows the reason, full host-write risk, and Run outside sandbox /
Reject. Both Ask and Auto require this explicit approval. The stored command,
flag, and reason are authoritative; client responses contain only request ID and
decision. Approval is single-use and never applies to subsequent commands.
An approved command bypasses filesystem confinement with normal host-user access,
not root privileges; runtime storage, filtered environment, time limits, and
local-policy checks remain. Changes made on the host remain afterward.
Retry flow: inspect the error and partial effects, redirect disposable state when
possible, then submit a reasoned bypass request if required. The runtime never
automatically retries outside the sandbox. Permission-looking stderr is a hint,
not proof of sandbox denial or invalid credentials. Rejected requests must not be
retried via another route. Legacy write_paths proposals are refused.
Each command has a fresh process, no interactive stdin, a 60-second timeout, and the last 32 KiB of combined output. The process group is terminated on timeout or when the execution generator closes; macOS process-group cleanup alone does not guarantee termination of deliberately detached descendants. Ordinary server API key environment variables are not inherited, but local files and cached CLI credentials remain accessible. Selected CLI profile/config-location variables and proxy/CA settings are inherited, but credential values such as API keys are not copied from the server environment. Login stores are never copied into scratch or runtime storage. Command arguments, bypass reasons, policy snapshots, and results appear in the conversation and are sent to the configured model, so do not print credentials or other sensitive data. Complete interactive authentication outside the agent. This is write confinement, not complete isolation: network access remains enabled, and remote mutations or effects delegated to external services are not prevented by the filesystem boundary. Auto approval does not remove these risks or authorize actions outside the user's requested task.
Scratch files are absent from the ordinary workspace listing. In Backend Log, the Scratch files tab lists visible scratch entries for the active workspace, with read-only previews and downloads. Hidden execution state remains internal.
Terminal access is rejected in hosted mode, when data connectors are disabled, or without a matching local Host and Origin. The Vite analyst proxy preserves Host for this check. Restart the backend after adding the skill; Vite reloads its proxy configuration automatically. Pending approvals are transient and cannot be restored after reloading the page; ask for a fresh proposal.
Focused checks (no browser automation required):
uv run pytest tests/backend/agents/test_terminal_skill.py tests/backend/agents/test_terminal_windows.py tests/backend/agents/test_background_tasks.py tests/backend/agents/test_analyst_skill_registry.py tests/backend/routes/test_analyst_data_operation_flow.py
npx vitest run tests/frontend/unit/views/TerminalApprovalDialog.test.tsx tests/frontend/unit/views/ToolsAccess.test.tsx
MCP Servers (Local Mode)
In single-user local mode the analyst agent can call tools from MCP servers the
user adds, as tools next to the terminal: Tools beside the chat input lists
them, and + Add MCP server takes one server's MCP configuration JSON
(How to add a server in the form shows examples), the format VS Code
(servers), Claude Desktop and Cursor (mcpServers) use; Codex TOML
([mcp_servers.*]) is also accepted. A remote server is a url ("type": "sse"
for older servers) with optional headers; a local one is a command with
args, env and cwd. MCP is off
in hosted and managed deployments and when data connectors are disabled.
-
Storage. Server definitions and the reviewed tool snapshots live in
~/.data_formulator/mcp.json(underDATA_FORMULATOR_HOME). API keys, header values, environment variables, OAuth tokens and client secrets go to the credential vault undermcp:<server-id>, never into the file. -
Sign-in. Public servers need nothing. A
headersentry sends an API key or token. A server that answers 401 without either is retried with the MCP SDK's OAuth flow (PKCE, dynamic client registration), as other MCP clients do; when it offers no OAuth, the error suggests adding a header. For providers that require a pre-registered app, add"oauth": {"client_id": …, "client_secret": …, "scope": …}. The redirect URI ishttp://localhost:<port>/api/mcp/oauth/callback. Agent runs never start a sign-in; an expired sign-in asks the user to sign in again in settings. -
Remote URLs must be HTTPS except
localhost/127.0.0.1. Local commands run as the user, outside the terminal sandbox, so add only trusted servers. -
Calls. Each enabled server becomes a gated skill (
mcp-<id>) whose actions aremcp_<id>__<tool>with the tool's own input schema. They follow the shared tools mode: Off; Ask every time (default); Auto when safe (tools annotatedreadOnlyHintrun without asking). Tools the user marked Always allow never ask. Approval pauses the run like a terminal command (Reject / Always allow this tool / Run once); the server-side request is single-use and bound to the conversation, and the stored arguments, not the client's, are what run. -
Tool changes. The first connection's tools count as reviewed. New or changed tools (name, description, schema or annotations) are held until the user allows them in settings; a call to a tool that changed since approval is refused.
-
Results. Short text returns inline (marked untrusted). Large text, images, audio and binary content are saved under
scratch/mcp/<server>/; the agent promotes anything worth keeping with its normal workspace actions. -
Agent-assisted setup. The configure skill's
list_mcp_serversshows the agent each server's status, last error, missing key or sign-in, and tools (never secret values).propose_mcp_changespublishes anmcpsetup form that opens in the Tools dialog (not the canvas; the thread entry reopens it): each proposed server appears as prefilled configuration JSON with empty""slots for keys and environment values, which the user fills in before Add and connect. The dialog closes once the new server connects (it stays open while signing in or on errors); server/tool switches and the shared tools mode apply on Apply changes. The form never submits itself, proposals containing secret values are rejected, and approving changed tools or Always allow stay manual. Both tools exist only when MCP is available and are excluded from workflow runs. -
Toolkits (tool packs). + Add tool → Toolkits adds a ready-made server that ships with Data Formulator (
py-src/data_formulator/tool_packs/) in one step; it runs with Data Formulator's own Python (no Node oruvdownload) and is then an ordinary MCP server row (packrecords its origin).GET /api/mcp/packslists packs;POST /api/mcp/packs/<id>adds one (once) with its optional settings.- Research (arXiv) (
tool_packs/research):search_papers,recent_papers,paper_details(up to 20 ids),count_papers(per year/month, for trends) andget_full_text(arXiv's HTML as Markdown, else the PDF; saved to scratch). All are read-only. It uses only arXiv's own API and pages (no citation counts) and is polite by construction: one connection, requests at least 3 seconds apart, a descriptive User-Agent with the optional contact email, retries with backoff on 429/5xx (honouringRetry-After) and on the API's "results but no entries" glitch, and a disk cache under~/.data_formulator/cache/research(searches a day, papers a week, versioned papers for good). Large JSON results save to scratch as.jsonso the agent can load them withcreate_data.
- Research (arXiv) (
Routes live under /api/mcp (py-src/data_formulator/mcp_servers/routes.py);
changes require a same-origin local request with X-DF-Configuration: 1. The
Vite dev proxy preserves Host for /api/mcp, so the check and OAuth redirects use
the app's own origin.
uv run pytest tests/backend/mcp
Azure CLI Deployment Discovery
In local mode, choose Add Model > Azure > Azure CLI, sign in, and select Browse deployments. The picker defaults to the CLI's current subscription and lists ready OpenAI model deployments grouped by resource. Selecting a deployment fills in its endpoint and deployment name; Test and save checks inference access using the existing Azure identity configuration.
Discovery uses read-only Azure CLI commands with explicit subscription arguments; it does not change the active CLI subscription, create deployments, or retrieve API keys. No additional app registration or Python package is required.
The initial picker supports public Azure OpenAI and Foundry (AIServices)
resources in enabled subscriptions in the current CLI tenant. It does not list
the undeployed Foundry catalog, other model formats, or sovereign-cloud endpoints.
To change tenant, sign in with the intended tenant through Azure CLI and reopen
the model dialog. Resource/deployment read permissions are separate from inference
permissions. Partial discovery failures are shown per resource. Enter manually
remains available for restricted discovery, unsupported endpoints, and custom
configurations. Network restrictions still apply to inference.
OpenRouter Account Connection
In Select Model, choose Add Model > OpenRouter > Connect OpenRouter. Authorization uses OpenRouter's OAuth PKCE flow; no application client secret is needed. After authorization, choose a tool-capable model and use Test and save. Model tests and subsequent usage are billed to the user's OpenRouter account.
The returned API key stays in the backend's encrypted credential vault. Model configurations, including knowledge-distillation requests and workspace exports, carry only a per-user connection reference. One OpenRouter account connection can serve multiple models. Removing a model does not disconnect the account. Disconnect forgets the saved key locally; revoke it separately in OpenRouter's key settings when needed. Reconnect starts a new authorization flow rather than refreshing a subscription token.
Local loopback callback origins are supported in local mode, including Vite's dev
port. Hosted deployments require HTTPS. When a reverse proxy changes the apparent
origin, set MODEL_CONNECTION_ALLOWED_ORIGINS to the exact public frontend origin
(comma-separated for multiple origins). The frontend origin must route
/api/model-endpoints/connections/openrouter/callback to this backend. Authorization
state expires after ten minutes and is bound to the initiating Data Formulator
identity, so callbacks also work when opened in an external browser.
The credential vault must be available, including when user-created data
connectors are disabled. Persist DATA_FORMULATOR_HOME and its vault key across
restarts. If DF_ALLOWED_API_BASES is configured, include
https://openrouter.ai/api/v1 to permit inference through this connection.
GitHub Copilot Account Connection (Experimental)
In Select Model, choose Add Model > Sign in > GitHub Copilot > Connect GitHub Copilot. Copy the displayed device code, open GitHub, and authorize the account. After authorization, select a compatible model and choose Test and save. Testing and subsequent agent requests consume the account's Copilot allowance; subscription limits, model access, and organization policies still apply. This is not GitHub Models or an API-key integration.
Device authorization uses the same default public OAuth client ID as LiteLLM's
Copilot adapter. GITHUB_COPILOT_CLIENT_ID can override it, but an arbitrary OAuth
app is not guaranteed Copilot entitlement. The adapter uses Copilot internal token
exchange endpoints and client headers; this is not a claim of official GitHub
support for third-party subscription clients. Review applicable GitHub terms and
organization policies before enabling it in a deployment.
Data Formulator stores the GitHub OAuth token and expiring Copilot token in its
identity-scoped encrypted vault. Device polling honors the provider interval and
slow_down; cancellation and expiry prevent a late exchange from saving tokens.
Copilot tokens are refreshed when resolving a connection for a new inference client
or model refresh. The frontend and saved model configurations receive no tokens.
Multiple saved models can share one connection; Edit > Disconnect forgets that
connection locally without deleting the models or revoking GitHub authorization.
Revocation is available separately in GitHub's application settings.
The installed LiteLLM github_copilot/ adapter ignores explicit API keys and reads
a shared on-disk cache. Data Formulator therefore uses LiteLLM's OpenAI-compatible
transport with explicit vault-resolved credentials and Copilot headers instead.
It does not read or write LiteLLM's Copilot token files or launch terminal login.
The picker includes enabled tool-calling chat models advertising
/chat/completions or /responses. The backend caches each model's transport in
the account connection when the catalog is refreshed; models advertising both
keep Chat Completions. Responses-only models use the Responses transport. Models
advertising only native protocols such as /v1/messages remain excluded.
ChatGPT Account Connection (Experimental)
In Select Model, choose Add Model > Sign in > ChatGPT > Sign in with ChatGPT. Enable device-code login in ChatGPT security settings, then enter the displayed code on OpenAI's authorization page. Select an account model and choose Test and save. Testing and agent requests use the account's subscription allowance; model availability, usage limits, and applicable OpenAI terms still apply. This is separate from OpenAI API-key access and does not provide API credits.
OAuth access and refresh tokens stay in Data Formulator's identity-scoped encrypted vault. The browser and saved models hold only a connection reference. Tokens are refreshed when resolving a new inference client or loading the model catalog. Edit > Disconnect deletes the local connection while retaining saved models; manage authorization separately in ChatGPT settings.
The pinned LiteLLM version's native ChatGPT adapter uses a shared token file by
default. The small agents/chatgpt_transport.py compatibility override replaces
its config factories with request-authenticated subclasses, without global tokens,
environment changes, or token files. Native ChatGPT request transformation,
Responses streaming, and response parsing remain in LiteLLM. Revalidate this
override when upgrading LiteLLM. The integration uses ChatGPT's Codex backend and
model catalog, which can change independently of the public OpenAI API; it is not
a claim of official support for third-party subscription clients.
Model Client Transports
Agents use the same Client.get_completion and get_completion_with_tools
methods for both transports. Client dispatches internally to Chat Completions
or LiteLLM's Responses bridge, returning chat-style messages and streaming deltas.
The provider model identity is unchanged; bridge-specific prefixes and parameter
mapping are confined to the transport implementation.
Backend OpenAI and Azure configurations can select api_type: "responses" or
api_type: "chat_completions"; omitting it preserves existing LiteLLM routing.
This is a backend configuration option, not a new control in the model dialog.
Copilot resolves the value from its server-side catalog, overriding caller input.
Refresh the model list to pick up changed Copilot capabilities.
Explicit Responses requests use store: false and request encrypted reasoning
items for replay in locally managed message history. Both streaming agent loops
retain these opaque items without rendering them as user-visible text. The
transport supports text and function tools, not provider-hosted tools or
background Responses jobs. It does not retry failed generation on a different
transport. Live provider behavior, billing, and immediate upstream cancellation
still require integration testing; the network-free tests verify protocol
conversion, tool turns, reasoning replay, streaming, usage, and failure handling.
The encrypted vault must be enabled and persisted as described above. No callback
URL is needed for device authorization. Outbound access is required to
github.com, api.github.com, and the Copilot API. Only these API bases are accepted:
https://api.githubcopilot.com, https://api.individual.githubcopilot.com,
https://api.business.githubcopilot.com, and https://api.enterprise.githubcopilot.com.
Include the applicable bases in DF_ALLOWED_API_BASES when that allowlist is enabled.
Custom GitHub Enterprise hosts are not supported by this initial implementation.
Frontend (TypeScript)
-
Install NPM packages
yarn -
Development mode
First, start the backend server (in a separate terminal):
uv run data_formulator --dev # or ./local_server.shThen, run the frontend in development mode with hot reloading:
yarn startOpen http://localhost:5173 to view it in the browser. The page will reload if you make edits. You will also see any lint errors in the console.
-
Component galleries
/devshows UI components in their states;/dev/mapsshows sample map artifacts (every layer type and control, with generated data) and lets you edit their JSON or open the Style panel. See design-docs/70-map-artifact.md.
Advanced: developing against a local flint-chart
DF depends on the published flint-chart package;
by default (FLINT_CHART_LOCAL unset) the bare flint-chart import resolves to that npm package.
For co-development against a local Flint checkout with hot reload, point FLINT_CHART_LOCAL at
its source when starting the dev server (or tests):
FLINT_CHART_LOCAL=../flint-chart/packages/flint-js/src yarn start
# tests:
FLINT_CHART_LOCAL=../flint-chart/packages/flint-js/src yarn test
Opt-in, advanced-dev only — leave it unset for normal work and CI, which use the installed
package. The alias is wired in vite.config.ts and vitest.config.ts.
Build for Production
-
Build the frontend and then the backend
Compile the TypeScript files and bundle the project:
yarn buildThis builds the app for production to the
py-src/data_formulator/distfolder.Then, build python package:
# With uv uv build # Or with pip pip install build python -m buildThis will create a python wheel in the
dist/folder. The name would bedata_formulator-<version>-py3-none-any.whl -
Test the artifact
You can then install the build result wheel (testing in a virtual environment is recommended):
# replace <version> with the actual build version. pip install dist/data_formulator-<version>-py3-none-any.whlOnce installed, you can run Data Formulator with:
data_formulatoror
python -m data_formulatorOpen http://localhost:5567 to view it in the browser.
-
Customize the home page (optional)
A demo is a front page of your own at
/demo/<id>, configured in aprofile.yamlwith no rebuild. See py-src/data_formulator/demos/README.md; the UIS demo at/demo/unescois an example.
Desktop installer validation
The desktop builds GitHub Actions workflow builds unsigned test artifacts.
Validate this path before integrating production signing. Windows installers
must be built and exercised on Windows; a successful macOS build is not Windows
installation evidence. Use a disposable Windows 11 x64 user account with an
interactive desktop, PowerShell 7, Inno Setup 6, Node/Yarn, and uv:
yarn install --frozen-lockfile
yarn build
uv sync --extra desktop --frozen
uv run pytest tests/backend/test_desktop_packaging.py tests/backend/test_startup_spinner.py tests/backend/test_desktop_single_instance.py -q
uv run pyinstaller --noconfirm --clean packaging/data_formulator_desktop.spec
./packaging/windows/build-installer.ps1 -PayloadDir 'dist/Data Formulator' -OutputDir release -Unsigned
The wrapper emits a versioned *-Setup-unsigned.exe, SHA-256 sidecar, and
.payload.json file manifest. Keep the manifest beside the installer when running
the installed-app test (substitute the generated filename):
./packaging/windows/test-installer.ps1 -Installer 'release/Data-Formulator-0.8.0b1-Windows-x64-Setup-unsigned.exe'
The test refuses to replace an existing installed app. It checks payload hashes,
native GUI/backend/sandbox startup, same-version reinstall, uninstall, and
retention of isolated application data. Logs and installation timing are saved
under build/installer-test; GitHub CI uploads them even when a step fails.
Different-version upgrade and browser-download acceptance remain separate tests.
Setup rejects destinations that would exceed the supported payload path length
before writing application files; use /DIR="a shorter per-user path" if needed.
The installed-app test covers this failure path as well as normal installation.
Setup installs per-user, preserves DATA_FORMULATOR_HOME/~/.data_formulator,
and provisions Microsoft's WebView2 Runtime if absent (network access required
in that case). Silent setup/uninstall supports
/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /LOG="path" in the intended user context.
Uninstall does not remove user data or the shared WebView2 Runtime.
Unsigned Windows installers are CI artifacts, not automatically published release assets. Production signing must cover the application, setup, and generated uninstaller before repeating validation on the actual browser download. Do not use manual unblocking or antivirus exclusions to declare a release usable.
For ADO task-based signing, the wrapper supports three explicit phases around an
already-signed payload. These replace an inline -SignCommand; do not combine
them with -Unsigned:
./packaging/windows/build-installer.ps1 -PayloadDir 'dist/Data Formulator' -OutputDir candidate -SigningPhase PrepareUninstaller -SignedUninstallerDir build/signed-uninstaller
# ESRP signs the single generated EXE in build/signed-uninstaller.
./packaging/windows/build-installer.ps1 -PayloadDir 'dist/Data Formulator' -OutputDir candidate -SigningPhase AssembleInstaller -SignedUninstallerDir build/signed-uninstaller
# ESRP signs the generated candidate/*-Setup.exe.
./packaging/windows/build-installer.ps1 -PayloadDir 'dist/Data Formulator' -OutputDir candidate -SigningPhase VerifyInstaller
Use an empty per-candidate cache and identical compiler/version/icon settings for
preparation and assembly. The preparation phase recognizes only Inno's documented
request to externally sign the generated uninstaller; other compilation failures
are fatal. Assembly verifies the cached uninstaller but does not emit release
checksums. Final verification requires valid payload signatures and timestamped
Microsoft signatures on the launcher/setup before emitting checksum and manifest
sidecars. Run test-installer.ps1 -RequireSignatures on the resulting installer
before any promotion; this also verifies the installed uninstaller.
On a service-session ADO agent, test-installer.ps1 -ValidationMode Headless
can exercise installation, signatures, payload integrity, sandbox/CLR, reinstall,
and uninstall without an interactive desktop. This is candidate-only
validation: installation.json records guiVerified: false, and the GUI report
explicitly records that it was skipped. Full validation remains the default.
Publish headless results only as distinctly labeled candidate artifacts; require
full interactive and browser-download acceptance before release promotion.
Docker
Docker is the easiest way to run Data Formulator without installing Python or Node.js locally.
Quick start
-
Copy the environment template and add your API keys:
cp .env.template .env # Edit .env and set your OPENAI_API_KEY, ANTHROPIC_API_KEY, etc. -
Build and start the container:
docker compose up --build -
Open http://localhost:5567 in your browser.
To stop the container: docker compose down
Workspace data (uploaded files, sessions) is persisted in a Docker volume (data_formulator_home) so it survives container restarts.
Compose publishes the port on 127.0.0.1 only and sets DF_SINGLE_USER=1, so you get single-user local mode (no login, all local features). To share the container with other machines, remove both: other clients then get separate anonymous workspaces instead of acting as you.
Build the image manually
docker build -t data-formulator .
docker run --rm -p 127.0.0.1:5567:5567 -e DF_SINGLE_USER=1 --env-file .env data-formulator
Without DF_SINGLE_USER=1 the container (which listens on 0.0.0.0) runs in anonymous multi-user mode. Set it only when the published port is reachable from your machine alone.
Docker sandbox (SANDBOX=docker) is not supported inside a container
The Docker sandbox backend works by calling docker run -v <host_path>:... to bind-mount temporary workspace directories into child containers. When Data Formulator itself runs in a Docker container those paths refer to the container filesystem, not the host, so Docker daemon cannot mount them and the feature does not work.
Use SANDBOX=docker only when running Data Formulator directly on the host (e.g. with uv run data_formulator --sandbox docker or python -m data_formulator --sandbox docker). When using the Docker image, keep the default SANDBOX=local.
Sandbox
AI-generated Python code runs inside a sandbox to isolate it from the main server process. Two backends are available:
| Backend | Flag | How it works | Overhead |
|---|---|---|---|
| local (default) | --sandbox local |
Persistent warm subprocess with pre-imported pandas/numpy/duckdb. Audit hooks block file writes and dangerous operations (subprocess, shutil, etc.). | ~1 ms |
| docker | --sandbox docker |
Each execution runs in a disposable docker run --rm container. Workspace is mounted read-only; output is returned via a bind-mounted parquet file. Memory/CPU/PID limits enforced. |
~700 ms |
# Use the default local sandbox
python -m data_formulator
# Use Docker sandbox (requires Docker daemon)
python -m data_formulator --sandbox docker
The Docker sandbox image is built from py-src/data_formulator/sandbox/Dockerfile.sandbox:
docker build -t data-formulator-sandbox -f py-src/data_formulator/sandbox/Dockerfile.sandbox .
Source: py-src/data_formulator/sandbox/
Azure Blob Storage Workspace
By default, workspace data (uploaded files, parquet tables, metadata) is stored on the local filesystem under ~/.data_formulator/workspaces/. For cloud deployments you can switch to Azure Blob Storage so all workspace data lives in a blob container instead.
Quick start (local dev with connection string)
-
Install extra dependencies:
pip install azure-storage-blob # or with uv: uv pip install azure-storage-blob -
Create a storage account & container (one-time setup):
az storage account create -n <account> -g <resource-group> -l eastus --sku Standard_LRS az storage container create -n data-formulator --account-name <account> -
Get the connection string:
az storage account show-connection-string -n <account> -g <resource-group> -o tsv -
Add to
.env:WORKSPACE_BACKEND=azure_blob AZURE_BLOB_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=... # AZURE_BLOB_CONTAINER=data-formulator # default, change if needed -
Run normally:
uv run data_formulator --devOr pass as CLI flags:
data_formulator --workspace-backend azure_blob \ --azure-blob-connection-string "DefaultEndpointsProtocol=https;AccountName=..."
Production setup with Entra ID (no secrets)
In production (Azure App Service, AKS, etc.) you can authenticate the app to blob storage via Managed Identity instead of a connection string. This eliminates secrets entirely.
-
Install extra dependencies:
pip install azure-storage-blob azure-identity -
Assign a role to the app's Managed Identity:
# Get the App Service's principal ID PRINCIPAL_ID=$(az webapp identity show -n <app-name> -g <rg> --query principalId -o tsv) # Grant it "Storage Blob Data Contributor" on the storage account az role assignment create \ --assignee "$PRINCIPAL_ID" \ --role "Storage Blob Data Contributor" \ --scope "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/<account>" -
Set environment variables (no secrets needed):
WORKSPACE_BACKEND=azure_blob AZURE_BLOB_ACCOUNT_URL=https://<account>.blob.core.windows.net # AZURE_BLOB_CONTAINER=data-formulatorThe app uses
DefaultAzureCredential, which automatically picks up the Managed Identity. -
For local development with the same Entra ID path, log in with the Azure CLI:
az login # Grant your user the same "Storage Blob Data Contributor" role az role assignment create \ --assignee "<your-email@example.com>" \ --role "Storage Blob Data Contributor" \ --scope "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/<account>"Then set:
WORKSPACE_BACKEND=azure_blob AZURE_BLOB_ACCOUNT_URL=https://<account>.blob.core.windows.netDefaultAzureCredentialwill use youraz loginsession.
Authentication methods summary
| Method | Env var | When to use |
|---|---|---|
| Connection string | AZURE_BLOB_CONNECTION_STRING |
Local dev, quick tests |
| Entra ID (Managed Identity) | AZURE_BLOB_ACCOUNT_URL |
Azure App Service, AKS — no secrets |
| Entra ID (az login) | AZURE_BLOB_ACCOUNT_URL |
Local dev without secrets |
| Entra ID (service principal) | AZURE_BLOB_ACCOUNT_URL + AZURE_CLIENT_ID / AZURE_TENANT_ID / AZURE_CLIENT_SECRET |
CI/CD pipelines |
If both AZURE_BLOB_CONNECTION_STRING and AZURE_BLOB_ACCOUNT_URL are set, the connection string takes precedence.
Blob layout
All workspace data is stored under <datalake_root>/<sanitized_identity_id>/ inside the container:
data-formulator/ ← container
workspaces/ ← datalake_root (default)
browser_550e8400.../ ← anonymous user workspace
workspace.yaml
sales_data.parquet
user_alice_example_com/ ← authenticated user workspace
workspace.yaml
quarterly_report.parquet
CLI flags reference
| Flag | Env var | Default | Description |
|---|---|---|---|
--workspace-backend |
WORKSPACE_BACKEND |
local |
local, azure_blob, or ephemeral |
--azure-blob-connection-string |
AZURE_BLOB_CONNECTION_STRING |
— | Shared-key connection string |
--azure-blob-account-url |
AZURE_BLOB_ACCOUNT_URL |
— | Account URL for Entra ID auth |
--azure-blob-container |
AZURE_BLOB_CONTAINER |
data-formulator |
Blob container name |
Deployment Profiles
Data Formulator runs in default mode unless administrator-managed operation is enabled. The deployment profiles below describe authentication and storage choices, not separate product editions.
Managed Mode
Start with data_formulator --managed or set DF_MANAGED=true to enable
administrator-managed resources and policies. Managed mode is off by default.
It does not change authentication, workspace storage, or the execution sandbox.
It can be used locally for setup as well as on a hosted installation.
Authorized administrators see Admin alongside App
(or in the compact navigation menu); other users see no page links. The page remains at /configurations for
existing links. Ordinary users do not see it, and the backend denies configuration
access unless both managed mode and administrator authorization are present.
Personal Settings remains separate from installation administration.
In Administration > Appearance, set an optional App name (up to 80
characters) and Tagline (up to 300 characters), then Save changes. A custom
name replaces Data Formulator in the landing heading, navigation,
and browser title. Long headings use smaller text and wrap. Clearing a field
restores its default; the default tagline follows the selected UI language.
These plain-text values are saved in installation configuration, not environment
variables, and are visible to all users. Other open clients pick up changes on
reload.
In single-user localhost identity mode, the local owner is the administrator.
For hosted installations, configure an authentication provider and list verified
identities in DF_ADMIN_IDENTITIES, for example user:<verified-subject> (comma
separated). Anonymous browser identities cannot administer the installation.
An anonymous demo can be provisioned locally before hosting, or use authenticated
administrators alongside anonymous visitors. Remote shared-connection saves
also require CREDENTIAL_VAULT_KEY.
For Azure App Service, configure administrators by full sign-in address at deployment:
DF_MANAGED=true
AUTH_PROVIDER=azure_easyauth
ALLOW_ANONYMOUS=false
DF_ADMIN_EMAILS=alice@example.com,bob@example.com
At runtime, the backend compares the trusted X-MS-CLIENT-PRINCIPAL-NAME
provided by EasyAuth against this comma-separated allowlist, ignoring case and
surrounding whitespace. Use the actual sign-in address, which can differ from a
mailbox alias or a guest user's home address. Missing addresses, short aliases,
display names, and wildcard patterns do not grant access. No directory lookup,
Graph permissions, or synchronization with Azure owners/roles is involved.
Email authorization currently supports Azure EasyAuth only; other providers keep
using DF_ADMIN_IDENTITIES. Workspace and credential identity remain based on
the verified object ID, not email. Admin access follows the address if reassigned,
so maintain the list when users leave or change addresses.
Alternatively, DF_ADMIN_IDENTITIES=user:<principal-id> matches the trusted
X-MS-CLIENT-PRINCIPAL-ID (the user's object ID for Entra). If both lists are
configured, matching either grants access; remove old ID entries when switching
to email-only administration. Azure subscription/resource ownership does not
automatically grant application administrator access. Without an allowlisted
authenticated identity or sign-in address, no hosted user is an application administrator.
Enable App Service Authentication and prevent direct access that bypasses its
trusted-header boundary. Do not expose a deployment in single-user localhost
identity mode through an unauthenticated proxy.
DISABLE_DATA_CONNECTORS=true / --disable-data-connectors force shared-only
connector access. DISABLE_CUSTOM_MODELS=true / --disable-custom-models force
shared-only model access. These deployment settings override saved configuration,
including previously saved false values. Administration disables the policy
controls, and the configuration API rejects attempts to set the corresponding
restriction to false, including JSON edits. Administrators can still manage
shared resources; changing a deployment lock requires changing the deployment
environment or startup flags and restarting the server.
Disabling an individual shared model or connector also blocks subsequent API and agent lookups by ID, including cached connector loaders. Administration retains access to inspect, test, and re-enable disabled resources. Already-running calls are not cancelled by a configuration change.
Managed-mode startup checks warn about missing administrator access, unsupported email authentication, and detectable hosted use of local-owner identity. They do not replace correct proxy/authentication configuration. Successful configuration saves log the verified actor ID, revision, and changed top-level sections without configuration values or credentials; retain these logs under your audit policy.
A fresh managed installation defaults to administrator-provided models and connections. These are editable defaults: Administration can permit user-created resources or restrict model endpoints. Explicit deployment restrictions remain locked. Existing saved configurations keep their policies when managed mode is enabled or disabled; turning it off hides administration, not policy enforcement. Legacy configurations trigger a startup notice explaining how to enable access.
--disable-database / DISABLE_DATABASE=true is deprecated. It still selects
managed mode plus its legacy demo restrictions and ephemeral workspace behavior,
and turns the credential vault off unless CREDENTIAL_VAULT is set (shared
connections that store secrets need CREDENTIAL_VAULT=local).
For new deployments, use --managed with explicit authentication, storage, and
policy settings.
Profile 1: Single-User Local (default)
A personal instance running on localhost. No login required, full feature access.
# Everything uses defaults — just run it:
data_formulator
# Or equivalently:
data_formulator \
--workspace-backend local \
--sandbox local
| Setting | Value | Why |
|---|---|---|
AUTH_PROVIDER |
(unset) | Single user, no login needed |
WORKSPACE_BACKEND |
local |
Persist workspaces to ~/.data_formulator/ |
DISABLE_DATA_CONNECTORS |
false |
Full access to MySQL, PostgreSQL, etc. |
DISABLE_CUSTOM_MODELS |
false |
User can add any LLM endpoint |
DISABLE_DISPLAY_KEYS |
false |
User can see/manage their own API keys |
| Credential vault | auto-enabled | Remembers DB credentials across restarts |
| Identity | local:<os_username> |
Fixed, OS-derived — survives localStorage clear |
Security notes: In single-user localhost mode, the server ignores the X-Identity-Id header entirely and uses a fixed identity derived from the OS username (e.g., local:alice). This means vault credentials and workspaces are tied to your OS account, not a random browser UUID — clearing localStorage won't orphan your data.
Single-user mode applies only when the server listens on a loopback address (--host 127.0.0.1, the CLI default). A server on --host 0.0.0.0, or under a WSGI server such as gunicorn without HOST=127.0.0.1, uses anonymous multi-user mode, because anyone who can reach it would otherwise act as you. DF_SINGLE_USER=1 keeps single-user mode where access is restricted another way; docker-compose.yml sets it and publishes the port on 127.0.0.1 only. Data-loader plugins (*_data_loader.py in ~/.data_formulator/plugins) load only in single-user mode, unless DF_ALLOW_PLUGINS=1.
Profile 2: Multi-User Anonymous (demo / public hosting)
A shared server (e.g., for demos, workshops, public access). No login, short-lived server-side workspaces, and no sensitive features.
data_formulator \
--managed \
--workspace-backend ephemeral \
--disable-data-connectors \
--disable-custom-models \
--disable-display-keys
Legacy shortcut:
--disable-database(orDISABLE_DATABASE=true) retains this preset but is deprecated.
Or via environment variables:
DF_MANAGED=true
WORKSPACE_BACKEND=ephemeral
# Ephemeral retention only; local mode is durable and ignores these settings.
EPHEMERAL_WORKSPACE_TTL_HOURS=24
EPHEMERAL_WORKSPACE_MAX_BYTES=10737418240
EPHEMERAL_WORKSPACE_CLEANUP_INTERVAL_SECONDS=1800
DISABLE_DATA_CONNECTORS=true
DISABLE_CUSTOM_MODELS=true
DISABLE_DISPLAY_KEYS=true
# Pre-configure the LLM models users can access:
OPENAI_API_KEY=sk-...
OPENAI_MODELS=gpt-4.1
Each model connection can optionally specify Small Model on the same endpoint,
using the same credentials. An empty Small Model uses Model instead. Test and
save succeeds only when both distinct model names pass; identical names are tested
once. For environment-managed connections, set {PROVIDER}_SMALL_MODEL, for example
OPENAI_SMALL_MODEL=gpt-4.1-mini. It applies to every Model configured for that
provider. Backend callers explicitly opt in with get_client(config, use_small_model=True). Workspace naming, starter questions, Smart Sort, and code
explanation use Small Model (or Model when Small Model is not configured). Other
agent tasks continue using Model, including the analyst and workflow agents,
semantic annotation, chart restyling, and knowledge distillation.
| Setting | Value | Why |
|---|---|---|
AUTH_PROVIDER |
(unset) | Anonymous access for demos |
WORKSPACE_BACKEND |
ephemeral |
Temporary server-local workspaces with TTL/LRU cleanup |
DISABLE_DATA_CONNECTORS |
true |
Critical — allows only administrator-configured sources; blocks personal connectors |
DISABLE_CUSTOM_MODELS |
true |
Prevents users from adding arbitrary LLM endpoints (SSRF risk) |
DISABLE_DISPLAY_KEYS |
true |
Hides server-configured API keys from UI |
| Credential vault | Available | Protects administrator-configured connection credentials |
| Identity | anonymous (browser:<uuid>) |
Isolates temporary workspaces by browser identity |
Retention notes: Ephemeral workspaces may disappear after inactivity or when the configured byte cap is reached. The browser keeps only a row-free recovery snapshot for read-only viewing. Use WORKSPACE_BACKEND=local for durable workspaces; ephemeral TTL/LRU cleanup never scans or deletes local-mode workspaces.
Security notes: Disable user-created connectors and custom models for anonymous deployments. Administrator-configured sources remain available, so publish only sources whose data may be shared with every app user. Browser identities are client-provided and are suitable for isolating disposable demo workspaces, not for protecting durable credentials or sensitive server-side state.
Profile 3: Multi-User Authenticated (team)
A shared server with SSO login. Full features, proper identity isolation.
data_formulator \
--managed \
--workspace-backend azure_blob \
--disable-display-keys
DF_MANAGED=true
DF_ADMIN_IDENTITIES=user:<verified-admin-subject>
AUTH_PROVIDER=oidc
OIDC_ISSUER_URL=https://your-idp.example.com/realms/main
OIDC_CLIENT_ID=data-formulator
ALLOW_ANONYMOUS=false
WORKSPACE_BACKEND=azure_blob
AZURE_BLOB_ACCOUNT_URL=https://<account>.blob.core.windows.net
DISABLE_DISPLAY_KEYS=true
DISABLE_CUSTOM_MODELS=true
FLASK_SECRET_KEY=<generate-with-secrets-token-hex-32>
| Setting | Value | Why |
|---|---|---|
AUTH_PROVIDER |
oidc / github / azure_easyauth |
Verified identity from SSO |
ALLOW_ANONYMOUS |
false |
Login required — no anonymous fallback |
WORKSPACE_BACKEND |
azure_blob or local |
Persistent per-user workspaces |
DISABLE_DATA_CONNECTORS |
false |
Not deployment-locked; Administration controls whether user-created connections are permitted |
DISABLE_CUSTOM_MODELS |
true |
Users only use server-configured models |
DISABLE_DISPLAY_KEYS |
true |
Hide server keys; users add their own |
FLASK_SECRET_KEY |
set explicitly | Required for stable sessions across server restarts |
| Credential vault | auto-enabled | DB credentials scoped to verified user:<id> |
| Identity | user:<sub> from auth provider |
Server-verified, cannot be spoofed |
Security notes: With an auth provider, get_identity_id() returns user:<verified_id> from the IdP token — the X-Identity-Id header is ignored entirely. Workspaces, vault credentials, and DB connections are all scoped to the verified identity. Set ALLOW_ANONYMOUS=false to prevent unauthenticated access.
Profile Comparison
| Feature | Profile 1 (Local) | Profile 2 (Demo) | Profile 3 (Team) |
|---|---|---|---|
| Login required | No | No | Yes |
| Data connectors (DB) | Yes | Administrator-configured only | Administrator policy |
| Custom LLM endpoints | Yes | No | Operator choice |
| Credential vault | Yes | Yes (configured sources/models) | Yes |
| Workspace persistence | Local disk | Ephemeral server storage | Cloud / disk |
| Identity | local:<os_user> (fixed) |
browser:<uuid> (client) |
user:<sub> (SSO) |
CLI Flags Reference (complete)
| Flag | Env var | Default | Description |
|---|---|---|---|
--managed |
DF_MANAGED |
false |
Enable managed resources and administrator-only Administration page; independent of auth, storage, and sandbox |
| — | DF_ADMIN_IDENTITIES |
(unset) | Comma-separated verified user:<subject> identities allowed to administer a managed installation |
| — | DF_ADMIN_EMAILS |
(unset) | Comma-separated full EasyAuth sign-in addresses allowed to administer a managed installation; case-insensitive exact matches |
--workspace-backend |
WORKSPACE_BACKEND |
local |
local, azure_blob, or ephemeral |
--sandbox |
SANDBOX |
local |
Code execution backend: local or docker |
--disable-database |
DISABLE_DATABASE |
false |
Deprecated demo preset: managed mode + ephemeral + configured connectors only + no custom models + hide keys + credential vault off (unless CREDENTIAL_VAULT is set) |
--disable-display-keys |
DISABLE_DISPLAY_KEYS |
false |
Hide API keys in frontend UI |
--disable-data-connectors |
DISABLE_DATA_CONNECTORS |
false |
Allow configured sources only; block personal connector creation and use |
--disable-custom-models |
DISABLE_CUSTOM_MODELS |
false |
Prevent users from adding custom LLM endpoints |
--max-display-rows |
MAX_DISPLAY_ROWS |
10000 |
Max rows sent to frontend |
--data-dir |
DATA_FORMULATOR_HOME |
~/.data_formulator |
Data directory |
--host |
HOST |
127.0.0.1 |
Network interface to bind. Only a loopback address selects single-user local mode; under gunicorn, set HOST=127.0.0.1 for that |
DF_SINGLE_USER |
unset | 1 keeps single-user local mode on a non-loopback address. Only for servers no one else can reach (e.g. a container port published on 127.0.0.1) |
|
DF_ALLOW_PLUGINS |
unset | 1 loads data-loader plugins outside single-user local mode. The plugin directory must be trusted |
|
-p, --port |
— | 5567 |
Port number |
--dev |
DEV_MODE |
false |
Development mode (no auto-open browser) |
| — | AUTH_PROVIDER |
(unset) | oidc, github, azure_easyauth, or unset for anonymous |
| — | ALLOW_ANONYMOUS |
true |
Allow unauthenticated access when auth provider is set |
| — | DF_ALLOWED_API_BASES |
(unset, all allowed) | Comma-separated URL globs for LLM endpoint allowlist |
| — | FLASK_SECRET_KEY |
auto-generated | Session signing key (set explicitly for production) |
--azure-blob-connection-string |
AZURE_BLOB_CONNECTION_STRING |
— | Azure Blob shared-key connection string |
--azure-blob-account-url |
AZURE_BLOB_ACCOUNT_URL |
— | Azure Blob account URL for Entra ID auth |
--azure-blob-container |
AZURE_BLOB_CONTAINER |
data-formulator |
Azure Blob container name |
Configured-Only Models
In Administration > Models, enable Disable user-created models
and save. This persists disable_user_models and restricts users to administrator-
configured models, including blocking use of previously saved personal models and
creation of personal account connections. Administrator model setup remains available.
DISABLE_CUSTOM_MODELS=true (also included in DISABLE_DATABASE) enforces this
restriction and locks the checkbox. Turning off the saved setting restores personal
models unless a deployment flag still restricts them. The endpoint URL allowlist is
independent and continues to apply when personal models are permitted.
Configured-Only Data Sources
In Administration > Data Sources, enable Disable user-created
connections and save. This persists disable_user_connectors in the installation
configuration. Users can still connect to and browse administrator sources from
the configuration page, connectors.yaml, or DF_SOURCES__*; personal connections
(including previously saved ones) cannot be created or used. Disabling the setting
restores access to personal definitions without deleting them.
DISABLE_DATA_CONNECTORS=true / --disable-data-connectors enforces the same policy
and locks the checkbox. DISABLE_DATABASE includes this restriction as part of its
existing deployment preset. These flags no longer disable configured sources or
the credential vault. Administrator connection testing and saving remain available.
In restricted mode, configure complete connection parameters on the server; user-supplied parameters cannot replace the configured host, URL, path, or credentials. Discovery requires a configured connector ID. Agent connection-creation tools and local terminal access are also disabled. This is a connector policy, not a network sandbox: uploads, other application capabilities, and database permissions need their own controls. Configure read-only database credentials where appropriate. Configured sources are shared with app users, so do not publish data that those users must not access.
Shared Connection Settings
Application Configuration stores model and connector settings inline under
overrides.connections.models and overrides.connections.connectors. Model
entries contain the provider, model name, endpoint URL, and authentication mode;
connector entries contain the loader type, display name, and non-sensitive
parameters. These settings are not stored in separate workflow-style files.
Each entry has an internal credential_ref linking it to encrypted credentials.
The referenced permanent vault record contains secrets, not the full connection
definition. API keys, passwords, and loader-declared sensitive parameters are
never written to configuration JSON. Connection tests use temporary encrypted
staging records; saving promotes their credentials and writes the readable
settings. API changes to connection settings require a fresh connection test.
Environment-provided connections remain managed by the deployment.
Legacy bare vault references still load. The configuration view expands them into readable settings; the next save persists the inline form and migrates credentials without changing them. The installation's credential vault and encryption key are still needed when moving or restoring the configuration.
Shared Workflow Files
Custom workflows saved through Application Configuration are stored as separate
YAML files in workflows/ next to configuration.json in the installation data
directory. Configuration holds references, for example:
{
"workflows": {
"server/team-review.yaml": {
"file": "workflows/team-review.yaml",
"enabled": true
}
}
}
This is the workflows section inside overrides. Administrators can place YAML
files in that directory and reference them directly. Only simple .yaml
filenames are accepted; absolute paths, traversal, and symlinks are rejected.
Bundled defaults retain their demo/<filename>.yaml IDs and use
"file": "builtin:<filename>.yaml" when saved without content changes. Editing a
built-in creates a separate custom file without modifying the bundled original.
The editor continues to load and edit YAML. Saving edited content creates a new
uniquely named file and updates the reference, preserving the previous file if
the configuration save fails. Removing a reference or resetting an override does
not delete files. Old unreferenced versions can be removed manually. Existing
inline content remains readable and migrates to file references on the next
configuration save. Personal workspace workflow storage is unchanged.
Conversational Workflow Authoring
In local or managed mode, ask the main chat to create a workflow from the current analysis.
Managed deployments (including the legacy DISABLE_DATABASE=true preset) support
workflow authoring, personal workflow libraries, and execution for the current
application identity; application administrator access is not required. Libraries
remain user-scoped and run checkpoints remain workspace-scoped. Existing model,
connector, and execution-sandbox policies still apply. Terminal commands remain
restricted to single-user local mode; managed mode does not enable host-shell access
or provide additional sandbox isolation.
The Define a workflow shortcut in the Workflows panel submits a guidance prompt
to that same chat without changing its conversation focus. There is no separate
authoring dialog. The analyst uses the current conversation and data context,
asks clarification questions when needed, and calls propose_workflow to publish
a validated definition in the chat rather than a Markdown file. The proposal
action does not save files or execute the workflow.
propose_workflow accepts a structured definition object and a short summary.
The canonical JSON Schema lives in workflows/instances.py; the skill registry
embeds it in the tool schema, and the proposal handler validates the object before
serializing YAML for display and storage. YAML imports use the same contract, and
adapt_plan reuses its step schema. Unknown definition, parameter, step, and checker
fields are rejected; source mappings remain open descriptive guidance. New proposals
require step descriptions, while older saved steps without descriptions remain valid.
Structural validation is supplemented by parameter-value, unique-ID, and transition
checks. Date interpretation and analytical correctness still require task-specific
verification; schema validity alone does not establish either.
Save to workspace writes a .workflow.yaml file visible under Workspace
workflows. Existing files require their current content hash to be overwritten.
Run opens the usual setup form and can execute the reviewed definition without
saving it first. These actions are independent. Proposals are persisted with
ordinary chat turns, and their complete YAML is included in focused-thread context
for follow-up revisions. They are not entries in the shared workflow library.
New chat-authored definitions require version: 1, name, overview,
deliverables, and concrete ordered steps. Each step identifies its operation,
inputs, expected results, and relevant verification conditions. Optional prompt
provides cross-step constraints and adaptation rules; it does not replace the
procedure. source and parameters support fixed inputs, parameterized inputs,
and mixtures. propose_workflow returns a repair request for step-free definitions.
Older saved definitions without steps remain runnable through an initial planning
phase; newly authored definitions seed the run with their concrete steps.
Each run stores an independent definition snapshot, mutable plan.steps, and
execution state (progress, checks, evidence, outputs, and history). Adapting a run's
plan never rewrites its definition or the saved YAML. Existing checkpoints are
migrated when resumed. Workflow authoring belongs to the main analyst's configure
skill; the execution agent cannot call propose_workflow.
Configuring Data Formulator in Chat
The analyst's gated configure skill (analyst/skills/configure/) sets up the
application for the user: connections, workflows, workflow schedules,
sessions, and (local app only) MCP servers. Read-only tools inspect the current setup (list_connectors,
describe_connector, read_connector_form, list_workflows, list_schedules,
list_sessions, list_mcp_servers). Each committing action publishes one prefilled setup form artifact:
propose_connection / update_connector_form (connector form),
propose_schedule (schedule form), and propose_session_changes (a session panel
whose cards each offer rename, open in a new tab, and delete; the agent only lists
sessions and suggests names, and deletion is always the user's confirmed action). propose_workflow publishes the existing workflow proposal artifact.
All forms share one interact event shape (form.kind, title, response,
auto_submit, and a body keyed by the kind) and are submitted by the frontend
through the same APIs as the corresponding dialogs, so credentials, permissions,
and session state follow the manual path. Setup tools act for the identity the
app resolves for the request (get_identity_id), which must own the active
workspace. Review is the default. With user_review_needed: false, a schedule or
session form submits itself once when the live agent stream creates it, only if it
is complete and not elevated; schedules that auto-approve or publish always wait
for review. The request is never persisted, so reloaded or imported sessions cannot
replay it. Connections always wait for the user's Connect, because connecting
opens a server-side network connection to agent-supplied hosts. Workflow runs keep
propose_connection but cannot author workflows or manage schedules and sessions.
See design-docs/56-configure-skill.md.
Workflow Setup
Workflows can declare optional top-level parameters. Clicking Run opens a setup
form before creating a session or calling the agent. Every workflow also accepts
optional additional instructions, including workflows without parameters.
parameters:
- name: symbol
label: Stock symbol
type: text
default: MSFT
required: true
- name: period
label: Review period
type: select
options: [Latest month, Latest year]
default: Latest month
allow_custom: true
description: Relative to the latest available data.
Supported types are text (the default), number, boolean, and select.
Names must be unique identifiers; labels are required. description, default,
and required are optional. Select fields require unique string options;
allow_custom: true permits a typed alternative. An unchecked boolean is a valid
false value, including for required fields. There are at most 20 parameters,
50 options per select, 4,000 characters per text value, and 8,000 characters of
additional instructions. Avoid requesting passwords or other secrets in setup.
New-run requests accept setup: {parameters: {...}, instructions: "..."}. The
server validates values against the current workflow, resolves missing defaults,
and saves the confirmed setup separately from the workflow snapshot. The agent
receives it as user guidance, with precedence over workflow defaults, not as code
substitution or additional authorization. Workflow instructions should explain
how each parameter affects the task and label fallback values as defaults.
Source constraints, data verification, and tool approvals still apply.
Setup is immutable on resume; later changes use normal workflow steering. Saved run state includes the initial setup. No setup agent call is made: a future assisted setup step can supply the same validated payload without changing the execution contract.
Security Considerations for Production Deployment
⚠️ IMPORTANT SECURITY WARNING FOR PRODUCTION DEPLOYMENT
Identity System
Data Formulator uses a namespaced identity system with three tiers:
- Local mode (
127.0.0.1, no auth provider): Identity islocal:<os_username>, determined by the server. TheX-Identity-Idheader is ignored. Vault and workspaces are tied to the OS user. - Anonymous mode (multi-user, no auth provider): Identity is
browser:<uuid>where the UUID is generated in the browser'slocalStorage. The server trusts the client-providedX-Identity-Idheader, but always forces thebrowser:prefix. - Authenticated mode (auth provider configured): Identity is
user:<verified_id>from the auth provider. TheX-Identity-Idheader is ignored entirely.
Key security principle: An attacker sending X-Identity-Id: user:alice@... gets browser:alice@... — completely separate from the real user:alice@... that only authenticated Alice can access.
Anonymous spoofing risk: In anonymous mode, if an attacker knows another user's browser UUID, they can impersonate them via the X-Identity-Id header. This is why Profile 2 disables data connectors (no DB credentials to steal) and Profile 3 requires authentication (header is ignored).
Data Storage
| Backend | Flag | Storage | Persistence |
|---|---|---|---|
| local (default) | --workspace-backend local |
~/.data_formulator/users/<identity>/workspaces/ |
Server filesystem |
| azure_blob | --workspace-backend azure_blob |
Azure Blob container | Cloud |
| ephemeral | --workspace-backend ephemeral |
Browser IndexedDB (frontend) + temp dirs (backend) | Browser session only |
Recommended Security Measures
- Multi-user anonymous (demos): Use Profile 2 —
--workspace-backend ephemeral --disable-data-connectors --disable-custom-models --disable-display-keys(or--disable-databaseas shortcut) - Multi-user authenticated: Use Profile 3 — set
AUTH_PROVIDER,ALLOW_ANONYMOUS=false, andDISABLE_CUSTOM_MODELS=true - HTTPS: Use a reverse proxy (nginx, Azure App Gateway) with TLS termination
FLASK_SECRET_KEY: Set explicitly for production (auto-generated key changes on restart)
Server Migration Checklist
When migrating Data Formulator to a new server (or rebuilding a Docker container), the following secrets and data files must be carried over. Losing any of them will break the corresponding functionality.
| Item | Location | What breaks if lost |
|---|---|---|
FLASK_SECRET_KEY |
.env (env var) |
All user sessions invalidated (SSO login, plugin tokens). Agent code signatures fail verification — saved charts cannot refresh until re-executed. |
.vault_key |
DATA_FORMULATOR_HOME/.vault_key |
credentials.db becomes undecryptable — all stored database passwords / service credentials are lost. |
credentials.db |
DATA_FORMULATOR_HOME/credentials.db |
Same as above — the encrypted credential store itself. |
DF_CODE_SIGNING_SECRET |
.env (env var, optional) |
If set, overrides Flask-derived signing key. Must match the old value or all code signatures break. |
.code-signing-key |
DATA_FORMULATOR_HOME/.code-signing-key |
Code-signing key used when neither DF_CODE_SIGNING_SECRET nor FLASK_SECRET_KEY is set. Saved charts cannot refresh until re-executed. |
CREDENTIAL_VAULT_KEY |
.env (env var, optional) |
If set, overrides .vault_key file. Must match or vault data is unreadable. |
CREDENTIAL_VAULT=off |
env var (optional) | Disables the vault: no .vault_key or credentials.db is created, credentials last only for the session, and shared connections or account model sign-ins that need stored secrets are unavailable. The default under DISABLE_DATABASE; set CREDENTIAL_VAULT=local to keep the vault there. |
DF_SANDBOX_BWRAP |
env var (optional) | Path to a bubblewrap binary for the Python sandbox's OS isolation on Linux, when it is neither on PATH nor bundled at py-src/data_formulator/sandbox/bin/bwrap (a git-ignored location that deployment packages fill). The startup log reports the isolation in use: Python sandbox isolation: .... |
DF_REQUIRE_OS_SANDBOX=1 |
env var (optional) | Refuse to run generated code when OS isolation is unavailable, instead of falling back to audit hooks only. Recommended for hosted deployments once the startup log confirms isolation. |
DF_SANDBOX_IDLE_SECONDS |
env var (optional) | How long an idle Python sandbox worker (a separate ~200 MB interpreter) is kept for reuse. Default 600 in local mode, 120 otherwise; multi-user servers also keep at most two used workers idle per server process. |
users/ & workspaces/ |
DATA_FORMULATOR_HOME/ |
User workspace data (parquet files, session metadata). |
configuration.json |
DATA_FORMULATOR_HOME/configuration.json |
Installation policies, enabled resources, defaults, and shared-connection vault references. |
workflows/ |
DATA_FORMULATOR_HOME/workflows/ |
Administrator-published workflow YAML referenced by installation configuration. |
Minimum migration steps:
Stop all application workers before taking the backup, and restore before any
worker starts. Keep installation configuration, workflow files, credential vault,
and encryption keys from the same backup. Blob workspace storage does not back
up this installation state. If --data-dir is set, use that directory for
installation configuration and workflows. Preserve deployment environment
settings too, including admin allowlists and immutable policy flags; securely
export these through your deployment platform if they are not stored in .env.
# On the OLD server — back up secrets + data
cp .env /backup/.env
cp $DATA_FORMULATOR_HOME/.vault_key /backup/.vault_key
cp $DATA_FORMULATOR_HOME/credentials.db /backup/credentials.db
if [[ -f "$DATA_FORMULATOR_HOME/configuration.json" ]]; then
cp "$DATA_FORMULATOR_HOME/configuration.json" /backup/configuration.json
fi
if [[ -d "$DATA_FORMULATOR_HOME/workflows" ]]; then
cp -R "$DATA_FORMULATOR_HOME/workflows" /backup/workflows
fi
# Copy workspace data if using local backend
cp -r $DATA_FORMULATOR_HOME/users /backup/users
cp -r $DATA_FORMULATOR_HOME/workspaces /backup/workspaces
# On the NEW server — restore before first start
cp /backup/.env .env
cp /backup/.vault_key $DATA_FORMULATOR_HOME/.vault_key
cp /backup/credentials.db $DATA_FORMULATOR_HOME/credentials.db
if [[ -f /backup/configuration.json ]]; then
cp /backup/configuration.json "$DATA_FORMULATOR_HOME/configuration.json"
fi
if [[ -d /backup/workflows ]]; then
cp -R /backup/workflows "$DATA_FORMULATOR_HOME/workflows"
fi
cp -r /backup/users $DATA_FORMULATOR_HOME/users
cp -r /backup/workspaces $DATA_FORMULATOR_HOME/workspaces
Tip: If you forgot to back up
FLASK_SECRET_KEYand it was auto-generated, there is no way to recover it. Users will need to log in again, and any chart with a cached code signature will need to be re-executed by the Agent.
中文版: 详细的迁移操作指南见 docs/docs-cn/7-server-migration-guide.md。
Authentication Architecture
Data Formulator supports a hybrid identity system with anonymous and authenticated modes. See Deployment Profiles above for which mode to use in each scenario.
Identity Flow
┌─────────────────────────────────────────────────────────────────────┐
│ Frontend Request │
├─────────────────────────────────────────────────────────────────────┤
│ Headers: │
│ X-Identity-Id: "local:alice" / "browser:550e8400-..." / ... │
│ Authorization: Bearer <jwt> (if auth provider configured) │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Backend Identity Resolution │
│ (auth.py: get_identity_id) │
├─────────────────────────────────────────────────────────────────────┤
│ Priority 1: Auth provider (OIDC/GitHub/EasyAuth) → "user:<id>" │
│ Priority 2: Localhost mode (127.0.0.1) → "local:<user>" │
│ (ignores X-Identity-Id header) │
│ Priority 3: X-Identity-Id header → "browser:<id>" │
│ (client-provided namespace prefix is IGNORED) │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Storage Isolation │
├─────────────────────────────────────────────────────────────────────┤
│ "user:alice@example.com" → alice's workspace (ONLY via auth) │
│ "local:alice" → localhost user's workspace (fixed) │
│ "browser:550e8400-..." → anonymous user's workspace │
└─────────────────────────────────────────────────────────────────────┘
Auth Provider Setup
See the AUTH_PROVIDER section in .env.template for configuration details.
| Provider | AUTH_PROVIDER |
Setup |
|---|---|---|
| OIDC / OAuth2 | oidc |
Set OIDC_ISSUER_URL + OIDC_CLIENT_ID |
| GitHub | github |
Set GITHUB_CLIENT_ID + GITHUB_CLIENT_SECRET |
| Azure EasyAuth | azure_easyauth |
Enable in Azure App Service (no extra env vars) |
| Anonymous only | (unset) | Default — no login, browser:<uuid> identity |
Usage
See the Usage section on the README.md page.