Fork of keboola/agnes-the-ai-analyst (via manana2520 GitHub fork). Develop here, push to GitHub fork to open upstream PRs.
Find a file
Vojtech d63f1473ab
feat(home): drop /home connectors block — onboarding covers it (#305)
The dedicated `<details data-section="connectors">` section on /home
duplicated content that the install hero's Step 4 clipboard payload
already inlines. Both surfaces sourced the same prompt strings from
`app/web/connector_prompts.py` (home tiles via `<code id="*-prompt">`,
setup script via `app/web/setup_instructions.py::_connectors_block`),
so users walking the install script visited each connector inline and
then had no reason to scroll back up.

Removed the full block (3 tiles + summary + section-label). Lead
paragraph in the install hero now mentions the connector families
briefly so the benefit is visible before kick-off:

  "... your team's curated data, plugins, third-party tools (Asana,
   Google Workspace, Atlassian), and shared knowledge ... the install
   script also connects your tools for you, so there's no extra page
   to visit."

The "Email admin" mailto CTA, previously gated inside the GWS tile
when admin_email was set + GWS unconfigured, moves implicitly to the
install script's GWS step (Claude prompts the user when the OAuth
gating wall lands). Tests updated:

- test_connectors_section_removed_from_home (renamed from
  test_connectors_render_flat_when_onboarded_by_default) — asserts
  `class="connector-tiles"` and `data-section="connectors"` are absent
  in BOTH onboarded states, and that the lead paragraph still mentions
  the three connector families so the benefit isn't lost.
- test_home_renders_connector_prompts_from_shared_module — DROPPED.
  Was a parity check between the home tiles and the setup script's
  connector_prompts.py source. One surface now → no drift risk → test
  redundant. Replaced with an inline comment pointing future readers
  at where the strings flow (setup_instructions.py::_connectors_block).
- test_home_no_longer_shows_email_admin_button (renamed from
  test_home_shows_email_admin_button_when_admin_email_set_and_gws_unconfigured)
  — asserts the mailto CTA is gone from /home regardless of
  admin_email / GWS-configured state; documents the path-move.

