How to Manage and Configure Calliope System Settings: A Complete Guide
Calliope uses a two-layer configuration system where static service settings are managed via environment variables in calliope/settings.py and dynamic per-client configurations are stored in PostgreSQL using Piccolo ORM models, merged hierarchically at runtime by the ConfigManager.
Calliope is an open-source generative media framework that supports hierarchical device management through a flexible configuration architecture. Understanding how to manage and configure Calliope system settings is essential for deploying the service across local development environments, cloud infrastructure, or distributed IoT device fleets. This guide examines the actual source code implementation to show you exactly how to modify static service behavior and customize dynamic client configurations.
Understanding Calliope's Configuration Architecture
The codebase separates configuration into two distinct layers: static service settings that control global runtime behavior, and dynamic per-client configurations that determine how individual devices or device groups interact with the inference pipeline.
Static Service Settings
Global system behavior is controlled by the Settings class in calliope/settings.py, which extends Pydantic's BaseSettings to load values from environment variables at startup. These settings include database connections, cloud storage buckets, API keys, and network ports.
# calliope/settings.py (lines 6-18)
class Settings(BaseSettings):
APP_VERSION: str = "0.0.1"
CLOUD_ENV: str = "local"
CALLIOPE_API_KEY: str = "xyzzy"
CALLIOPE_BUCKET_NAME: str = "artifacts.ardent-course-370411.appspot.com"
MEDIA_FOLDER: str = "media"
POSTGRESQL_HOSTNAME: str = "postgres"
POSTGRESQL_USERNAME: str = "postgres"
POSTGRESQL_PASSWORD: str = "postgres"
POSTGRESQL_DATABASE: str = "postgres"
PORT: str = "1234"
settings = Settings()
The settings object is a singleton that remains mutable at runtime. When you need to update a value programmatically, the update() method modifies both the object attribute and the underlying process environment.
# calliope/settings.py (lines 24-30)
def update(self, name: str, value: str) -> None:
"""
Updates settings and the system environment variable 'name' to 'value'.
"""
setattr(self, name, value)
os.environ[name] = value
Dynamic Client Configuration
Device-specific behavior is stored in PostgreSQL and managed through Piccolo ORM models defined in calliope/tables/config.py. This layer supports hierarchical inheritance: individual devices (sparrows) can inherit defaults from parent groups (flocks), which can themselves inherit from higher-level flocks.
The primary configuration tables are:
SparrowConfig– Stores individual device or flock configurations with hierarchical parent references and JSONB parameter overrides.ClientTypeConfig– Defines reusable parameter templates that can be assigned to multiple clients via aclient_typefield.
# calliope/tables/config.py (lines 18-38)
class SparrowConfig(Table):
"""The configuration of a sparrow, flock of sparrows, or flock of flocks."""
client_id = Varchar(length=50, unique=True, index=True)
description = Text(null=True, required=False)
date_created = Timestamptz()
date_updated = Timestamptz(auto_update=datetime.now)
parent_flock_client_id = Varchar(length=50, null=True)
follow_parent_story = Boolean()
parameters = JSONB(null=True) # Override parameters
keys = JSONB(null=True) # Override API keys
# calliope/tables/config.py (lines 98-114)
class ClientTypeConfig(Table):
"""The definition of a client type."""
client_id = Varchar(length=50, unique=True, index=True)
description = Text(null=True, required=False)
date_created = Timestamptz()
date_updated = Timestamptz(auto_update=datetime.now)
parameters = JSONB(null=True)
Hierarchical Configuration Resolution
When a request arrives at the /v1/frames/ endpoint, the ConfigManager in calliope/storage/config_manager.py resolves the final configuration by walking up the flock hierarchy. Starting with the specific client ID, it retrieves each ancestor's SparrowConfig, merges the parameters and keys JSON objects, and applies any ClientTypeConfig overrides before adding strategy-specific defaults.
# calliope/storage/config_manager.py (lines 61-99)
while sparrow_or_flock_id:
sparrow_or_flock_config = await get_sparrow_config(sparrow_or_flock_id)
# ... validation logic ...
if sparrow_or_flock_config.parameters:
sparrow_or_flock_params_dict = _get_non_default_parameters(
load_json_if_necessary(sparrow_or_flock_config.parameters)
)
# Merge parent parameters into child parameters
params_dict = {**sparrow_or_flock_params_dict, **params_dict}
# Walk up to parent flock
sparrow_or_flock_id = sparrow_or_flock_config.parent_flock_client_id
The result is a fully populated FramesRequestParamsModel containing resolved parameters, a KeysModel with merged API credentials, and a StrategyConfig defining the AI pipeline to execute.
Updating Static Settings at Runtime
To modify global service configuration after startup, import the singleton settings object and call its update() method. This simultaneously changes the in-memory value and the environment variable, ensuring consistency for subprocesses or modules that read os.environ.
from calliope.settings import settings
# Switch from local storage to GCP
settings.update("CLOUD_ENV", "gcp")
settings.update("CALLIOPE_BUCKET_NAME", "my-production-bucket")
print(f"Cloud environment is now {settings.CLOUD_ENV}")
Configuring Clients Programmatically
Creating Sparrows and Flocks
You can insert configuration records directly using the Piccolo ORM API. When creating a sparrow that belongs to a flock, set the parent_flock_client_id to establish the hierarchical relationship.
from calliope.tables.config import SparrowConfig
from calliope.utils.file import FileMetadata
from datetime import datetime, timezone
async def create_sparrow():
metadata = FileMetadata(
date_created=datetime.now(timezone.utc),
date_updated=datetime.now(timezone.utc),
user_id="admin",
)
await SparrowConfig.from_pydantic(
model=type("Tmp", (), {
"id": "esp32_001",
"description": "Living-room ESP32 board",
"parent_flock_id": "living_room",
"follow_parent_story": False,
"parameters": {
"output_image_width": 512,
"output_image_height": 512,
"strategy": "fern"
},
"keys": {"OPENAI_API_KEY": "sk-your-key"},
}),
file_metadata=metadata,
)
Resolving Configuration for Debugging
To inspect exactly what configuration a specific client will receive during inference, use the get_sparrow_story_parameters_and_keys() function directly.
from calliope.models import FramesRequestParamsModel
from calliope.storage.config_manager import get_sparrow_story_parameters_and_keys
async def debug_client_config(client_id: str):
request = FramesRequestParamsModel(client_id=client_id)
params, keys, strategy = await get_sparrow_story_parameters_and_keys(request)
return {
"resolved_params": params.dict(),
"resolved_keys": keys.dict(),
"strategy_slug": strategy.slug if strategy else None,
}
Client-Side Key Overrides
Clients can provide temporary API key overrides in the request payload. When the JSON body includes a keys object, the ConfigManager merges these with stored configuration, giving request-level values highest priority.
{
"client_id": "browser_12345",
"client_type": "clio",
"keys": {
"PINECONE_API_KEY": "pc-override-key",
"OPENAI_API_KEY": "sk-temporary"
},
"input_text": "A quiet garden at dawn"
}
Administrative Interface
For manual configuration management, Calliope exposes the Piccolo Admin UI at http://localhost:8008/admin/ (or your hosted endpoint). This web interface provides CRUD operations for:
SparrowConfig– Create individual devices or flock containersClientTypeConfig– Define reusable client templatesStrategyConfig– Manage AI pipeline configurations
All changes made through the UI are immediately persisted to PostgreSQL and reflected in subsequent API requests.
Summary
- Static settings in
calliope/settings.pycontrol global service behavior like database connections and cloud storage, loaded from environment variables and mutable at runtime viasettings.update(). - Dynamic configurations use Piccolo ORM models (
SparrowConfig,ClientTypeConfig) stored in PostgreSQL to define per-device and per-flock behavior. - The
ConfigManagerincalliope/storage/config_manager.pyimplements hierarchical resolution, walking from specific clients up through parent flocks to merge parameters and API keys. - You can manage configurations programmatically via the ORM, through the built-in Piccolo Admin UI, or by providing override keys in individual API requests.
Frequently Asked Questions
How do I change the database connection string in Calliope?
Set the POSTGRESQL_HOSTNAME, POSTGRESQL_USERNAME, POSTGRESQL_PASSWORD, and POSTGRESQL_DATABASE environment variables before starting the service, or call settings.update("POSTGRESQL_HOSTNAME", "new-host") at runtime. The piccolo_conf.py file uses these settings to configure the Piccolo database engine.
Can I override API keys for specific devices without changing environment variables?
Yes. Store device-specific keys in the keys JSONB column of the SparrowConfig table for that device. These overrides take precedence over global environment variables but can be superseded by keys provided in the actual API request payload.
What is the difference between a Sparrow and a Flock in Calliope configuration?
Both use the same SparrowConfig table. A Sparrow represents an individual client device with a unique client_id. A Flock is also a SparrowConfig entry, but other entries reference it via parent_flock_client_id. Flocks allow you to set default parameters for groups of devices, which individual sparrows can override through their own parameters JSON.
How does configuration inheritance work when a device belongs to multiple hierarchy levels?
The ConfigManager performs a bottom-up merge. It starts with the specific device's parameters, then climbs the hierarchy through each parent_flock_client_id, merging parent parameters on top of child parameters at each level. Finally, it applies ClientTypeConfig parameters and strategy defaults. This means higher-level (ancestor) values override lower-level values unless the child explicitly sets its own parameters.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →