Community Edition | Enterprise Edition

Admin Guide

Administration Console

SWIRL's Administration Console is the single place to monitor and configure a running SWIRL instance. It is available at http://localhost:8000/admin/ after logging in as a superuser (username admin; on Enterprise Docker installs the password is the ADMIN_PASSWORD value set in .env, while Community installs default to password).

SWIRL Administration Console

COMMUNITY: Virtually all SWIRL objects - SearchProviders, AIProviders, Query Transforms, OAuth Tokens, API Tokens, Authenticators, Users, and scheduled tasks - can be added and edited entirely from the Administration Console. Routine configuration does not require editing .env or loading JSON fixtures by hand.

Dashboard

The top of the console shows four status tiles:

  • Version - the running SWIRL build.
  • Celery / Redis - OK when both the Celery worker and Redis broker are reachable.
  • SearchProviders - the number of active SearchProviders configured.
  • Searches (total) - the lifetime count of Search records in the database.

Underneath, two charts give an at-a-glance view of recent activity:

  • Searches - last 30 days - a daily column chart of total search volume.
  • Top SearchProviders - last 30 days - a bar chart of the providers that have served the most results.

Actions Panel

The Actions panel groups the most common administrative jumps:

  • Activity Analytics - long-form analytics on searches, providers, and futile queries.
  • Log Viewer - a tailing, filterable view of the Django, Celery worker, and Celery beat logs.
  • SWIRL Search UI - opens the Galaxy search UI in a new tab.
  • Authenticators - edit the Microsoft authenticator used for M365 OAuth.
  • Upload Query Transform CSV - bulk-load rewrite/synonym/bag CSVs into a QueryTransform record.

Additional console pages, mounted under /admin/:

  • AI Yield (/admin/ai-yield/) - how AI Insights are rated over time.
  • Business Console (/admin/business-console/) - business-level usage reporting.
  • Semantic Cache Analytics (/admin/semantic-cache/) - Enterprise; Semantic Cache activity and health.
  • Agents (/admin/agents/) - agent activity.

API & Reference Panel

The API & Reference panel links to the browsable DRF endpoints and the Swagger/OpenAPI schema:

  • Search API/swirl/search/ (browsable)
  • Results API/swirl/results/ (browsable)
  • Swagger / OpenAPI/swirl/api/docs/
  • Documentationdocs.swirlaiconnect.com (external)

Configuration Panel

The Configuration panel exposes Add / Change shortcuts for the three core configuration objects:

SearchProvider and AIProvider edit pages now group fields into Identity, Connector/Model, Processing, and Auth sections, with monospaced text-areas for JSON fields and inline help text.

Runtime, Users & Scheduling Panels

The remaining panels give read-only access to runtime objects (Searches, Results, OAuth tokens), user and group management (Tokens, Groups, Users), and the Celery Beat schedule (Clocked, Crontabs, Intervals).

Activity Analytics

The page at /admin/activity/ renders pure-Django queries against the Search and Result tables - no external services contacted:

  • Time range presets - 24H / 7D / 30D / 90D / YTD, or a custom from/to range.
  • Overview tiles - total searches, unique searchers, errors, and AI Yield (the share of AI Insights rated positively).
  • Searches and Messages charts - daily search and Assistant-message volume over the selected range.
  • Errors and futile queries - searches that completed with no results or errored out, so drifting providers and common failure modes stand out.

The SWIRL 5 Activity Analytics page: time-range presets, overview tiles, and search/message charts

Use this page to spot drifting providers (consistently zero or low result counts), to find common failure modes, and to gauge whether the system is being exercised the way you expect.

Log Viewer

COMMUNITY: The Log Viewer at /admin/logs/ reads any logs/*.log file SWIRL is currently writing (Django, Celery worker, Celery beat) and tails the last N lines without requiring shell access to the host.

The SWIRL 5 Log Viewer: file selector, line count, filter, auto-refresh, and download

This is especially useful when:

  • Debugging a SearchProvider that is silently failing - Celery worker logs will show the connector exception.
  • Troubleshooting M365 OAuth token refreshes.
  • Verifying that the expirer or subscriber Celery Beat tasks are firing on schedule.

For deeper investigation, the log files themselves still live under logs/ in the SWIRL repository root and can be tailed with python swirl.py logs from a shell.

Configuring AIProviders (COMMUNITY)

COMMUNITY: The AIProvider model is part of SWIRL Community Edition. An AIProvider is a reusable configuration of an LLM - its model name, API key, base URL, and role assignments.

To configure an AIProvider:

  1. Sign in to the Administration Console at http://localhost:8000/admin/.
  2. Under Configuration → AIProviders, click + Add.
  3. Fill in Name, Model, API key, and (if not OpenAI's default endpoint) API base.
  4. Tick the roles this provider should be used for - typically RAG and/or embeddings for Community.
  5. Mark it Active and save.

SWIRL ships preloaded with AIProviders for OpenAI, Azure/OpenAI, Anthropic, Google Gemini, HuggingFace, Ollama, Cohere, and more in db.sqlite3.dist; set your API key and mark a provider Active to enable it. See the RAG Configuration Guide for the role matrix and the supported model list.

SWIRL Admin Console - AIProvider edit page with the API key, model, and role fields

How the API key field works

The API key input on both the AIProvider edit page and the SearchProvider edit page is rendered as an HTML <input type="password">. Two consequences worth knowing:

  • The field renders empty when you open the edit page - the stored key is not displayed back to you (or to anyone who shoulder-surfs). This is a deliberate display-write-only pattern, not a sign that the key is missing.
  • Saving the form with the field left empty preserves the previously stored value. SWIRL resolves blank submissions server-side and keeps the stored key; the field's help text reads "Currently set. Leave blank to keep the existing value." So you can change the model, roles, or active flag without having to re-enter the API key each time. The same applies to Save as new: a blank secret field copies the secret from the source record.
  • To change the key, type the new value into the field and Save. To clear a stored key entirely, update the record through the /swirl/aiproviders/ (or /swirl/searchproviders/) API or the Django shell - the edit form treats any blank submission as "keep the existing value."

Bulk operations on SearchProviders and AIProviders

Both list pages (SearchProviders, AIProviders) support the standard Django-admin bulk-action pattern plus a SWIRL-specific JSON bulk-import:

  • Bulk actions via row checkboxes. Tick the checkbox next to one or more rows, choose an action - Delete selected …, Activate selected, Deactivate selected, or Duplicate selected (marked inactive) - from the Action dropdown above the list, and click Go. Django prompts for confirmation before deleting.

    SWIRL SearchProviders admin list with the row checkboxes and the Action dropdown above the list

  • Create from JSON. The Add SearchProvider and Add AIProvider pages include a Pre-fill from JSON panel: paste a JSON object (or an array of objects) produced by the Export selected as JSON admin action or copied from a SWIRL preloaded.json fixture, then click Pre-fill form to populate the form. Secret fields (credentials, API keys) are intentionally not pre-filled - paste them manually. For bulk loading, POST to the /swirl/searchproviders/ or /swirl/aiproviders/ API endpoints, or use the swirl_load.py bulk loader. See Adding SearchProviders.

    Add AIProvider form with the Pre-fill from JSON panel

  • Tip: the Save as new button on any edit page (next to Save) clones the current record's fields into a new row, which is useful for spinning up a second SearchProvider that differs from the first by only a tag or query template. Renaming the cloned record first prevents the unique-name constraint from blocking the save.

Configuring the Environment

COMMUNITY: Day-to-day configuration of providers, transforms, schedules, and users happens in the Administration Console. .env holds one-time bootstrap values - SECRET_KEY, database connection info, the license, and Microsoft OAuth callback host/port.

SWIRL uses django-environ to load critical settings from a file named .env.

  • .env.dist contains default settings.
  • If no .env file exists, the install.sh script copies .env.dist to .env.

Example .env file:

SECRET_KEY=<your-secret-key>
ALLOWED_HOSTS=localhost
PROTOCOL=http
SWIRL_EXPLAIN=True
SQL_ENGINE=django.db.backends.postgresql
SQL_DATABASE=swirl
SQL_USER=<database-user>
SQL_PASSWORD=<database-password>
SQL_HOST=localhost
SQL_PORT=5432
SQL_SSLMODE=disable
CSRF_TRUSTED_ORIGINS='http://localhost:8000'
IN_PRODUCTION='False'
SWIRL_LICENSE=
MICROSOFT_CLIENT_ID=''
MICROSOFT_CLIENT_SECRET=''
MICROSOFT_REDIRECT_URI=''

Making changes to .env

To configure hostname, protocol, or port:

  1. Edit the .env file.
  2. Restart SWIRL for changes to take effect.

There should never be a .env file in the SWIRL repo. When upgrading SWIRL, these settings persist automatically.

Environment variable descriptions

Creating a SWIRL Super User

To reset the database:

  1. Delete or rename the db.sqlite3 file.
  2. Run:
python swirl.py setup

This creates a new blank database.

To create a super user, run:

python manage.py createsuperuser --email admin@example.com --username admin

Changing the Super User Password

To change the password of an existing admin user:

python manage.py changepassword admin

If the new password is too simple, Django will reject it.

For more, see Django Admin and manage.py.

Using the Django Admin

To change the super user password via Django Admin:

  1. Go to http://localhost:8000/admin/.
  2. Click CHANGE PASSWORD:

Django Admin Change Password

  1. Enter a new password:

Django Admin Change Password

  1. Click CHANGE MY PASSWORD.

Adding Normal Users

Users can be managed via Django Admin at http://localhost:8000/admin/.

Django Admin - Users

If using OpenID Connect, manual user creation is not required. See the AI Search Guide for details.

Permissioning Normal Users

Each SWIRL core object (SearchProviders, Search, Result, Query Transform) has four permissions:

  • add - allows creation.
  • change - allows modification.
  • delete - allows removal.
  • view - allows read-only access.

Django Admin - Permissions

Recommended Permission Configurations

Scenario SearchProvider Search Results Query Transform
Admin ALL ALL ALL ALL
Search ONLY NONE Add
Result ONLY NONE NONE View
Search & View Results NONE Add, View Add, View Add, View
Manage Search (including re-run) NONE ALL ALL ALL
SearchProvider Admin ALL Add View View

Object Ownership

SearchProvider, Search, Result, and Query Transform objects are owned by the Django user who creates them, and are private by default.

Shared SearchProviders and Query Transformations

  • By default, sharing is disabled (false) for all users.
  • Django super users (admin) have sharing enabled (true) by default.

This prevents users from needing to duplicate SearchProviders or query transformations.

Managing large user groups

For installations with many users, consider:

  1. Creating user groups with predefined permissions.
  2. Assigning users to the appropriate groups.

This simplifies access control and reduces administrative overhead.

Deploying SWIRL for Production Use

Deploy SWIRL behind a reverse proxy for scalability, security, performance, and availability.

Why use a reverse proxy?

  • Scalability - horizontal scaling across multiple SWIRL servers; dynamic provisioning of additional VMs under load; deactivation of idle VMs to reduce hosting costs.
  • Security - SSL/TLS termination at the proxy reduces CPU load on the application server.
  • Performance - separates static-content delivery from the application server; supports CDNs for faster response times.
  • Availability - load-balances traffic across multiple backend SWIRL servers and removes failed servers to maintain uptime.

Recommended reverse-proxy solutions

For deployment assistance, contact support.

Upgrading SWIRL

Local Installations

For Docker upgrades, contact support for instructions.

  1. Update the swirl-search repository:
   git pull
  1. Run the install script:
   ./install.sh
  1. Set up SWIRL:
   python swirl.py setup
  1. If upgrading the Galaxy UI, run:
   ./install-ui.sh
  1. Update AI prompts:
   python swirl.py reload_ai_prompts
  1. Update authenticators. Check the release notes (below) and update authenticators that have changed:
   python manage.py load_authenticators swirl/fixtures/DefaultAuthenticators.json
  1. Update SearchProviders. Check the release notes and only update SearchProviders that have changed:
   python manage.py load_fixture <path-to-searchprovider-json>
  1. Restart SWIRL:
   python swirl.py restart

See the release notes for details on each update.

Docker

SWIRL 5 Enterprise deployments upgrade by pulling the new images and recreating the containers. Back up the database first, then:

docker compose pull
docker compose up -d

Database migrations apply automatically during startup. Data volumes persist across upgrades - never use docker compose down -v during an upgrade (it deletes all data). See the Installation Guide for details, and the edition Quick Starts for first-time installs:

Upgrading from 4.x to SWIRL 5: a new license is required; contact support to include the Semantic Cache entitlement and for upgrade assistance.

Resetting Prompts

To reset AI prompts to defaults, run:

python swirl.py reload_ai_prompts

This restores system prompts to factory settings while keeping custom prompts unchanged.

Resetting Authenticators

To update all Authenticators:

python manage.py load_authenticators swirl/fixtures/DefaultAuthenticators.json

Resetting SearchProviders

To reset a specific SearchProvider:

python manage.py load_fixture <path-to-searchprovider-json>

Configuring SWIRL

SWIRL configuration is managed in: swirl_server/settings.py

Key Configuration Items

Configuration Item Explanation Example
Periodic task schedules (django-celery-beat) The schedules for the Search Expiration Service, Search Subscriber Service, and Bearer Token Service are stored as database-backed periodic tasks, editable in the Django Admin under Periodic Tasks See linked sections
SWIRL_DEFAULT_QUERY_LANGUAGE Determines the stopword dictionary SWIRL_DEFAULT_QUERY_LANGUAGE = 'english'
SWIRL_SEARCH_HTTP_CONNECT_TIMEOUT Time (in seconds) to wait for initial connection to a SearchProvider SWIRL_SEARCH_HTTP_CONNECT_TIMEOUT = 3
SWIRL_SEARCH_HTTP_READ_TIMEOUT Time (in seconds) to wait for a SearchProvider to return results SWIRL_SEARCH_HTTP_READ_TIMEOUT = 10
SWIRL_SUBSCRIBE_WAIT Timeout for updating a search SWIRL_SUBSCRIBE_WAIT = 20
SWIRL_DEDUPE_FIELD Field used for duplicate detection SWIRL_DEDUPE_FIELD = 'url'
SWIRL_DEDUPE_SIMILARITY_MINIMUM Minimum similarity score to classify as duplicate SWIRL_DEDUPE_SIMILARITY_MINIMUM = 0.95
SWIRL_DEDUPE_SIMILARITY_FIELDS Fields used for duplicate detection SWIRL_DEDUPE_SIMILARITY_FIELDS = ['title', 'body']
SWIRL_RELEVANCY_CONFIG Defines relevancy score weights for key fields See below
SWIRL_MAX_MATCHES Maximum matches per result (limits long articles) SWIRL_MAX_MATCHES = 5
SWIRL_MIN_SIMILARITY Minimum score required for query hits to be scored SWIRL_MIN_SIMILARITY = 0.01
SWIRL_EXPLAIN Enables relevancy explain structures in responses SWIRL_EXPLAIN = false

Example SWIRL_RELEVANCY_CONFIG

SWIRL_RELEVANCY_CONFIG = {
    'title': {
        'weight': 1.5
    },
    'body': {
        'weight': 1.0
    },
    'author': {
        'weight': 1.0
    }
}

All configuration items must be uppercase, following the Django settings convention.

Search Expiration Service

The Expirer service automatically deletes expired Search objects and their associated Result objects. This prevents SWIRL from retaining all past searches indefinitely.

Service Frequency

SWIRL allows custom expiration settings for Search and Result objects, but the expiration service runs on a fixed schedule.

  • By default, it runs every hour (the expire periodic task, crontab(minute=0, hour='*')).
  • The schedule is stored as a database-backed django-celery-beat periodic task, seeded at install time.
  • Changes can be made via the Django Admin Console:
  http://localhost:8000/admin/django_celery_beat/crontabschedule/

Django console crontab page

SWIRL Enterprise supports a 5-minute expiration schedule. Contact support for details.

Search Subscriber Service

When a Search object has subscribe=True, SWIRL periodically updates that Search. See the Developer Guide for details.

  • By default, the service runs every 4 hours (the subscribe periodic task, crontab(minute=0, hour='*/4')).
  • The schedule is stored as a database-backed django-celery-beat periodic task, seeded at install time.
  • Changes can be made via the Django Admin Console:
  http://localhost:8000/admin/django_celery_beat/crontabschedule/

