# Changelog All notable changes to Agnes AI Data Analyst. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html), pre-1.0 — public surface (CLI flags, REST endpoints, `instance.yaml` schema, `extract.duckdb` contract) may shift between minor versions; breaking changes called out under **Changed** or **Removed** with the **BREAKING** marker. CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every CI build; semver tags (`v0.X.Y`) are cut at release boundaries and reference the same commit as a `stable-*` tag from the same day. --- ## [Unreleased] ### Changed - `agnes diagnose` is now role-aware. A fresh analyst install no longer reports `Overall: degraded` just because the server has operator-side warnings (stale tables, session-pipeline cadence, BQ billing-project config) that the analyst can't act on. Server (`/api/health/detailed`) tags every check with `audience: "analyst" | "operator"` plus a top-level `caller_role` derived from `user.is_admin` and an `overall_analyst` aggregation. Client excludes operator checks from the headline for analyst callers, surfaces operator warning count on a secondary line so they stay visible, auto-promotes admin/operator callers to the full aggregation, and lets analysts opt in via `--include-operator-checks`. Legacy servers (no `caller_role`) keep the pre-#345-B full aggregation — no silent regression. Closes #345 B. ### Added - `AGNES_MARKETPLACE_URL` env override for `agnes refresh-marketplace --bootstrap`. Pre-fix the marketplace endpoint was hardcoded to `{server_host}/marketplace.git/`, which broke deployments that serve the marketplace from a different host than the API (reverse-proxy split, CDN-fronted marketplace). When set, the env var is parsed via `urlparse`; missing scheme or host fails fast with a clear error (operator misconfiguration surfaces immediately). The PAT injection / strip behavior is preserved on the override path. Default behavior unchanged when the env var is empty / unset. Closes #345 A. ### Added - `agnes query --json` is now a shortcut for `--format json` — paste-prompts and LLM-assisted analysts routinely reach for `--json` first, and the typer "Did you mean `--stdin`?" suggestion the missing flag previously produced was actively misleading. `--json --format ` is rejected as mutually exclusive (`--json --format json` is redundantly allowed). Closes #345 D. ### Internal - Added explicit 5xx-path regression test alongside the existing 4xx case for `agnes query --remote` to lock in the `raise typer.Exit(1)` rc=1 contract for any non-2xx response (`tests/test_cli_query.py::test_remote_query_5xx_exits_nonzero`). No code change — the existing exit-code logic already does the right thing; the test guards against future regression. Closes #345 C. ### Fixed - **UI consistency pass** (I-UI-01..05): radio-card selected state on `/admin/tables` (14 cards get blue border + light bg highlight via `.sync-option-card:has(input:checked)`); promoted `.label-qualifier` / `.optional` to global rule (drops local duplicate); inline `` migrated to design tokens with bg + border; `.btn-google` hover hardcoded swatches replaced with vars; `.code-block code` border + radius reset for dark containers; `.form-textarea` promoted to global. Plus #340 follow-up: removed leftover Phase F2 `{% if data_source_type == 'keboola' %}` guard around edit-modal JS so handlers ship to every instance type (Discover button onclick call sites still respect the guard). Closes #347 (credit @MonikaFeigler). - `agnes refresh-marketplace` (non-bootstrap path) now re-applies `chmod +x` to every `.sh` under `~/.agnes/marketplace` after each `git reset --hard FETCH_HEAD`, not just on the initial bootstrap clone. `git reset --hard` rewrites the working tree from the tree object — if the upstream tree stores a hook script as non- executable (or on `core.filemode=false` setups), every refresh silently re-strips the +x bit and the previously-fixed hooks fire with "Permission denied" again on the next `SessionStart`. Extracted `_chmod_clone_sh_files()` helper, called from both `_bootstrap_clone` and `_git_fetch_and_reset`. Best-effort, no-op on Windows NTFS. Closes the coverage gap Devin Review flagged on PR #350. - Stripped six stale unresolved merge-conflict markers (`<<<<<<<` / `=======` / `>>>>>>>`) from the `[0.55.1]` section of `CHANGELOG.md` that landed on `main` via PR #350's release-cut commit. Markers were rendering as raw conflict text on GitHub and in any tooling that parses the changelog; the HEAD-side content inside each pair is what was kept (the incoming side held superseded intermediate-commit duplicates). ## [0.55.2] — 2026-05-19 ### Fixed - **Customer-instance Terraform module pre-creates `/data/uploads`** (`infra/modules/customer-instance/startup-script.sh.tpl`). v50/0.55.0 added a marketplace cover-image upload directory mounted under `${DATA_DIR}/uploads`; `app/main.py` eagerly mkdirs it at boot for the `StaticFiles` mount. On host-bind deploys where `/data` root is root-owned, the container's non-root `agnes` user (UID 999) can't create the directory and the app crashloops with `PermissionError: '/data/uploads'`. The startup script now pre-creates `uploads` alongside `state/analytics/extracts` under the existing `chown -R 999:999`. Fresh VMs provisioned at `infra-v1.9.0`+ get the dir at first boot; for existing instances bump the module pin + `terraform apply` (which rewrites the instance startup-script metadata) and reboot the VM so the refreshed mkdir/chown block replays. As a one-off without rebooting, run `sudo mkdir -p /data/uploads && sudo chown 999:999 /data/uploads` on the host. ## [0.55.1] — 2026-05-19 ### Added - `/home` install-hero lead now includes a short "What leaves your machine" privacy callout: explains that prompts / tool-calls / tool-responses travel back to the central catalog while raw data rows stay local, and points at `/agnes-private` as the per-session opt-out. - `agnes init` now accepts `--token-file ` and `AGNES_TOKEN` env-var fallback alongside `--token`. Precedence: `--token` > `--token-file` > `AGNES_TOKEN`. The file-/env-var paths dodge Claude Code's auto-classifier, which sometimes flags a long bearer token in an `--token "eyJ..."` command line as a credential-exfil pattern. The pasted setup script now uses `--token-file ~/.agnes/token` (token written via single-quoted heredoc, umask 077) for the same reason. ### Changed - `/home` onboarding install-hero reordered: folder creation is now Step 2 (was Step 3) and starting Claude with `claude --dangerously-skip-permissions` is the new Step 3, rendered with the same `.install-cmd` + copy-button affordance as the other steps. Step 4 paste runs ~20 shell commands that auto-accept-edits would not cover (Bash still prompts), so the YOLO flag is the default recommendation (session-scoped, drops on next plain `claude`). Shift + Tab → auto-accept-edits kept as the strict- review fallback; persistent YOLO allowlist link to `/setup-advanced#yolo` opens in a new tab so users don't lose their `/home` install context. Setup script's "Verify cwd" warning copy refreshed to reference "/home Step 2". - `agnes init` adds `Bash(agnes *)` to the default `permissions.allow` list in the seeded `.claude/settings.json`. Without it, Claude Code was blocking subsequent `agnes ` invocations (`agnes catalog`, `agnes pull`, …) inside the workspace it had just bootstrapped. - `agnes init` and `agnes refresh-marketplace --bootstrap` now `chmod +x` every `.sh` they land on disk (`/.claude/hooks/*.sh` after init; every `.sh` under `~/.agnes/marketplace` after a clone/pull). Git checkout doesn't always preserve the file-mode bit (filemode=false repos, ZIP extractions), so hooks were firing with "Permission denied" — silent `SessionStart` / `PreToolUse` breakage. Best-effort: no-op on Windows NTFS. - Setup script step 3 now uses `--token-file ~/.agnes/token` plus a single-quoted heredoc for the token write, and includes an explicit note about the `!` prefix fallback when Claude Code's classifier blocks an `agnes ` invocation (e.g. `! agnes init …`). - Setup script step 1 (no-CA install path) now emits a robust `grep -qF + ||` snippet for the optional `~/.local/bin` PATH persistence so re-runs don't append a duplicate entry to the user's rc file (fixed-string match + short-circuit per the dedup bug report). ## [0.55.0] — 2026-05-19 ### Added - **Extended Data Packages content (v56 schema)** backing the rewritten `/catalog/p/` package detail page per the extended-descriptions admin extended-descriptions spec. Eight new schema fields, validated API, per-section template rendering, Browse-grid card augmentation: * **`data_packages`** gains owner_name + owner_team (rendered as "Owned by X · Team" line on hero + Browse card), tags (JSON list of category strings), long_description (markdown body for the "What it is" section), when_to_use + when_not_to_use (paired "Use it when / Skip it when" panels), example_questions (package- level flagship list as a one-click prompt panel). * **`table_registry`** gains grain, platforms, partition_col, history, gotchas — structured per-table documentation surfaced in the collapsible per-table row on the package detail page. First `gotcha` with `key=true` renders as a distinct "Key gotcha" block. * **Virtual badges** (`curated` / `new`) derived render-time from creator Admin-group membership + 30-day created_at window — no extra DB column needed. Surfaced on Browse-grid cards (`data-badge="…"` hooks) + the detail-page hero. - **`PUT/POST /api/admin/data-packages`** and **`PATCH /api/admin/registry/{id}/docs`** accept the new fields with per-field validation matching the extended-descriptions admin spec checklist (tags ≤8 × ≤30 chars, long_description ≤4000, bullets ≤8 × ≤200, example_questions ≤12, gotchas ≤8). PATCH echoes the fresh state for round-trip rendering. - **CI guard `test_data_packages_no_vendor_content.py`** scans `app/` + `src/` + `cli/` + `config/` + `scripts/` for vendor-specific tokens from the colleague's spec MD; fails CI if any leak into OSS surfaces. Vendor content stays in the private infra repo's admin- import flow. - **`+ New Memory Item`** button on `/admin/corporate-memory` for admin-seeded items (rules, playbooks, decisions). Modal chains POST `/api/memory` → optional PATCH `domain_ids` → POST `/admin/batch?action=approve|mandate`, so admin-created items land directly as Approved (or Mandatory if the Required checkbox is ticked) without going through Pending review. - **`domains: list[str]`** field on every memory-item API response. The bulk + single-item hydration paths now emit the full slug list, in addition to the legacy `domain` single-slug surface kept for back-compat. The admin queue renders all chips with a `+N` overflow past three. - **GET `/api/memory/admin/{id}`** — single-item fetch for admin. Powers the `#item-` deep link from `/memory/d/`'s Edit affordance: the page now fetches the row directly (no pagination racing) and injects it into `_itemsById` so the edit modal opens reliably even when the item is beyond page 1 of All Items. - **PATCH /api/memory/admin/{id}** accepts a new `domain_ids: list[str]` field that atomically replaces the item's full memory-domain membership via `knowledge_item_domains`. The admin item-edit modal now sends this on save so chip-input domain selections actually persist — previously the chip-input was decorative (legacy single-domain `` removed.** The chip-input is now the canonical domain control on `/admin/corporate-memory`; PATCH writes `domain_ids` (list) to the junction. The hidden `` to native `` swatch picker. Server now validates the hex format too (`^#[0-9a-fA-F]{6}$`) — admins can no longer save malformed values like `#ff5733#e0f2fe` that broke the card layout downstream. ### Fixed - **Memory admin modals were dead — duplicate `let _cmdNewDomainId`** in `admin_corporate_memory.html`. The deprecated step-2 RBAC modal left stub `let` declarations that collided with the live state vars declared earlier in the same `