CSS for `.connector-tile*` left in place as dead bytes — small
footprint, no behavior, easy follow-up if/when someone audits.
2026-05-14 18:31:24 +02:00
.github ci: fix indentation in cli-wheel-clean-install Python heredoc (#273) 2026-05-12 17:32:28 +00:00
app feat(home): drop /home connectors block — onboarding covers it (#305) 2026-05-14 18:31:24 +02:00
cli feat(initial-workspace): per-instance agnes init override (#292) 2026-05-13 20:35:01 +00:00
config feat(home): status frame on /home (operator-gated, onboarded-only) (#297) 2026-05-14 09:28:47 +00:00
connectors fix(security): RBAC filter uses stable user_id instead of mutable email local-part (#293) (#299) 2026-05-14 14:12:54 +00:00
dev_docs chore(docs): replace stale da verbs and vendor-specific install paths 2026-05-04 21:22:19 +02:00
docs UI design system unification — one stylesheet, canonical primitives, nav fix (#284) 2026-05-14 13:28:03 +02:00
infra infra(customer-instance): preserve operator AGNES_TAG / AGNES_TEMP_DIR (#214) 2026-05-07 11:36:36 +02:00
scripts fix(sync+ops): defer-probe race, AGNES_TEMP_DIR chown, default-schedule env knob (#283) 2026-05-13 09:44:20 +00:00
services fix(security): RBAC filter uses stable user_id instead of mutable email local-part (#293) (#299) 2026-05-14 14:12:54 +00:00
src fix(security): RBAC filter uses stable user_id instead of mutable email local-part (#293) (#299) 2026-05-14 14:12:54 +00:00
tests feat(home): drop /home connectors block — onboarding covers it (#305) 2026-05-14 18:31:24 +02:00
.dockerignore
.gitignore feat(home): state-aware /home + /setup-advanced + schema v26 (#228) 2026-05-08 18:28:47 +02:00
.pre-commit-config.yaml feat(ci+tests): deploy safety audit — linting, rollback, smoke tests, 50+ new tests (#120) 2026-04-29 09:18:55 +02:00
ARCHITECTURE.md fix: address Devin Review findings — incomplete renames + estimate guard 2026-05-04 20:05:06 +02:00
Caddyfile fix: Devin Review on #188 — try_files fallback + auto-upgrade ordering 2026-05-05 17:24:42 +02:00
CHANGELOG.md fix(security): RBAC filter uses stable user_id instead of mutable email local-part (#293) (#299) 2026-05-14 14:12:54 +00:00
CLAUDE.md docs(CLAUDE.md): release workflow recipe + issue economy anti-pattern guidance (#288) 2026-05-13 16:30:45 +00:00
docker-compose.ci.yml feat: multi-instance deployment — all 14 must-have items from spec 2026-04-10 11:57:42 +02:00
docker-compose.dev.yml fix(security+ops) + release(0.12.1): #82 #85 #87 hardening + cut 0.12.1 (#104) 2026-04-28 19:57:30 +02:00
docker-compose.flat-mount.yml fix: Devin Review on #194 round 2 — 3 BUG-class findings 2026-05-05 20:02:50 +02:00
docker-compose.host-mount.yml fix: Devin Review on #194 round 2 — 3 BUG-class findings 2026-05-05 20:02:50 +02:00
docker-compose.local-dev.yml release(0.11.2): LOCAL_DEV_GROUPS dev mock + Makefile defaults + docs/local-development.md (#70) 2026-04-26 16:48:55 +02:00
docker-compose.prod.yml fix(compose): drop corporate-memory + session-collector services (#176) 2026-05-04 23:59:44 +02:00
docker-compose.test.yml chore(deploy): trust proxy headers + document HTTPS env vars (#48) 2026-04-24 08:52:53 +02:00
docker-compose.tls.yml feat(tls): corporate-CA HTTPS with URL-driven rotation, on-VM CSR gen, self-signed fallback (#51) 2026-04-25 19:51:25 +00:00
docker-compose.yml fix(duckdb): CHECKPOINT on shutdown + 60s compose grace to prevent WAL corruption (#235) 2026-05-10 19:02:30 +00:00
Dockerfile fix(cli-install): move kbcstorage to [server] extra so wheel installs cleanly (P0 onboarding hotfix → 0.53.4) (#272) 2026-05-12 17:09:44 +00:00
LICENSE
Makefile fix(security+ops) + release(0.12.1): #82 #85 #87 hardening + cut 0.12.1 (#104) 2026-04-28 19:57:30 +02:00
pyproject.toml fix(security): RBAC filter uses stable user_id instead of mutable email local-part (#293) (#299) 2026-05-14 14:12:54 +00:00
pytest.ini feat(rbac+marketplace): RBAC v13 + Claude Code marketplace + #81/#83/#44 hardening 2026-04-28 14:25:04 +02:00
README.md fix: address Devin Review findings — incomplete renames + estimate guard 2026-05-04 20:05:06 +02:00
uv.lock feat(home): Getting Started + Overview + Usage modes sections (release 0.54.7) (#291) 2026-05-13 21:44:11 +02:00

Agnes — AI Data Analyst

Agnes is an open-source data distribution platform for AI analytical systems. It extracts data from configured sources into DuckDB, serves it via a FastAPI backend, and distributes Parquet files to analysts who query them locally using Claude Code and DuckDB.

Each data source produces a self-describing extract.duckdb file. The SyncOrchestrator attaches all extract databases into a master analytics.duckdb, making every table available through a unified view layer without copying data unnecessarily.

Architecture: extract.duckdb Contract

Every connector produces the same output structure:

/data/extracts/{source_name}/
├── extract.duckdb          ← _meta table + views
└── data/                   ← parquet files (local sources only)

The orchestrator scans /data/extracts/*/extract.duckdb, attaches each into analytics.duckdb, and creates master views.

┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│   Keboola    │  │   BigQuery   │  │   Jira       │
│  extractor   │  │  extractor   │  │  webhooks    │
│ (DuckDB ext) │  │ (remote BQ)  │  │ (incremental)│
└──────┬───────┘  └──────┬───────┘  └──────┬───────┘
       │                 │                 │
       ▼                 ▼                 ▼
   extract.duckdb    extract.duckdb    extract.duckdb
   + data/*.parquet  (views → BQ)      + data/*.parquet
       │                 │                 │
       └─────────────────┼─────────────────┘
                         ▼
              SyncOrchestrator.rebuild()
              ATTACH → master views in analytics.duckdb
                         │
              ┌──────────┼──────────┐
              ▼          ▼          ▼
          FastAPI      CLI
          (serve)    (agnes pull)

Supported Data Sources

Mode Distribution Sources Use when
Batch pull (local) Parquet on disk, scheduled Keboola Source has a native bulk-export and the table fits on disk
Materialized SQL (materialized) Parquet on disk, scheduled query BigQuery, Keboola Source table is too large to mirror as-is; you want a curated subset / aggregate on disk
Remote attach (remote) View only, no download BigQuery Table is too large to materialize; latency cost of remote query is acceptable
Real-time push Incremental parquet Jira Source is event-driven and you need sub-minute freshness

The first three modes are what agnes pull distributes to analysts. The fourth is server-side only — analysts query Jira data through the same agnes pull-distributed parquets.

Admins manage per-source registrations through the /admin/tables UI (per-connector tabs for BigQuery / Keboola / Jira) or the agnes admin register-table CLI; per-row "Manage access" deep-links to /admin/access for granting tables to user groups via resource_grants(group, ResourceType.TABLE, table_id).

Analysts get a closed loop with Claude Code: agnes init writes <workspace>/.claude/settings.json with SessionStart (agnes pull --quiet) and SessionEnd (agnes push --quiet) hooks so every Claude Code session starts with fresh RBAC-filtered parquets and ends with the session log uploaded back.

Adding a new source means creating connectors/<name>/extractor.py that produces extract.duckdb with a _meta table (table_name, description, rows, size_bytes, extracted_at, query_mode). The orchestrator attaches it automatically.

Quick Start with Docker

# Clone the repository
git clone https://github.com/keboola/agnes-the-ai-analyst.git
cd agnes-the-ai-analyst

# Copy and edit configuration
cp config/instance.yaml.example config/instance.yaml
cp config/.env.template .env
# Edit both files for your environment

# Start the app and scheduler
docker compose up

# Start with all optional services (Telegram bot, etc.)
docker compose --profile full up

# Start with TLS (Caddy on :443 with corporate-CA certs from /data/state/certs)
docker compose -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.tls.yml \
    --profile tls up -d

Once running, the FastAPI app is available at http://localhost:8000 (or https://$DOMAIN in TLS mode). See docs/DEPLOYMENT.md for cert provisioning + auto-rotation via scripts/ops/agnes-tls-rotate.sh. Trigger a manual sync:

curl -X POST http://localhost:8000/api/sync/trigger

Local sync & auto-update

Analysts run Claude Code against a local DuckDB built from RBAC-filtered parquets pulled from the server. agnes pull is the distribution path:

agnes pull             # delta-pull: manifest → MD5 compare → download changed → rebuild views
agnes pull --quiet     # same, no progress output (for hooks/cron)
agnes push  # push session jsonl + CLAUDE.local.md back to the server

agnes init writes Claude Code lifecycle hooks into <workspace>/.claude/settings.json:

  • SessionStartagnes pull --quiet — fresh data on every session
  • SessionEndagnes push --quiet — uploads notes and session log

Hooks live at workspace level so they only fire in this analyst workspace, not in unrelated Claude Code sessions on the same machine.

Admin: which tables auto-sync to whom

The auto-sync set per analyst is the intersection of:

  1. Tables with query_mode IN ('local', 'materialized') — these have parquets on disk and end up in the manifest
  2. Tables granted to one of the analyst's groups via resource_grants(group, ResourceType.TABLE, table_id) (see docs/RBAC.md)

To enroll a new table for auto-sync, register it (or update its query_mode) and grant it to the relevant groups in /admin/access. New analysts get the same set on their next agnes pull.

For BigQuery, register a query_mode='materialized' table with a SQL body:

agnes admin register-table orders_90d \
    --source-type bigquery \
    --query-mode materialized \
    --query @docs/queries/orders_90d.sql \
    --schedule "every 6h"

The scheduler runs the query through the DuckDB BigQuery extension on each tick that's due, writes the result as a parquet, and the analyst picks it up on the next agnes pull. Cost guardrail: data_source.bigquery.max_bytes_per_materialize (default 10 GiB) — operations exceeding the BQ dry-run estimate are skipped.

Development Setup

# Create and activate virtual environment
python3 -m venv .venv && source .venv/bin/activate

# Install dependencies
uv pip install ".[dev]"

# Run FastAPI locally with hot reload
uvicorn app.main:app --reload

# Run the test suite
pytest tests/ -v

Project Structure

├── src/                    # Core engine
│   ├── db.py               # DuckDB schema (system.duckdb, analytics.duckdb)
│   ├── orchestrator.py     # SyncOrchestrator — ATTACHes extract.duckdb files
│   ├── repositories/       # DuckDB-backed CRUD (sync_state, table_registry, users, etc.)
│   ├── profiler.py         # Data profiling
│   └── catalog_export.py   # OpenMetadata catalog export
├── app/                    # FastAPI application
│   ├── main.py             # App setup, router registration
│   ├── api/                # REST API (sync, data, catalog, admin, auth)
│   ├── auth/               # Auth providers (Google OAuth, email magic link, desktop JWT)
│   └── web/                # HTML dashboard routes
├── connectors/             # Data source connectors (extract.duckdb contract)
│   ├── keboola/            # Keboola: extractor.py (DuckDB extension) + client.py (fallback)
│   ├── bigquery/           # BigQuery: extractor.py (remote-only via DuckDB BQ extension)
│   └── jira/               # Jira: webhook + incremental parquet → extract.duckdb
├── cli/                    # CLI tool (`agnes pull`, `agnes query`, `agnes admin`)
├── services/               # Standalone services (scheduler, telegram_bot, ws_gateway, etc.)
├── scripts/                # Utility + migration scripts
├── config/                 # Configuration templates (instance.yaml.example)
├── docs/                   # Documentation + metric YAML definitions
└── tests/                  # Test suite (633 tests)

Configuration

File Purpose
config/instance.yaml Instance-specific settings: branding, data source type, auth provider, Google domain
.env Secrets and environment variables — never committed
system.duckdb table_registry table Table definitions managed via POST /api/admin/register-table (or PUT /api/admin/registry/{id} to update) or the web UI

Copy the example to get started:

cp config/instance.yaml.example config/instance.yaml

See config/instance.yaml.example for all available options.

Documentation

Contributing

  1. Fork the repository and create a feature branch.
  2. Run pytest tests/ -v to verify all tests pass before opening a pull request.
  3. Keep commits focused and messages concise.
  4. Open a pull request against main with a clear description of the change.

For bugs and feature requests, open a GitHub issue.

License

This project is licensed under the MIT License.