Django console crontab page

Service Startup & Daemonization

Using swirl.py

For normal operations, use swirl.py to start, stop, or restart services. The script is located in the SWIRL installation directory (next to manage.py).

Starting services

python swirl.py start

To start specific services, specify them by name:

python swirl.py start celery-beats

Checking service status

python swirl.py status

Example output:

INFO 2026-07-01 19:59:55 settings Swirl Enterprise 5.0.0 licensed to: SWIRL_Corporation

         .   o
        .        .   .  o
        .      .
  o        .  @ @   .            SWIRL AI ENTERPRISE 5.0.0
    .        @ @  .    .         Licensed to: SWIRL_Corporation
      .  . .   .     .    .
            .       .     o
     o  .       o .


Service: django...RUNNING, pid:34738
Service: celery-search-worker...RUNNING, pid:34767
Service: celery-pagefetch-worker...RUNNING, pid:34780
Service: celery-interactive-worker...RUNNING, pid:34793
Service: celery-maintenance-worker...RUNNING, pid:34806
Service: celery-healthcheck-worker...RUNNING, pid:34819

SWIRL Enterprise runs specialized Celery workers, each handling a different task type: celery-search-worker (query federation), celery-pagefetch-worker (full-page content fetching for RAG), celery-interactive-worker (real-time chat and interactive queries), celery-maintenance-worker (expiration, subscription, and housekeeping), celery-healthcheck-worker (service health monitoring), and celery-corpus-worker (Semantic Cache ingestion and Qdrant indexing). Depending on configuration, celery-beats (the periodic scheduler) and celery-search-orchestrator-worker (async search orchestration) also run. SWIRL Community runs a single celery-worker that handles all task types.

Stopping services

python swirl.py stop

Restarting services

python swirl.py restart

Restart the whole stack rather than individual workers: the Celery workers and the web service must restart together.

Getting help

python swirl.py help

Complete Command Reference

Command Description
start Start all SWIRL services
stop Stop all SWIRL services
restart Restart all SWIRL services
status Show status of all services
logs Tail logs from all services
setup Initialize SWIRL (create database, load fixtures)
migrate Run Django database migrations
debug Start SWIRL in debug mode (foreground, verbose logging)
watch Start SWIRL and watch log output
load_branding Load or reload branding configuration
load_data Load fixture data into the database
reload_ai_prompts Reload AI prompt templates to factory defaults
reload_license Re-read and re-validate the SWIRL license without a restart
config_postgres Configure PostgreSQL as the database backend
config_db General database configuration
config_default_api_settings Configure default API settings
admin / adminpw Create the admin user / reset the admin password
version Print the SWIRL version
health / doctor Check the health of the running services / diagnose common problems
clean Remove temporary files and stale state
shell Open a Django shell
collectstatic Run Django collectstatic
reset_cache Reset the Semantic Cache
init_qdrant / init_storage Initialize the Qdrant vector store / the object-storage bucket for the Semantic Cache
galaxy Open the Galaxy UI in the default browser
help Display help information

