SQLAlchemy DBURI Configuration
Soliplex uses SQLAlchemy to store persistent data in two separate databases:
-
One database holds history for AG-UI threads and runs, created by clients interacting with its AG-UI endpoints. See below.
-
Another database holds authorization information: a list of administrative users, and a list of room authorization policies and the access control list (ACL) entries they contain. See below.
Configuration for these databases uses SQLAlchemy database URLs, in two flavors:
-
Synchronous URLs, used by code which is not written to run using Python's
asyncmechanisms (e.g., the CLI and TUI modules). This style of database URLs is the default described in the SQLAlchemy docs. -
Asynchronous URLs, used by code which does use Python's
asyncmechanisms (e.g., FastAPI endpoint functions). See this SQLAlchemy page for details on the extension which addsasyncsupport to SQLAlchemy.
Because of the requirement for async support, Soliplex cannot use
all possible SQLAlchemy engines. Known to work:
-
Postgres via
psycopg, whosepostgresql+psycopg://DBURIs serve both the sync and the async engines (see below) -
Postgres via the
asyncpgasync dialect (postgresql+asyncpg://), installed separately
Untested:
-
Oracle via its built-in async dialect
-
Microsolt SQL Server via the
aioodbcasync dialect
Installing the PostgreSQL driver
Soliplex installs only the SQLite drivers. To use PostgreSQL, install one
of two extras, each providing psycopg:
| Extra | libpq / OpenSSL used | Needs on the host |
|---|---|---|
soliplex[postgres] |
the host's own | libpq (e.g. Debian's libpq5) |
soliplex[postgres-binary] |
bundled in the psycopg-binary wheel |
nothing |
Use postgres for container images and FIPS hosts, where the OS's
package updates and FIPS configuration cover libpq and OpenSSL; setting
PSYCOPG_IMPL=python there keeps psycopg on the system libpq even if a
psycopg-binary wheel is installed alongside. Use postgres-binary for
development, and for hosts without libpq.
The same postgresql+psycopg:// DBURI serves both sync_dburi and
async_dburi. Name the driver explicitly: a bare postgresql:// means
psycopg2, which neither extra installs, and a DBURI naming a driver
that is not installed fails with ModuleNotFoundError when its engine is
created.
On Windows, psycopg's async mode cannot run on the event loop Python uses
there by default. Use postgresql+asyncpg:// for the async_dburi
instead, installing asyncpg
yourself.
thread_persistence_db
Soliplex uses this pair of URLs to record and query information about AG-UI threads and runs initiated by clients. If this sections is not configured, Soliplex uses an in-memory Sqlite database, e.g.:
To use an on-disk SQLite database for thread persistence (note the four forward-slashes!):
thread_persistence_db:
sync_dburi: "sqlite:////path/to/thread_persistence.sqlite"
async_dburi: "sqlite+aiosqlite:////path/to/thread_persistence.sqlite"
To use a Postgres server, assuming the login is "soliplex" and the
password is defined as a secret named "POSTGRES_PASSWORD":
thread_persistence_db:
sync_dburi: "postgresql+psycopg://soliplex:secret:POSTGRES_PASSWORD@/soliplex_threads"
async_dburi: "postgresql+psycopg://soliplex:secret:POSTGRES_PASSWORD@/soliplex_threads"
authorization_db
Soliplex uses this pair of URLs to record and query authorization information: a list of administrative users, and a list of room authorization policies and the access control list (ACL) entries they contain. If this sections is not configured, Soliplex uses an in-memory Sqlite database, e.g.:
To use an on-disk SQLite database for thread persistence (note the four forward-slashes!):
authorization_db:
sync_dburi: "sqlite:////path/to/authorization.sqlite"
async_dburi: "sqlite+aiosqlite:////path/to/authorization.sqlite"
To use a Postgres server, assuming the login is "soliplex" and the
password is defined as a secret named "POSTGRES_PASSWORD":
authorization_db:
sync_dburi: "postgresql+psycopg://soliplex:secret:POSTGRES_PASSWORD@/soliplex_authorization"
async_dburi: "postgresql+psycopg://soliplex:secret:POSTGRES_PASSWORD@/soliplex_authorization"
Migrations
Soliplex keeps both databases at the schema revision its release expects, and by default does that itself, on the first writable open (see Database Migrations). That is the right arrangement when the application role also owns its schema, which is the case for SQLite and for a default PostgreSQL stack.
It does not work for a deployment which deliberately runs the application
as a least-privilege role -- every object owned by an administrative role,
the application granted only SELECT, INSERT, UPDATE, DELETE. Such a
role is refused CREATE TABLE and ALTER TABLE, correctly. Two optional
sub-keys, available on both stanzas, hand the job to somebody else.
migration_dburi
A synchronous DBURI naming the role which owns the schema. Only a sync URL is needed, because Alembic's online mode is synchronous.
authorization_db:
sync_dburi: "postgresql+psycopg://soliplex:secret:APP_PASSWORD@/soliplex_authz"
async_dburi: "postgresql+psycopg://soliplex:secret:APP_PASSWORD@/soliplex_authz"
migration_dburi: "postgresql+psycopg://owner:secret:OWNER_PASSWORD@/soliplex_authz"
When it is absent, migrations use sync_dburi, which is exactly what
every release before this key existed did. A SQLite deployment, a
soliplex-template stack, or a PostgreSQL deployment which has not
separated owner from application role needs no change and sees no
difference.
Like the other two URLs, it interpolates both secret: and env:
markers.
migration_policy
Two named values; absence is the third state:
| value | meaning |
|---|---|
| absent | migrate automatically, on any writable open |
explicit |
only soliplex-cli database upgrade migrates |
disabled |
nothing migrates this database from this configuration |
Configuring migration_dburi without a migration_policy implies explicit.
Configuring disabled allows sharing a single installation.yaml between
services running as different roles: one service resolves disabled, while
another (the one which runs the migration) resolves explicit.
The whole value may be a single env: marker instead of a literal, wired
via the compose service environment and the installation config:
authorization_db:
sync_dburi: "postgresql+psycopg://soliplex:secret:APP_PASSWORD@/soliplex_authz"
async_dburi: "postgresql+psycopg://soliplex:secret:APP_PASSWORD@/soliplex_authz"
migration_dburi: "postgresql+psycopg://owner:secret:OWNER_PASSWORD@/soliplex_authz"
migration_policy: "env:SOLIPLEX_MIGRATION_POLICY"
Both the ordinary server container and a special-purpose migrations
container read that one stanza, and they differ only in what
SOLIPLEX_MIGRATION_POLICY says:
| container | policy resolves to | what it does |
|---|---|---|
| server | disabled |
runs as soliplex, migrates nothing |
| migrations | explicit |
runs soliplex-cli database upgrade as owner |
Which is why the stanza names two credentials. soliplex /
APP_PASSWORD is the least-privilege role the application runs as,
granted only DML; owner / OWNER_PASSWORD owns the schema, and nothing
but the migrations container's soliplex-cli database invocation ever
connects with it.
A value which is neither policy name is an error, reported when the policy is read. Absence means automatic migration, so an unrecognized value must not fall through to it.
What a policy refuses
Both values are enforced wherever soliplex would otherwise migrate on its
own: the server's startup, and every soliplex-cli command which opens a
database for writing. The refusal names the remedy under explicit
(soliplex-cli database upgrade) and deliberately names no command under
disabled, where migrating belongs to another service entirely.
A policy bites only when a migration is actually owed. A database already
at the revision this release expects starts normally under any policy,
which is what lets a service configured disabled run against a current
database without knowing or caring.
One consequence is worth planning for: a database under a policy does not
get its schema built on first use. Ordinarily the first writable open
creates the tables; under explicit or disabled it refuses instead, so a
newly created database has to be initialized with
soliplex-cli database upgrade before the server is started against it.
Reading is unaffected -- soliplex-cli audit databases creates and migrates
nothing, so it keeps working, and reporting that a migration is owed is
exactly its job.
Interpolation
Each of the DBURI values can include values interpolated from the installation configuration's environment. E.g:
authorization_db:
sync_dburi: "postgresql+psycopg://env:SOLIPLEX_AUTHZ_USER:secret:POSTGRES_PASSWORD@/env:SOLIPLEX_AUTHZ_DBNAME"
async_dburi: "postgresql+psycopg://env:SOLIPLEX_AUTHZ_USER:secret:POSTGRES_PASSWORD@/env:SOLIPLEX_AUTHZ_DBNAME"
Deprecated spelling
Through Soliplex v0.81, these two stanzas named the URL flavor as the
sub-key, with the word dburi in the stanza name instead:
thread_persistence_dburi:
sync: "<sync dburi>"
async: "<async dburi>"
authorization_dburi:
sync: "<sync dburi>"
async: "<async dburi>"
That spelling is still read, but is deprecated and will be removed after
Soliplex v0.84. Loading a configuration which uses it emits a
DeprecationWarning naming the stanza and the configuration file.
The mapping to the current spelling is mechanical:
| Deprecated | Current |
|---|---|
thread_persistence_dburi |
thread_persistence_db |
authorization_dburi |
authorization_db |
sync |
sync_dburi |
async |
async_dburi |
A configuration which somehow sets both spellings for the same database uses the current one, ignoring the deprecated one.
soliplex-cli config <installation-path> exports the resolved
configuration using the current spelling, which makes it a convenient way
to derive the replacement text: secret: and env: markers within the
DBURIs are exported as written, not resolved, so the exported stanzas can
be pasted back into installation.yaml.