Updates the stale version mentions in the chat config comment, the subpath
deploy README and Caddyfile, and records the v0.12.4.5 publish in UPGRADE.md.
- Embedded chat pins Open WebUI v0.12.4.3-subpath (fork fix for title links and Back buttons).
- Studio's chat mask targets the current Open WebUI markup (main, not main#main-content),
so the admin tab bar is hidden again.
- Open WebUI pin: v0.12.0.3-subpath -> v0.12.4.1-subpath (compose default and
embedded wheel). v0.12.4.1 is upstream v0.12.4 plus the fork's subpath build fix,
so the /chat frontend base is baked in and the playground iframe loads.
- ENABLE_VERSION_UPDATE_CHECK defaults to false (Studio pins the build, so the
update banner is noise). Override via env var.
- Docs and comments updated to match.
18 days ago
feat(agentic): realistic agentic audit demo with real tool execution (#55)
* feat(chat): bump Open WebUI subpath build to v0.12.0
The port + GHCR publish were already done in the SushantGautam/open-webui
fork (tag v0.12.0-subpath, functional head 92f0108 == main). This wires it
into Studio:
- docker-compose.yml: OPEN_WEBUI_IMAGE default -> :v0.12.0-subpath
- build.sh: pinned SHA -> 15818d88 (pristine v0.12.0)
- subpath.patch: regenerated as 15818d88..92f0108 EXCLUDING .github/FORK.md
(198 files); git apply --check clean on pristine alongside the node-heap
and otel patches. Old patch kept as subpath-v0.11.4.patch.
- README.md / UPGRADE.md: current base + outcome log updated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(ports): match Open WebUI owner by package name, not checkout path
_classify_command substring-matched the whole command line against
the open-webui and open_webui spellings. A checkout whose path contains
open-webui (e.g. this worktree) made every listener process classify as
Studio's own Open WebUI - a forc…
21 days ago
fix(sse): harden live event stream against worker shutdown + gthread workers (#33)
* fix: handle SystemExit and GeneratorExit in SSE event_stream
The long-running SSE poll loop (up to 1 hour) surfaced SystemExit and
GeneratorExit as unhandled errors in Sentry when the worker process was
shut down (SIGTERM during time.sleep) or the client disconnected.
Wrap the polling loop in a try/except that catches these expected
teardown signals, logs at debug level, and re-raises so Python's normal
close semantics apply without surfacing noise in error tracking.
Closes #25
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(server): run gunicorn with gthread workers for SSE live event streams
Gunicorn's default sync worker cannot heartbeat while actively yielding a
streaming response, so the arbiter SIGABRTs it after the 30s timeout, killing
long-lived SSE streams and raising SystemExit(1). Switch to the threaded
gthread worker, whose main loop heartbeats independently of in-flight requests.
- gunicorn.conf.py (new): gthread workers (workers=2, thre…
28 days ago
feat(otel): enable Open WebUI content capture runtime
Caddy is the only front-door implementation. caddyserver is a hard
dependency with wheels for every platform, so the ~520-line Python handler
could only run on a broken install, where it silently diverged from the
Caddyfile it was meant to mirror (second implementation of identity
strip, forward-auth, CSS mask, favicon, /workspace repair and WS piping).
_caddy_binary() now raises instead of returning None and serve() has no
fallback; a missing binary (or Caddy that never binds) surfaces at start,
and the dev/CLI callers stop Open WebUI and continue the rest of the stack.
Removes the fallback-only stubs and their 19 tests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Seed: seed_platform now creates a demo agent (Support Refund Assistant)
linked to an 'Acme Retail Policy' knowledge base (2 sample policy docs)
and an 'Acme Order Lookup' tool. Local rows always; when chat is enabled,
the KB, docs, and toolkit are also pushed to Open WebUI (idempotent by
name/id, external_id backfilled on rerun). Opt out with --skip-demo-agent.
Also fixes the tools endpoints: the Tools page is /api/v1/tools (toolkit
modules), not /api/v1/functions (pipes/filters) - corrected the seed
push and both pull-side readers.
Chat: repair the subpath fork's root-relative /workspace/knowledge hard
navigation (308 back onto the subpath at the front door) and pass
?embed=admin to /chat/authz via X-Studio-Embed since the Caddy rewrite
drops the query.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mask directory-upload rows in the admin workspace embed via Caddy
The pure-Python proxy already masked 'Upload directory'/'Sync directory'
by injecting embed_admin.js into marked documents, but Caddy mode (local
dev + docker) does no document rewriting, so the buttons stayed visible
in /agents/knowledge/ and /agents/tools/.
Restore the mask in both modes with one file and no fork rebuild: Studio
now serves embed_admin.js as the subpath build's otherwise-empty
static/loader.js (the SPA's first <script>; SvelteKit hydration comes
from the separate entry/start bundle, so replacing it is safe). A Caddy
handle rewrites /chat/static/loader.js to a new /chat/loader-mask view,
mirroring the existing custom.css -> css-mask route.
The script self-gates on the PAGE URL carrying ?embed=admin: the admin
workspace iframes (which always load with that param) get masked; plain
/chat/ and the top-level /chat/workspace pages keep their full menu, so
folder bulk-upload still has one canonical place. This also re-applies
the title pin and Access List / branding masks …
feat(chat): unify Studio + Open WebUI behind one Caddy front door at /chat/
Replaces the two-port embedded model (Studio :8000 + OWUI :8801 in an
iframe) with a single-origin, single-port wiring, identical for local dev
and Docker:
- Caddy is the ONLY published listener (:8000). Studio and Open WebUI
publish no ports; /chat/* reverse-proxies to the subpath build with the
prefix preserved (handle, not handle_path); /ai/ and everything else
go to Django.
- Uses the subpath-capable Open WebUI fork wheel (v0.11.4-subpath,
WEBUI_SUBPATH=/chat), so OWUI emits every URL prefixed with /chat and
no Caddy-side URL rewriting is needed.
- Forward-auth to /chat/authz injects trusted identity headers
(X-Studio-Email/Name/Role); client-forged values are stripped at site
level; 401 redirects to the Studio login.
- Studio's /chat/css-mask serves the embed stylesheet (admin vs plain
mask) in place of a mounted custom.css.
- Local dev: manage.py dev manages a bundled Caddy (uvx caddy /
caddyserver) with a generated Caddyfile; Docker: caddy:2-alpine
chat-prox…
docs(openwebui-subpath): warn never to delete untagged ghcr versions
Registry now publishes only the v0.11.4-subpath tag family (multi-arch).
UPGRADE.md documents the final mechanics: publish-vNEW branch = pristine +
CI plumbing, dispatch-only docker builds, prune job + prune_only input.
Add deploy/openwebui-subpath: embedded OWUI under /chat (v0.11.4, published)
The [tool.uv.sources] override pointed simpleaudit at a local ../SimpleAudit
checkout, breaking fresh-clone uvx and PyPI publish. The published
simpleaudit==0.3.0 wheel already includes the tracing module (verified:
parse_otlp_json, SpanStore, BuiltinOTLP all present), so the override is
stale.
- Remove [tool.uv.sources] block from pyproject.toml
- Regenerate uv.lock (simpleaudit now resolves from PyPI registry)
- Remove git clone steps from ci.yml, docker.yml, root Dockerfile,
and deploy/compose/Dockerfile (no longer needed)
- Update remaining repo links (SushantGautam/kelkalot → SimulaMet)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fix(docker): clone core for local path dependency
The Dockerfiles run uv sync which fails because pyproject.toml points
simpleaudit at ../SimpleAudit (editable) for the tracing layer. Clone the
core repo into /SimpleAudit (which is ../SimpleAudit relative to /app)
before uv sync. Add git to the base image.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Open WebUI has no embed mode, but its app shell loads /static/custom.css
on every page. Answer that one request with chat/embed.css instead of
forwarding it, so the iframe renders without the sidebar (collapsed rail,
expanded panel, and resizer). The rule lives in this repo and survives
Open WebUI upgrades; both modes share the file — the proxy reads it
directly, Caddy mounts it read-only.
feat: optional Open WebUI chat module with Studio single sign-on
Adds an off-by-default module that embeds Open WebUI in Studio at /chat/,
signed in as the Studio user. With SIMPLEAUDIT_CHAT unset nothing changes:
the routes 404, the sidebar has no Chat entry and no extra process runs.
Open WebUI serves from the root of an origin only — it has no base-path
setting and its HTML references /static, /api and /ws absolutely — so it
cannot be proxied under Studio's own /chat/ path. It gets its own origin,
which Studio embeds in an iframe.
Sign-on uses Open WebUI's trusted-header mode. A proxy in front of it asks
Studio who the browser is (GET /chat/authz, the standard forward-auth
contract) and injects the answer as X-Studio-Email/-Name/-Role. Django stays
the only authority on identity; nothing outside it reads sessions or user
tables. Superusers and workspace admins map to Open WebUI's admin role,
everyone else to user.
Two deployments, same contract:
embedded infra/chat_proxy.py, a stdlib HTTP proxy started by
`uvx simpleaudit-studi…
feat: interactive runs dashboard, security fixes and codebase cleanup
Dashboard
- Tabulator grid with server-side paging/sort/filter (/runs/data/), bulk
archive/unarchive/cancel, CSV export that follows filters
- Multi-select Target / Scenario set / Experiment filters, date presets,
filters kept in the URL; one-line toolbar
- Columns menu: show/hide, drag to reorder, resize; layout saved per user
(User.preferences) and in the browser; saved only on user actions
- Cancelled stat card so the cards add up to All
- Compare page: run names in headers, jumps to the comparison
Security
- Run snapshots no longer store raw API keys; the worker resolves keys at
execution time (migration strips keys from existing runs)
- UI forms require admin/auditor, same rule as the API; edit controls hidden
for viewers and archived workspaces
- Connection ping scoped to the user's workspaces
- Fix stored XSS (run event log, health page, scenario errors) and broken
inline confirm handlers; data-confirm pattern for forms
Cleanup
- Remove MinIO/boto3, CORS, pytest, u…
feat: connection-grouped model picker with key warnings; remove legacy ModelEndpoint
The new-audit model pickers now group models by connection (optgroup) and
show a 'no key' warning for connections without an API key, matching the
/models/ page. AuditRun FKs migrate from the flat ModelEndpoint to
RegisteredModel (with a one-time data migration backfilling existing runs),
and the deprecated ModelEndpoint model, its API endpoints, admin entry, and
all UI references are removed.
chore: consolidate deps to pyproject.toml + fix all ruff lint errors
Single source of truth: delete requirements.txt, standardize on uv.
- pyproject.toml is now the only dependency manifest (uv.lock pins versions)
- Makefile, Dockerfiles, CI all use 'uv sync' instead of 'pip install -r'
- Add 'server' extra (gunicorn) for the compose image
- Bump simpleaudit 0.2.0 -> 0.2.1 in uv.lock (was already in pyproject)
Fix all 169 pre-existing ruff errors so CI lint step passes:
- Auto-fix: I001, RUF100, UP017, PLR1730, F401, etc. (94)
- Ignore RUF012 (Django Meta/admin/factory false positives) via config
- BLE001: add justified noqa on intentional broad catches (16)
- SIM117: merge nested with statements (5)
- S110/DTZ005/SIM115/EXE001/TRY004/TRY203: targeted fixes (8)
chore: add ruff linting, fix lint violations, fix test indentation
- BOOTSTRAP_ADMIN_USERNAME → BOOTSTRAP_USERNAME (default: studio)
- BOOTSTRAP_ADMIN_EMAIL → BOOTSTRAP_EMAIL
- BOOTSTRAP_ADMIN_PASSWORD → BOOTSTRAP_PASSWORD
- DEMO_USERNAME default: admin → studio
- Dockerfile: use studio credentials, change-me password placeholder
The account is a normal platform owner, not a Django admin — the old
naming was misleading.
fix: persist embedded Hatchet Postgres in a fixed dir instead of leaking temp dirs
Each uvx run created a fresh tempfile.mkdtemp() for the embedded Postgres
cluster and never removed it, leaking ~218M per run (37 runs ≈ 8GB found
in $TMPDIR). Use a persistent cluster at ~/.simpleaudit-studio/embedded-pg/
(override with SIMPLEAUDIT_EMBEDDED_PG_DIR) so initdb runs once, and
register an atexit hook so the engine stops cleanly even if the caller
forgets. Also align demo bootstrap username assertions with the studio
default.
feat: publish SimpleAudit Studio (docs, PyPI, footer links)
- Rewrite README for public release with Quick Start, live demo link
- Add MIT LICENSE
- Publish to PyPI; pin simpleaudit to 0.2.0
- Allow uvx to build from git (hatchling direct-reference metadata)
- Consolidate self-hosting docs to two paths (uvx + docker compose)
- Remove internal process artifacts (AGENTS.md, ADRs, ROADMAP, plans)
- Add 'Report issue or suggest change?' link to footer
- Remove banner.png from repository
Replace the supervisord multi-process Dockerfile with a clean single-process
image that runs the full stack (Django + embedded Hatchet + worker) in one
Python process via 'python -m simpleaudit_studio.cli'.
Two deployment configs now:
- Minimal: root Dockerfile / uvx simpleaudit-studio (HF Space, laptop, VPS)
- Full: docker-compose.yml (teams, GPU workers, production)
Changes:
- New pyproject.toml with 'simpleaudit-studio' console script entry point
- New simpleaudit_studio/cli.py: boots SQLite + embedded Hatchet + mock
server + web thread + worker in main thread
- New infra/minimal_config.py: embedded Hatchet lifecycle management
- Simplified Dockerfile: no Postgres, no supervisord, no Hatchet binaries
- Removed deploy/hf-space/ (supervisord.conf, start.sh)
- Settings: MINIMAL_CONFIG flag selects SQLite + local Hatchet URLs
- Worker: get_client() returns embedded client in minimal config
- Mock server: importable start_mock_server()/stop_mock_server() API
- CLI flags: --port, --no…
Seed demo audits from pre-recorded fixture (no API key at deploy)
Real audit results captured by running Target→Auditor→Judge against a
live endpoint, saved as infra/fixtures/demo_audit_results.json. The
seed command loads this fixture and creates AuditRun + ScenarioResult
rows referencing the existing GPT-4o / GPT-4o Mini endpoints created
by seed_platform.
Works identically across all deployment paths:
- docker-compose web container boot
- HF Space start.sh
- Local dev (make seed-demo)
- Any manual invocation
No API key needed at deploy time. Idempotent.
Unify demo audit seeding across all deployment paths
Refactor the standalone deploy/seed_demo_audits.py into a proper Django
management command (manage.py seed_demo_audits) so it works identically:
- docker-compose: added to web container boot sequence
- HF Space: start.sh calls the management command
- Local dev: make seed-demo target
- Any other deployment: just run manage.py seed_demo_audits
Gated on SEED_DEMO_AUDITS env var (default true) AND SIMULACHAT_API_KEY
being present. Idempotent — skips if demo runs already exist.
Add demo audit seed script for SimulaChat endpoint
Creates realistic demo audit runs using Qwen3.8-27B as target and
GLM-5.2 FP8 as auditor/judge. Runs 3 scenarios per pack (safety, rag,
health) to populate the dashboard with real execution data.
Idempotent: skips if demo runs already exist. Integrated into
start.sh so it seeds automatically on Space restart.
fix: revert supervisord shell-substitution — it is not evaluated, broke Postgres
The $(shell ...) form is a Dockerfile directive; supervisord passes the
literal string to sudo, so Postgres never started -> Hatchet connection
refused -> crash loop (web still served :7860, masking it in health checks).
Hardcode PostgreSQL 17 (what python:3.12-slim/trixie ships), matching the
original config. Verified locally: all four supervised processes reach
RUNNING and healthz returns ok.
fix: make root Dockerfile the HF Space image (was crash-looping)
Root cause: HF Spaces only build the root Dockerfile (there is no
frontmatter key to point elsewhere). The root Dockerfile was the
compose-only application image — no Postgres, no Hatchet, no start.sh,
serving :8000. But the Space expects the app on :7860, so gunicorn
started yet nothing answered on 7860 -> health check failed -> restart
loop.
Fix:
- Root Dockerfile is now the self-contained single-container Space image
(Postgres + Hatchet + web + worker via supervisord, serves :7860).
- Compose application image moved to deploy/compose/Dockerfile; updated
docker-compose.yml and CI to reference it.
- Removed redundant deploy/hf-space/Dockerfile.
- Made supervisord postgres path version-agnostic (matches start.sh).
Verified locally: root image builds, starts healthy, serves :7860 with
bootstrap + seed completing; compose image still builds for the stack.
refactor: unify seeding into a single seed_platform command
Consolidate all first-run seeding into one path:
- infra/seed.py: shared idempotent logic (pack import + model connections)
- seed_platform: the only seed command (auto on boot, manual with --packs)
Removed:
- seed.py (root ad-hoc script, replaced by seed_platform)
- import_simpleaudit_packs (redundant duplicate of seed_platform --skip-models)
Moved:
- seed_bulk.py -> deploy/seed_bulk.py (stress-test tool, not a first-run seed)
Wired seed_platform into the HF Space start.sh so every deployment path
(docker-compose, HF Space, local dev) uses the same unified seed.
fix: sync HF Space deploy files with latest fixes (#2)
- settings.py: MINIO_ENDPOINT defaults to empty (was http://minio:9000)
- docker-compose.yml: removed hardcoded MINIO_ENDPOINT from web env
- .env: commented out MINIO_* vars
- .env.example: commented out with instructions to enable via storage profile
- Dockerfile + deploy/hf-space/Dockerfile: removed unused MINIO keys
- infra/health.py: probe skips when endpoint or access_key is empty
fix: disable MinIO probe in HF Space (no MinIO in single-container)
Set MINIO_ENDPOINT='' in web and worker env so the health check
reports 'not configured' instead of trying to connect to a
non-existent http://minio:9000 host.
feat: add DEMO_MODE to prefill login form with credentials
When DEMO_MODE=true (set via env var), the login page shows a hint
banner and prefills username/password fields. Enabled by default on
the HF Space deployment so visitors can log in without knowing creds.
- settings.py: DEMO_MODE, DEMO_USERNAME, DEMO_PASSWORD settings
- LoginView: passes demo context to template when enabled
- login.html: amber banner + value attrs on inputs
- supervisord.conf: DEMO_MODE=true for HF Space
- .env.example: documents the new vars (default false)
docs: use clone-from-GitHub + push-to-Space flow in deploy guide
- Beginner guide: step-by-step with explanations, prerequisites,
verification steps, and troubleshooting table
- Expert quick reference: minimal commands for experienced devs
- Verified against HF docs (spaces-sdks-docker) and live testing
- Covers both HF Space deploy and Docker Compose self-hosting
- Includes API endpoints for logs/status/restart
docs: remove GPU mention from HF Space instructions
Supervisord's environment= block replaces the inherited container env, so the web/worker processes never saw DJANGO_ALLOWED_HOSTS, DJANGO_SECRET_KEY, or POSTGRES_* from the Dockerfile ENV. This caused ALLOWED_HOSTS to fall back to localhost-only (breaking CSRF origin matching and host validation) and risked DB connection failures. Explicitly pass the required vars to both programs.
feat: add HF Space single-container deployment files
Adds Dockerfile, supervisord.conf, and start.sh for deploying
SimpleAudit Studio as a Hugging Face Space (single container with
Postgres + Hatchet + Worker + Django managed by supervisord).
The canonical production deployment remains docker-compose.yml;
this is for the demo Space at sushantgautam-simpleaudit-studio.hf.space.