Customizing

The services invoked by swirl.py are defined in swirl/services.py. Modify this list to start celery-beats automatically.

By default, the cookie consent notice appears for users in Galaxy until they accept it.

SWIRL Galaxy UI with consent notice

To prevent the notice from appearing, add the following to the .env file:

COOKIE_CONSENT_DISPLAY=False

Changing the Galaxy Logos and Branding

To customize the logo, search button, and labels in SWIRL Galaxy:

  1. Go to the Administration Console at http://localhost:8000/admin/.
  1. Under the SWIRL section, click Branding Configurations, then Add (or open Upload a Branding Configuration) to go to the Branding Configuration page:

SWIRL branding configuration form

  1. Fill out the form: - Name is required. - At least one additional field must be customized. - To change the logo, upload a light-mode image.

Branding configuration options

Item Galaxy Location Default
Name Not shown N/A
Search button label Search button text SEARCH
Search bar placeholder Placeholder text in search box "What are you searching for today?"
AI Response label Below search box "Generate AI Response"
Select items label Below paging control "Select Items"
  1. Click Submit to save your branding changes.
  2. Refresh your browser to apply changes in the Galaxy UI.

Refreshing may start a new RAG or chat session.

Logo File Suggestions

  • Format: PNG
  • Dimensions: 818 × 214 px
  • Whitespace: at least 30 px margin around the logo

