Skip to content

Repository files navigation

Analyst Copilot

CI Python 3.12+ Node.js 20+ Status

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/.

Project status

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.

Contents

Capabilities

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.

How it works

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"]
Loading

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.

Bounded-memory data plane

Auto EDA uses a staged, per-table lifecycle instead of retaining every pandas DataFrame for the duration of a run:

  1. Inputs are copied into content-addressed storage and represented by lightweight DatasetSource handles.
  2. One table is materialized for profiling, quality checks, charts, statistics, and optional modeling; only typed artifacts survive when that table is released.
  3. 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.
  4. 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.

Agent harness and evaluation

The harness treats the complete system—model, prompts, tools, policy, environment, data fingerprints, and budget—as the unit under test.

  • QuestionAnswerContract binds 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.
  • WorkflowEvalTrial is 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 3

The repository also contains adversarial, golden, scoreboard, security, and exploration-release suites under eda_platform/tests/.

Technology and requirements

  • Python 3.12+
  • Node.js 20+ and npm, to build the React workbench (apps/web)
  • FastAPI for the /api/v1 contract 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-python for 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.

Quick start

Run the following commands from the repository root.

1. Install dependencies

Python (using uv is recommended):

uv sync --extra dev

Without uv:

python -m venv .venv
.venv/bin/python -m pip install -e ".[dev]"

Frontend:

npm install --prefix apps/web

2. Configure the local environment (optional)

Offline 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 .env

Add 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.

3. Build and start

npm run build --prefix apps/web
uv run python scripts/serve.py            # http://127.0.0.1:8000

serve.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.

4. First run in the browser

Open http://127.0.0.1:8000/. A fresh workspace has no projects yet:

  1. 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.
  2. Upload one or more CSV files on the new-run page, optionally add business context, and choose the LLM mode (Environment default uses whatever .env configures; Offline makes no API calls).
  3. 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.
  4. When the job completes, the run's pages (Data Map, Quality, Report, …) are populated. Past runs are listed in the left session rail.

Ways to run the app

The React UI (apps/web) talks to the FastAPI backend under /api/v1.

Option A: single command (default)

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 port

serve.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.

Option B: development (two processes)

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 :8000

Option C: Docker

cd 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 :8080

The 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_WORKSPACE must be an absolute path. Relative values are rejected. Without an override, every entry point resolves the same repository-anchored eda_platform/workspace path, independent of its current working directory. This guard prevents API and worker processes from silently creating separate workspaces.

User guide

Standard workflow

  1. Pick or create a project, then open New session.
  2. Upload one or more CSV files, optionally add business context, and choose the LLM mode for this run.
  3. 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.
  4. 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.
  5. 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.
  6. 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.

Pages

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.

Reports and exports

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  # macOS

Once installed, download PDF from the Report page. If system dependencies are unavailable, HTML export remains available and the interface explains the missing requirement.

LLM and privacy settings

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=auto

How 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.

Optional: open-ended Python analysis

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-sandbox

EDA_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.py

Set EDA_SANDBOX_DOCKER_IMAGE to use a custom prebuilt image. See the sandbox documentation.

Artifacts and traceability

Local storage

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.

How conclusions are kept reviewable

  • 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.

Security model

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.

Known boundaries

  • 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.

Development and quality checks

Run the complete local suite:

scripts/ci_local.sh

ci_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 --check

Browser end-to-end tests

Playwright 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 chromium

Then, with a current production build in place:

npm run build --prefix apps/web
npm run e2e --prefix apps/web

The 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/web

Together 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 contract

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 types

Without uv, replace uv run with .venv/bin/python -m, for example:

.venv/bin/python -m pytest

The 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 3

Project structure

apps/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/ and api/: use cases, services, HTTP contracts, and the web application boundary.

Contributing

Issues and focused pull requests are welcome. Before opening a pull request:

  1. keep generated files, schemas, and frontend types in sync;
  2. add regression tests for behavioral or security changes;
  3. run scripts/ci_local.sh, or the equivalent individual checks documented above;
  4. 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.

License

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.

Related documentation

About

Local-first agent platform for exploratory data analysis. Deterministic profiling and evidence-validated LLM reports, with open-ended Python analysis isolated in a sandbox.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages