A local-first analytical workspace that turns CSV data into reviewable facts, evidence-backed findings, and reproducible reports.
Analyst Copilot combines a deterministic data-analysis core with bounded agent workflows. Pandas, DuckDB, SciPy, and scikit-learn establish reproducible facts; LLMs may plan, select typed tools, and explain results, but they cannot bypass method contracts, evidence validation, approval boundaries, or publication gates.
The project is designed for analysts who want AI assistance without treating a model response as the system of record. Every meaningful result is represented as a typed artifact, linked to source evidence, recorded in a trace, and exposed for review in a React workbench.
The current release is 0.2.0. The Python implementation lives in
eda_platform/, the web application in
apps/web/, and operational entry points in scripts/.
Analyst Copilot is under active development. The local, single-workspace flow, deterministic EDA, question execution, reporting, comparison, and guarded chat paths are implemented and covered by automated tests. Autonomous exploration is present behind a fail-closed production certificate gate; it remains hidden until a trusted provider-specific three-bucket evaluation certificate is installed.
Remote mode is a protected single-workspace deployment option, not a multi-tenant security boundary. Open-ended model-authored Python requires the separate Docker sandbox and fails closed when its runtime proof is unavailable. See Security model and Known boundaries before using the project with sensitive data.
- Capabilities
- How it works
- Agent harness and evaluation
- Technology and requirements
- Quick start
- Ways to run the app
- User guide
- LLM and privacy settings
- Optional: open-ended Python analysis
- Artifacts and traceability
- Security model
- Known boundaries
- Development and quality checks
- Project structure
- Contributing
- License
| Area | What the platform provides |
|---|---|
| Data ingestion and understanding | Upload one or more CSV files and generate column profiles, quality checks, statistical analysis tables, chart specifications, and raw-data views. |
| Data preparation | Optionally apply non-destructive missing-value and IQR-outlier cleaning before analysis. Source files are never modified in place; cleaning produces a new data version. |
| Multi-table analysis | Discover candidate relationships from names, types, overlap, and uniqueness; validate joins with DuckDB; inspect join multipliers, orphan rates, cardinality, and an ER diagram. |
| Guided investigation | Generate, score, and batch-execute verifiable questions; review findings, statistical tests, and optional lightweight ML baselines with model cards. |
| Reporting | Produce evidence-backed reports with a claim ledger and hard-validator results. Download a self-contained HTML report, or PDF when the optional dependency is available. |
| Conversational analysis | In live LLM mode, use Chat for intent routing, planning, read-only DuckDB SQL, guarded missingness diagnostics, and leakage-aware baseline modeling. Open-ended Python analysis is isolated behind a sandbox; causal guidance is advisory and causal claims remain fail-closed. |
| Reuse and comparison | Edit semantic knowledge, save validated analysis skills, and compare two runs or fork a one-change variant. |
| Observability | Watch a run's stages and events in the floating Activity panel, then inspect the typed artifacts, validation state, and errors it produced. |
flowchart LR
U["CSV files + business context"] --> D["Deterministic profiling and quality"]
D --> Q["Questions + AnswerContract"]
Q --> R{"Execution route"}
R -->|"typed tool calling"| A["bounded question agent"]
R -->|"offline or unsupported"| P["deterministic SQL pipeline"]
A --> G["method and evidence gates"]
P --> G
G -->|"pass"| F["typed findings"]
G -->|"fail"| X["typed abstention"]
F --> O["report + claim ledger"]
X --> O
O --> T["trace, artifacts, usage, and eval trial"]
The model is never the persistence layer. Drivers invoke typed tools; tools produce content-addressed artifacts and evidence references; deterministic validators decide whether a result may become a finding or report claim. A failed method contract becomes an explicit abstention instead of a plausible but unsupported answer.
The runtime deliberately stays framework-light. It uses project-owned Pydantic contracts, an explicit tool loop, durable journals and receipts for exploration, and a single canonical evaluation trial format. This keeps execution semantics inspectable without coupling the product to a third-party agent graph runtime.
Auto EDA uses a staged, per-table lifecycle instead of retaining every pandas DataFrame for the duration of a run:
- Inputs are copied into content-addressed storage and represented by
lightweight
DatasetSourcehandles. - One table is materialized for profiling, quality checks, charts, statistics, and optional modeling; only typed artifacts survive when that table is released.
- Multi-table SQL imports the trusted CSV inputs sequentially into a private temporary DuckDB database. External file access is then disabled before any generated or template SQL can execute. DuckDB has a 512 MB memory ceiling and may spill into that private temporary directory.
- Reports consume the artifact graph rather than source DataFrames.
Resource preflight records both the estimated and verified working set. Its
memory decision is based on the largest active table plus bounded temporary
work, while total input bytes, rows, columns, and deep-frame bytes remain
separate admission and telemetry fields. A resource stop is published as
limited, not as a successfully completed analysis.
As a reference regression, the nine-table Olist fixture (126 MB of CSV, approximately 495 MB combined deep-frame size) completes offline with 123 artifacts in about 32 seconds on the development machine. The measured process peak was approximately 446 MB, or 242 MB above the imported-runtime baseline; these measurements are illustrative rather than deployment guarantees.
The harness treats the complete system—model, prompts, tools, policy, environment, data fingerprints, and budget—as the unit under test.
QuestionAnswerContractbinds an answer to its required metric, method, artifact type, tool identity, and result shape. Prediction and anomaly work require their dedicated typed outputs; unsupported forecast, segmentation, and causal execution paths abstain.WorkflowEvalTrialis the canonical release record. It combines final-output quality with artifact lineage, evidence locators, action spans, pending approvals, report publication state, and ledger/budget/metrics reconciliation.- Exploration certification evaluates three independent buckets: planted analytical capability, negative controls, and prompt/tool injection. All buckets share signed identity, fingerprint, seed, and budget constraints.
- Component scores cannot override a failed hard gate. Replay without durable trace evidence is inconclusive rather than silently passing.
Run the deterministic workflow evaluation with:
uv run python scripts/evaluate_workflow.py \
--case eda_platform/tests/evals/workflow_quality/cases/semantic_guardrails.json \
--input-dir eda_platform/tests/evals/workflow_quality/data \
--repeat 3The repository also contains adversarial, golden, scoreboard, security, and
exploration-release suites under eda_platform/tests/.
- Python 3.12+
- Node.js 20+ and npm, to build the React workbench (
apps/web) - FastAPI for the
/api/v1contract and React + Vite for the product UI - Pandas, DuckDB, SciPy, and scikit-learn for processing, querying, statistics, and baseline modeling
- Pydantic for typed artifacts and workflow contracts
- Vega-Lite and
vl-convert-pythonfor interactive charts and PNG export; Matplotlib is available to sandboxed open-ended Python analysis uv(recommended) or a standard Python virtual environment- Docker Desktop (optional, for isolated open-ended Python analysis)
Linux, macOS, and Windows are all supported. The platform-specific pieces —
cross-process file locking, the run fence, worker launch and termination, and
process birth identity — each have a Windows implementation alongside the POSIX
one; see core/file_lock.py, core/run_fence.py, core/process_control.py,
and infrastructure/launch_gate.py. The automated suite currently runs on
Linux and macOS only, so the Windows branches are reviewed but not yet covered
by CI.
Run the following commands from the repository root.
Python (using uv is recommended):
uv sync --extra devWithout uv:
python -m venv .venv
.venv/bin/python -m pip install -e ".[dev]"Frontend:
npm install --prefix apps/webOffline mode requires no API key and can run immediately. To enable live LLM
reports and Chat, or to put the workspace somewhere other than
eda_platform/workspace, copy the example file:
cp .env.example .envAdd the credentials for the chosen provider to .env, and set EDA_WORKSPACE
to an absolute path if you want a different data directory. The real .env
file is ignored by Git and must never be committed.
npm run build --prefix apps/web
uv run python scripts/serve.py # http://127.0.0.1:8000serve.py serves the built React workbench and the API from one origin. This is
the default and recommended way to run the platform. Rebuild the frontend
whenever apps/web changes; serve.py serves whatever is in apps/web/dist.
Open http://127.0.0.1:8000/. A fresh workspace has no projects yet:
- Create a project from the project list. The display name is free text; the
project id becomes a directory name, so it is restricted to letters, digits,
spaces,
_,., and-. Ids that differ only in case are rejected, because the same directory would be reused on case-insensitive filesystems. - Upload one or more CSV files on the new-run page, optionally add business
context, and choose the LLM mode (
Environment defaultuses whatever.envconfigures;Offlinemakes no API calls). - Run the analysis. It executes in a separate worker process, so progress keeps streaming into Activity even if you close its panel or navigate away, and the run survives closing the browser tab.
- When the job completes, the run's pages (Data Map, Quality, Report, …) are populated. Past runs are listed in the left session rail.
The React UI (apps/web) talks to the FastAPI backend under /api/v1.
npm run build --prefix apps/web
uv run python scripts/serve.py # http://127.0.0.1:8000
uv run python scripts/serve.py --port 8321 # alternate portserve.py serves the static build and the API from one origin, with an SPA
fallback so deep links like /projects/<id>/sessions/<id>/data-map work on refresh.
It binds loopback by default. In local mode, non-loopback bind addresses are
rejected; configure authenticated remote mode before passing a public or LAN
--host value.
The web UI is enabled by passing serve_web_dist to create_app (which is what
serve.py does); a bare create_app() (Option B) serves the API only.
uv run uvicorn eda_platform.api.main:create_app --factory --port 8000
npm run dev --prefix apps/web # Vite on http://localhost:5173, proxies /api to :8000cd docker/app
docker compose up --build # http://127.0.0.1:8000, loopback only
docker compose --profile caddy up --build # optional Caddy front proxy on :8080The container stores data in a named workspace volume and runs as a non-root
user. See docker/app/ for the Dockerfile, compose file, and an
example Caddyfile (gzip, upload cap, SSE-safe proxying). Open-ended Python
analysis is rejected inside this container because no sandbox backend is
available there; core EDA runs are unaffected.
Public deployment is an explicit security mode; changing only the Caddy domain
is not sufficient. Set EDA_DEPLOYMENT_MODE=remote, exact
EDA_ALLOWED_HOSTS host names, exact HTTPS EDA_ALLOWED_ORIGINS browser origins,
and a unique 32+ character EDA_REMOTE_AUTH_TOKEN. The browser prompts for HTTP
Basic credentials; use eda as the username and the configured token as the
password. Basic credentials are accepted only as a single-workspace deployment
boundary and must be protected by TLS. Remote unsafe requests additionally
require the same allowed origin plus the frontend's
X-EDA-CSRF signal. Remote uploads are also rate-limited by the direct client
address (or X-Forwarded-For only when the direct proxy IP is explicitly
trusted). Persistent SQLite quotas cover each project's canonical file count,
aggregate upload bytes, and concurrent upload reservations. See
.env.example for defaults and overrides. The default local
mode keeps the existing loopback-only, zero-configuration workflow.
EDA_WORKSPACEmust be an absolute path. Relative values are rejected. Without an override, every entry point resolves the same repository-anchorededa_platform/workspacepath, independent of its current working directory. This guard prevents API and worker processes from silently creating separate workspaces.
- Pick or create a project, then open New session.
- Upload one or more CSV files, optionally add business context, and choose the LLM mode for this run.
- Start the analysis. Open the draggable Activity button to see the stage stepper (Profiling, Quality, Charts, Analysis, Starter questions), and switch to the separate Event log section for the raw stream. Cancel stops the run at the next stage boundary and keeps whatever artifacts were already written.
- Read the results: Data Map for run health and per-dataset summaries, Quality for issues by severity, Profiles & Charts for column profiles and charts, Relationships for the dataset graph.
- Investigate: approve and run a candidate in Questions, collect conclusions in Findings, organise leads on the Board, or ask constrained read-only questions in Chat.
- Audit: Report for the narrative with its claim ledger, Artifacts for the raw typed artifacts behind every number.
Every page is addressable — the URL carries project, run, dataset, and table offset, so refreshing or sharing a link restores the same view. Past runs are in the left session rail; runs derived from another run (question batches, skill replays) are hidden from that list but remain reachable by direct link.
| Page | Route | Purpose |
|---|---|---|
| Projects | /projects |
Create a project, or open an existing one. |
| New session | /projects/:id/new-session |
Upload CSVs, set business context and LLM mode, start the analysis. |
| Data Map | …/sessions/:id/data-map |
Run health, key indicators, and per-dataset summaries. |
| Table Preview | …/sessions/:id/table/:datasetId |
Server-paged rows with column types; the offset lives in the URL. |
| Quality | …/sessions/:id/quality |
Data-quality issues by severity, filterable per dataset. |
| Profiles & Charts | …/sessions/:id/profiles |
Column profiles and the deterministic chart canvas. |
| Relationships | …/sessions/:id/relationships |
Dataset graph (join edges follow the relationships API). |
| Cleaning | …/sessions/:id/cleaning |
Preview a cleaning recipe, approve it, and fork an analysis of the cleaned version. |
| Questions | …/sessions/:id/questions |
Review candidates, approve one, and execute it as a tracked job. |
| Findings | …/sessions/:id/findings |
Evidence-backed conclusions with freshness and source links. |
| Knowledge | …/sessions/:id/semantic |
Edit field meanings, confirm join whitelist entries, accept or reject proposals. |
| Report | …/sessions/:id/report |
The narrative with its claim ledger and validation state. |
| Artifacts | …/sessions/:id/artifacts |
Browse the typed artifacts behind every number. |
| Chat | …/sessions/:id/chat |
Constrained read-only questions; analysis plans require approval before they run. |
| Board | …/sessions/:id/board |
Organise leads as cards; drag with the mouse or move them with the keyboard. |
| Compare | /projects/:projectId/compare |
Compare Overview, Questions, Analysis, Findings, Report, Artifacts, and Execution for two runs of the same project. Each semantic scope loads lazily from /api/v1/compare/{scope}; …/sessions/:id/compare redirects here with the run as ?left=. |
| Skills | …/sessions/:id/skills |
Browse saved skills and seed templates, and replay one against this run. |
Pre-cleaning profiles, deep-analysis tables, and ML model cards are currently reachable through Artifacts rather than dedicated pages.
The Report page shows the narrative, claim ledger, validator results, and limitations together. HTML exports are self-contained for archiving or sharing. PDF export is optional:
uv sync --extra pdf
brew install pango # macOSOnce installed, download PDF from the Report page. If system dependencies are unavailable, HTML export remains available and the interface explains the missing requirement.
The provider registry (core/provider_registry.py) ships 18 providers: OpenAI,
Anthropic, Gemini, Azure OpenAI, DeepSeek, Qwen, Moonshot, Zhipu, xAI, Mistral,
OpenRouter, Together, Groq, Fireworks, Ollama, LM Studio, a generic
openai_compatible entry, and offline.
The React workbench exposes provider, model, endpoint, key, structured-output,
and payload-policy controls in Settings. Environment or .env values seed a
new browser/API session; UI changes are held in the server's bounded in-memory
session store and apply to runs started afterward. They are not written back to
.env, and a server restart restores environment defaults.
| Mode | Use | Required values |
|---|---|---|
offline |
Deterministic local fallback with no external model request. Chat answers are canned. | None |
| Configured provider | Whatever EDA_LLM_PROVIDER names in the environment. |
API key and model (plus base URL for local/compatible endpoints) |
Restart the server after changing .env. Existing runs keep the settings they
froze at start, while new runs use the effective session settings.
Common environment variables are listed below; see .env.example for the complete example.
EDA_LLM_PROVIDER=offline
EDA_LLM_API_KEY=
EDA_LLM_BASE_URL=
EDA_LLM_MODEL=
EDA_LLM_TEMPERATURE=0.2
EDA_LLM_MAX_TOKENS=6000
EDA_LLM_TIMEOUT_SECONDS=180
EDA_LLM_STRUCTURED_OUTPUT_MODE=autoHow much data may be sent to the LLM is governed by the payload policy:
schema_only: structural metadata only; highest privacy and lowest cost.schema+aggregates: the default; adds aggregates to improve analytical quality.schema+aggregates+sample: also sends sample rows; highest information exposure and cost.
The Settings page defaults to schema+aggregates; changing it affects sessions
started afterward. Choose it according to your data classification, vendor
agreement, and organization policy. API keys are write-only in the UI after
submission and are never written into project files.
Managed SaaS providers are pinned to their registered origins. Custom endpoints
are loopback-only unless an operator explicitly allows the exact HTTPS origin
with EDA_LLM_ENDPOINT_ALLOWLIST; URLs containing user info, query parameters,
or fragments are rejected. Credential-bearing requests do not follow redirects,
and changing an endpoint clears the stored API key so credentials cannot be
silently reused at a different origin.
Open-ended Python analysis is used only when a matching Chat request requires it. Deterministic Auto EDA, reports, and read-only SQL do not depend on it. For the recommended isolation boundary, start Docker Desktop and build the image once:
docker build -t eda-agent-sandbox:py312 docker/eda-agent-sandboxEDA_SANDBOX_BACKEND=auto and docker both select the Docker-only backend.
There is no Seatbelt or host-subprocess fallback. Before each CodeAgent run, the
broker proves that the Linux container has seccomp, a private cgroup namespace,
no capabilities, no-new-privileges, a read-only root filesystem, and no
network. If any check fails, open-ended code is rejected.
Only the requested dataset files are copied into a private per-execution staging
directory and mounted read-only under /work/inputs; original uploads, the
workspace, source tree, .env, credentials, and Docker socket are never mounted.
/work is a byte- and inode-bounded tmpfs; /tmp is a separate bounded tmpfs.
The analysis process runs as UID 65532 with no effective capabilities. After
it exits, a narrowly privileged supervisor clears residual sandbox-user
processes, validates the output tree, streams regular files through a bounded
tar channel, and destroys the container. The host validates paths, entry types,
file counts, and sizes again before sealing SHA-256 manifests.
For deployments that must refuse to start unless this runtime proof succeeds,
set EDA_SANDBOX_REQUIRED=1. You can run the same operational check directly:
uv run python scripts/check_sandbox.pySet EDA_SANDBOX_DOCKER_IMAGE to use a custom prebuilt image. See the
sandbox documentation.
The default workspace is eda_platform/workspace/projects/<project_id>/:
uploads/<dataset_id>/v1/ # preserved source-data versions
sessions/<session_id>/manifest.json # session manifest and code version
sessions/<session_id>/trace.jsonl # step and call trace events
sessions/<session_id>/artifacts/*.json # typed analysis artifacts
sessions/<session_id>/report/report.md # Markdown report
sessions/<session_id>/report/report.html # self-contained HTML report
The platform is local-first: uploaded files and generated artifacts remain in the local workspace. Data is sent to a model provider only when a live LLM is enabled and the selected payload policy permits the relevant schema, aggregate, or sample data.
- Every LLM-supplied tool parameter is checked against column, range, and enum constraints.
- The Chat SQL path is read-only DuckDB and saves result artifacts for inspection.
- Reports use evidence packs, claim ledgers, and a hard validator; their validation state and limitations appear with the report.
- Each run records its
code_version, tool calls, model token usage, estimated cost, and failures for reproduction and troubleshooting.
These controls reduce the risk of unsupported conclusions; they do not replace business review, data-owner confirmation, or human judgment in production decisions.
The platform follows a fail-closed boundary for capabilities that can cross a trust domain:
| Boundary | Enforcement |
|---|---|
| Model data access | Per-session payload policy limits requests to schema, aggregates, or explicitly enabled samples. |
| Provider credentials | Keys are write-only in the UI, omitted from project artifacts, and sent only to pinned or operator-approved origins. Worker processes receive an explicit environment allow-list. |
| SQL | DuckDB statements are parsed and constrained to read-only analysis; results become inspectable artifacts. |
| Python | Model-authored code is accepted only by the verified Docker backend. No host-process or macOS Seatbelt fallback exists. |
| Tool actions | Typed schemas, method contracts, permission classification, bounded budgets, and approval hashes guard invocation and publication. |
| Reports | Evidence references, claim ledgers, publication state, and deterministic validators gate release. |
| Remote access | Exact hosts and origins, TLS-protected authentication, CSRF checks, upload quotas, and explicit remote mode are required. |
LLM debug capture stores metadata—type, shape, size, digest, and keys—by default. Full plaintext capture requires an explicit developer opt-in and should not be enabled for sensitive or remotely hosted workloads.
- The application is local-first and single-workspace. Its remote mode is not a multi-tenant authorization system.
- Autonomous exploration stays disabled until a trusted production certificate covers planted, negative-control, and injection trials for the configured provider and policy.
- Forecasting, segmentation, and causal execution do not yet have dedicated typed adapters. Requests requiring those methods abstain rather than falling back to generic SQL or Python.
- Docker live-runtime probes require a Linux-container Docker engine and the prebuilt sandbox image. Deterministic EDA and read-only SQL remain available when Docker is absent; model-authored Python does not.
- Generated analysis assists review but does not replace domain validation, privacy review, or human approval for consequential decisions.
Run the complete local suite:
scripts/ci_local.shci_local.sh covers the Python suite, the frontend gates (typecheck, Vitest,
production build), and an OpenAPI drift check. Browser E2E is not part of it
— it starts a real server and worker, so it is run separately (see below).
Or run checks individually:
UV_CACHE_DIR=.uv-cache uv run ruff check .
UV_CACHE_DIR=.uv-cache uv run pyright
UV_CACHE_DIR=.uv-cache uv run pytest
npm run typecheck --prefix apps/web
npm test --prefix apps/web # Vitest component/state tests
npm run build --prefix apps/web
git diff --checkPlaywright drives a real Chromium against a real server: playwright.config.ts
starts scripts/serve.py on a throwaway port pointed at a temporary workspace,
so the suite never touches your own runs. Install the browser once:
npm run e2e:install --prefix apps/web # == npx playwright install chromiumThen, with a current production build in place:
npm run build --prefix apps/web
npm run e2e --prefix apps/webThe default suite runs the deterministic Chromium flows and skips pixel snapshots and cross-browser drag checks. Opt into those separately after installing the required browsers:
npm run e2e:visual --prefix apps/web
npm run e2e:install:all --prefix apps/web
npm run e2e:cross-browser --prefix apps/webTogether the suites cover upload → offline run → live progress → Data Map, table deep links with an offset, report rendering, the one-shot cleaning approval, keyboard-only board reordering, visual snapshots, and cross-browser drag behavior.
api/openapi.json is generated, never hand-edited:
uv run python scripts/export_openapi.py # re-export after changing any endpoint
npm run gen:api --prefix apps/web # regenerate the TypeScript typesWithout uv, replace uv run with .venv/bin/python -m, for example:
.venv/bin/python -m pytestThe repository also includes offline demos and evaluation scripts:
uv run python scripts/demo_j3.py
uv run python scripts/evaluate_workflow.py \
--case eda_platform/tests/evals/workflow_quality/cases/semantic_guardrails.json \
--input-dir eda_platform/tests/evals/workflow_quality/data \
--repeat 3apps/web/ React + Vite workbench
api/openapi.json generated API contract
docker/app/ containerized application deployment
docker/eda-agent-sandbox/ isolated Python runtime and locked image
eda_platform/src/eda_platform/ Python application, agents, tools, and schemas
eda_platform/tests/ unit, golden, eval, scoreboard, and security suites
scripts/ server, demos, operations, and eval entry points
The primary architectural seams are:
agents/: model-facing loops, tool definitions, planning, interpretation, reporting, and exploration orchestration;core/: storage-independent policies, budgets, traces, sandbox broker, endpoint security, and durable control primitives;drivers/: end-to-end workflows that connect agents and deterministic tools;schemas/: versioned persisted and API-facing contracts;tools/: deterministic analytical and evaluation operations;application/andapi/: use cases, services, HTTP contracts, and the web application boundary.
Issues and focused pull requests are welcome. Before opening a pull request:
- keep generated files, schemas, and frontend types in sync;
- add regression tests for behavioral or security changes;
- run
scripts/ci_local.sh, or the equivalent individual checks documented above; - describe any provider calls, Docker requirements, skipped live probes, or changes to a trust boundary explicitly.
Do not commit .env, API keys, user workspaces, model payload captures, or
evaluation output containing project data.
This repository does not currently declare an open-source license. Source availability does not grant permission to copy, modify, or redistribute the project; add a license before publishing it for third-party reuse.