Only one branding configuration can be active at a time.

Restoring a Previous Branding Configuration

SWIRL displays the last updated branding configuration. To make a previous configuration active:

  1. Go to http://localhost:8000/admin/.
  2. Scroll down to Branding Configurations under the SWIRL section. Click it.
  3. Click a previous configuration, then click SAVE at the bottom of the form. No actual changes need to be made.

Reload Galaxy in the browser. The saved branding configuration appears, replacing the previous one.

Managing Django Users

Django Admin

Most users can be managed through Django Admin at http://localhost:8000/admin/.

For more, watch this YouTube video on Django Administration.

Changing a User's Password

To change a user's password via the command line:

python manage.py changepassword <user_name>

Unlocking a Locked Account

  1. Login to the SWIRL UI with a different superuser account.

  2. Open the user profile icon in the top right corner, and go to http://localhost:8000/admin/ (the Administration Console). SWIRL Reset Lockout, Step #1

  3. From the new tab that opens, click the Admin link to enter the Django Admin area.

  4. In the top left corner, click on the “Reset Lockout Attempts for User” option: SWIRL Reset Lockout, Step #2

  5. In the new tab that opens, select the “admin” user from the list of locked out accounts. (In the example above, we are unlocking the user testuser01.) With the user selected, click the Reset button on this page: SWIRL Reset Lockout, Step #3

  6. Return to main SWIRL UI tab, and select the User Profile icon again. Click Logout for the superuser account.

  7. This returns you to the SWIRL login page where you should be able to login again with the admin user, now unlocked!

Management Tools

Django Console

Django provides a web-based UI for managing users, groups, crontabs, and more.

Django console

Access it here: http://localhost:8000/admin/

For an in-depth tutorial, watch this YouTube playlist on Django Admin.

Django dbshell

Django has a built-in shell for direct database management. Run it inside the swirl-search directory:

./manage.py dbshell

Wiping the Database

To delete all SWIRL objects and reset the database:

python manage.py flush

This will remove all data permanently.

You must create a new SWIRL Super User after doing this.

sqlite-web

sqlite-web provides an open-source web-based GUI for SQLite.

Installation & Usage

pip install sqlite-web
sqlite_web my_database.db                   # Runs locally at http://localhost:8080/
sqlite_web --host 0.0.0.0 my_database.db    # Run it on the LAN

Use the full path to db.sqlite3 in swirl-search when running sqlite-web.

Database Migration

If you modify swirl/models.py, you must run a database migration.

Basic Migration Command

python swirl.py migrate

For more details, see: https://docs.djangoproject.com/en/6.0/topics/migrations/

General Migration Guidelines - Adding fields or changing defaults is usually simple. - If renaming an id or modifying relationships, consider wiping existing data first (sqlite-web can help).

If Migration Fails

  1. Restore the database from your most recent backup.
  2. Run:
   python swirl.py migrate
  1. If the migration still fails, contact support with the full error output. Do not delete the files in swirl/migrations/ - they are part of the product and are required for future upgrades.

Configuring Django

SWIRL's Django configuration is managed in:
swirl_server/settings.py

Key Configuration Items

Hostname & Protocol

## Set the Fully Qualified Domain Name (FQDN) first
ALLOWED_HOSTS = ['localhost']
HOSTNAME = ALLOWED_HOSTS[0]
PROTOCOL = 'http'

The FQDN SWIRL should listen on must be the first entry in ALLOWED_HOSTS.

Time Zone

TIME_ZONE = 'US/Eastern'
CELERY_TIMEZONE = "US/Eastern"
CELERY_TIME_ZONE = "US/Eastern"

Celery Beats

Celery-Beats is used for scheduled services like the Search Expiration Service and the Search Subscription Service.

CELERY_BEAT_SCHEDULE = {
    # Executes every hour
    'expire': { 
         'task': 'expirer', 
         'schedule': crontab(minute=0, hour='*'),
    },          
}

Database Provider

DATABASES = {
    "default": {
        "ENGINE": os.environ.get("SQL_ENGINE", "django.db.backends.sqlite3"),
        "NAME": os.environ.get("SQL_DATABASE", BASE_DIR / "db.sqlite3"),
        "USER": os.environ.get("SQL_USER", "user"),
        "PASSWORD": os.environ.get("SQL_PASSWORD", "password"),
        "HOST": os.environ.get("SQL_HOST", "localhost"),
        "PORT": os.environ.get("SQL_PORT", "5432"),
    }
}

The following sections apply only to SWIRL Community Edition. For Enterprise Edition instructions view the AI Search Guide.

Configuring PostgreSQL as the Database Backend

  1. Install PostgreSQL (if not already installed).
  2. Set the connection in .env:
SQL_ENGINE=django.db.backends.postgresql
SQL_DATABASE=swirl
SQL_USER=<database-user>
SQL_PASSWORD=<database-password>
SQL_HOST=localhost
SQL_PORT=5432
  1. Run python swirl.py config_db (or config_postgres) to initialize the schema, then restart SWIRL.

The PostgreSQL driver (psycopg2-binary) is installed with SWIRL's requirements, and the PostgreSQL SearchProvider connector is available out of the box - no code changes are needed.

For further setup, follow the Django Database Configuration Guide.

Configuring Celery & Redis

SWIRL uses Celery for executing metasearch requests asynchronously, with Redis as the backend.

Celery is configured in two locations. They must be consistent!

1. swirl_server/celery.py

app = Celery('swirl_server', 
             broker='redis://localhost:6379/0', 
             backend='redis://localhost:6379/0')

To verify the setup, run Celery from the command line and confirm the transport and results backend are both Redis:

> transport:   redis://localhost:6379/0
- ** ---------- .> results:     redis://localhost:6379/0

2. swirl_server/settings.py (Django Settings)

## Celery Configuration Options
CELERY_TIMEZONE = 'US/Eastern'
CELERY_TASK_TRACK_STARTED = True
CELERY_TASK_TIME_LIMIT = 30 * 60
CELERY_BEAT_SCHEDULE = {
    'expire': {
         'task': 'expirer',
         'schedule': crontab(minute=0, hour='*'),
    },
    'subscribe': {
         'task': 'subscriber',
         'schedule': crontab(minute=0, hour='*/4'),
    },
}
CELERY_BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'

Security

For the comprehensive, CISO-level view of SWIRL Enterprise security - identity, data protection, network hardening, logging, vulnerability management, incident response, and a production hardening checklist - see the Security Guide.

The Django Secret Key

Django's SECRET_KEY is a cryptographic salt used for security. If changed, active users will need to log in again.

To generate a new secret key:

python -c "import secrets; print(secrets.token_urlsafe())"

For more details: Django Secret Key Guide.

License Administration

SWIRL Enterprise. The license is a signed JSON document supplied by SWIRL and set in the SWIRL_LICENSE environment variable (in .env, or a Kubernetes/Compose secret). Its shape, with placeholders:

SWIRL_LICENSE={"owner": "<owner>", "max_users": <max-users>, "expiration": "<YYYY-MM-DD>", "cache": true, "cache_expiration": "<YYYY-MM-DD>", "key": "<license-key>"}
  • owner - the licensed organization, shown in the startup banner.
  • max_users - the maximum number of users the deployment may have.
  • expiration - the license expiration date.
  • cache / cache_expiration - the Semantic Cache entitlement. The cache is enabled only when the license carries an explicit "cache": true (and cache_expiration, if present, has not passed); a license without a cache field does not include the Semantic Cache.

SWIRL validates the license signature at startup. To apply a replacement license without restarting, update SWIRL_LICENSE and run:

python swirl.py reload_license

License status appears in the startup banner and the Administration Console. Never commit a real license to version control; treat it as a secret.

SWIRL User & Group Support

Django provides built-in authentication with User and Group objects. These can be managed via the Django Console or API.

User Management API

URL Explanation
/swirl/users/ List User objects & create new users
/swirl/users/id/ Retrieve, delete, or edit a User object

Group Management API

URL Explanation
/swirl/groups/ List Group objects & create new groups
/swirl/groups/id/ Retrieve, delete, or edit a Group object

Alternatively, use the Django Console: SWIRL admin console Users list

For additional details, see User Authentication in Django.

Workspaces and Members

SWIRL 5 Enterprise. Workspaces are team units that scope sources and configuration - SearchProviders, searches, pipelines, and topics can belong to a workspace. Workspaces do not scope documents: Semantic Cache documents and pins are deployment-wide ("All Workspaces") by design.

Workspace Roles

RoleCapabilities
ownerFull control: settings, members, invites, archive
partnerApprove canonical elections; manage matters
editorCurate documents and run content jobs
viewerRead-only

Workspace API

EndpointMethodsPurpose
/swirl/workspaces/GET, POSTList your workspaces / create one (creator becomes owner)
/swirl/workspaces/<slug>/GET, PATCH, DELETERetrieve / update (owner) / archive (owner; archives rather than hard-deletes)
/swirl/workspaces/<slug>/members/GET, POSTList / add members (owner)
/swirl/workspaces/<slug>/members/<user-id>/PATCH, DELETEChange role / remove (owner; the last owner cannot be removed)
/swirl/workspaces/<slug>/invites/GET, POSTList pending invites / invite by username or email (owner; invites expire after 14 days)
/swirl/workspaces/<slug>/invites/<invite-id>/DELETERevoke a pending invite (owner)
/swirl/workspaces/invites/accept/POSTAccept an invite token

Requests are scoped to a workspace with the X-SWIRL-Workspace header (membership required). Objects with no workspace are deployment-level. Pass ?all_workspaces=true to suppress workspace filtering for cross-workspace discovery; per-user ownership checks always still apply. Privileged workspace actions are recorded in an append-only audit log.

Semantic Cache Administration

SWIRL 5 Enterprise, separately licensed. The Semantic Cache has its own guide covering licensing (cache + cache_expiration license fields), settings, management commands, and troubleshooting: see the Semantic Cache Guide. Administration highlights:

  • License status appears in the startup banner and the Admin console license panel; replacing the license takes effect without a restart.
  • The cache depends on the stack's qdrant and seaweedfs services.
  • Key commands: upgrade_corpus_to_hybrid, reset_semantic_cache, dedup_signal_url_hash - run via docker compose exec swirl python manage.py <command>.

Per-Family Prompt Overrides

SWIRL 5 Enterprise. Each AI provider can bind its own prompt per role via the prompt_overrides field - so an Anthropic provider can use Claude-tuned prompts while an OpenAI provider uses the generic ones. The override maps a role tag to a saved Prompt name:

"prompt_overrides": {
  "chat": "chat_rag_instructions_claude",
  "chat-rag": "chat_rag_deeplink_claude",
  "search-rag": "search_rag_deeplink_claude",
  "chat-rag-structured": "chat_rag_structured_claude",
  "search-rag-structured": "search_rag_structured_claude"
}

Resolution order for a given role tag: an explicitly requested prompt name wins; otherwise the provider's prompt_overrides entry; otherwise the first active prompt carrying that tag. Family-specific prompts are seeded with empty tags so they are reachable only through an override and never pollute the generic fallback. See Managing Prompts in the AI Search Guide.