Skip to content

Remote Variables

When connected to Logfire, variables are managed through the Logfire UI or programmatically via the SDK. This is the recommended setup for production.

To enable remote variables, you can explicitly opt in using VariablesOptions:

import logfire
# Enable remote variables
logfire.configure(
    variables=logfire.VariablesOptions(),
)

# Define your variables
agent_config = logfire.var(
    name='support_agent_config',
    type=AgentConfig,
    default=AgentConfig(...),
)

How remote variables work:

  1. Your application connects to Logfire using your API key
  2. Variable configurations (including all versions and labels) are fetched from the Logfire API
  3. A background thread polls for updates (default: every 60 seconds)
  4. If available, the SDK listens for Server-Sent Events (SSE) on GET /v1/variable-updates/ and triggers an immediate refresh
  5. When you create a new version, move a label, or change a rollout in the UI, running applications pick up the change automatically via SSE or the next poll

Configuration options:

from datetime import timedelta

logfire.configure(
    variables=logfire.VariablesOptions(
        # Block until first fetch completes (default: True)
        # Set to False if you want the app to start immediately using defaults
        block_before_first_resolve=True,
        # How often to poll for updates (default: 60 seconds)
        polling_interval=timedelta(seconds=60),
    ),
)

Pushing Variables from Code

Instead of manually creating variables in the Logfire UI, you can push your variable definitions directly from your code using logfire.variables_push().

The primary benefit of pushing from code is automatic JSON schema generation. When you use a Pydantic model as your variable type, logfire.variables_push() automatically generates the JSON schema from your model definition. This means the Logfire UI will validate version values against your schema, catching type errors before they reach production. Creating these schemas manually in the UI would be tedious and error-prone, especially for complex nested models.

from pydantic import BaseModel

import logfire
logfire.configure(
    variables=logfire.VariablesOptions(),
)


class AgentConfig(BaseModel):
    """Configuration for an AI agent."""

    instructions: str
    model: str
    temperature: float
    max_tokens: int


# Define your variables
agent_config = logfire.var(
    name='agent_config',
    type=AgentConfig,
    default=AgentConfig(
        instructions='You are a helpful assistant.',
        model='openai:gpt-4o-mini',
        temperature=0.7,
        max_tokens=500,
    ),
)

# Push all registered variables to the remote provider
if __name__ == '__main__':
    logfire.variables_push()

When you run this script, it will:

  1. Compare your local variable definitions with what exists in Logfire
  2. Show you a diff of what will be created or updated
  3. Prompt for confirmation before applying changes

Example output:

=== Variables to CREATE ===
  + agent_config
    Example value: {"instructions":"You are a helpful assistant.","model":"openai:gpt-4o-mini","temperature":0.7,"max_tokens":500}

Apply these changes? [y/N] y

Applying changes...
Successfully applied changes.

Options:

ParameterDescription
variablesList of specific variables to push. If not provided, all registered variables are pushed.
dry_runIf True, shows what would change without actually applying changes.
yesIf True, skips the confirmation prompt.
strictIf True, fails if any existing label values in Logfire are incompatible with your new schema.

Pushing specific variables:

feature_flag = logfire.var(name='feature_enabled', type=bool, default=False)
max_retries = logfire.var(name='max_retries', type=int, default=3)

# Push only the feature flag
logfire.variables_push([feature_flag])

# Dry run to see what would change
logfire.variables_push(dry_run=True)

# Skip confirmation prompt (useful in CI/CD)
logfire.variables_push(yes=True)

Pushing Variable Types

When you have multiple variables that share the same type (e.g., several variables all using the same AgentConfig Pydantic model), you can push the type definition itself as a reusable schema. This is done with logfire.variables_push_types().

Why push variable types?

  • Schema reuse: Define a schema once and reference it from multiple variables
  • Centralized management: Update the schema in one place when your type definition changes
  • Documentation: Types serve as documentation for the expected structure of variable values
from pydantic import BaseModel

import logfire
logfire.configure(
    variables=logfire.VariablesOptions(),
)


class FeatureConfig(BaseModel):
    """Configuration for a feature flag with additional settings."""

    enabled: bool = False
    max_retries: int = 3
    timeout_seconds: float = 30.0


class UserSettings(BaseModel):
    """User preference settings."""

    theme: str = 'light'
    notifications_enabled: bool = True


if __name__ == '__main__':
    # Push type definitions using their class names
    logfire.variables_push_types([FeatureConfig, UserSettings])

Explicit naming:

By default, types are named using their __name__ attribute (e.g., FeatureConfig). You can provide explicit names using tuples:

logfire.variables_push_types([
    (FeatureConfig, 'feature_config'),
    (UserSettings, 'user_settings'),
])

Options:

ParameterDescription
typesList of types to push. Items can be a type (uses __name__) or a tuple of (type, name) for explicit naming.
dry_runIf True, shows what would change without actually applying changes.
yesIf True, skips the confirmation prompt.
strictIf True, fails if any existing variable label values are incompatible with the new type schema.

Example output:

Variable Types Push Summary
========================================

New types (2):
  + FeatureConfig
  + UserSettings

Apply these changes? [y/N] y

Applying changes...

Done! Variable types synced successfully.

When updating existing types, the output shows which types have schema changes:

Variable Types Push Summary
========================================

Schema updates (1):
  ~ FeatureConfig

Unchanged (1):
  = UserSettings

Validating Variables

You can validate that your remote variable configurations match your local type definitions using logfire.variables_validate():

from logfire.variables import ValidationReport

# Validate all registered variables
report: ValidationReport = logfire.variables_validate()

if report.has_errors:
    print('Validation errors found:')
    print(report.format())
else:
    print('All variables are valid!')

# Check specific issues
if report.variables_not_on_server:
    print(f'Variables missing from server: {report.variables_not_on_server}')

The ValidationReport provides detailed information about validation results:

PropertyDescription
has_errorsTrue if any validation errors were found
errorsList of label validation errors with details
variables_checkedNumber of variables that were validated
variables_not_on_serverNames of local variables not found on the server
description_differencesVariables where local and server descriptions differ
format()Returns a human-readable string of the validation results

This is useful in CI/CD pipelines to catch configuration drift where someone may have edited a version value in the UI that no longer matches your expected type.

Config Push Workflow (Programmatic)

For more control over your variable configurations, you can work with config data directly. This workflow allows you to:

  • Generate a template config from your code
  • Edit the config locally (add rollouts, overrides)
  • Push the complete config to Logfire
  • Pull existing configs for backup or migration

Generating a config template:

from pathlib import Path

import logfire
from logfire.variables import VariablesConfig

# Define your variables
agent_config = logfire.var(name='agent_config', type=AgentConfig, default=AgentConfig(...))
feature_flag = logfire.var(name='feature_enabled', type=bool, default=False)

# Build a config with name, schema, and example for each variable
config = logfire.variables_build_config()

# Save to a JSON file
Path('variables.json').write_text(config.model_dump_json(indent=2))

The generated file will look like:

{
  "variables": {
    "agent_config": {
      "name": "agent_config",
      "labels": {},
      "latest_version": null,
      "rollout": {"labels": {}},
      "overrides": [],
      "json_schema": {
        "type": "object",
        "properties": {
          "instructions": {"type": "string"},
          "model": {"type": "string"},
          "temperature": {"type": "number"},
          "max_tokens": {"type": "integer"}
        }
      },
      "example": "{\"instructions\":\"You are a helpful assistant.\",\"model\":\"openai:gpt-4o-mini\",\"temperature\":0.7,\"max_tokens\":500}"
    },
    "feature_enabled": {
      "name": "feature_enabled",
      "labels": {},
      "latest_version": null,
      "rollout": {"labels": {}},
      "overrides": [],
      "json_schema": {"type": "boolean"},
      "example": "false"
    }
  }
}

Pushing:

from pathlib import Path

from logfire.variables import VariablesConfig

# Read the edited config
config = VariablesConfig.model_validate_json(Path('variables.json').read_text())

# Sync to the server (including labels and versions)
logfire.variables_push_config(config)

Push modes:

ModeDescription
'merge' (default)Only create/update variables in the config. Other variables on the server are unchanged.
'replace'Make the server match the config exactly. Variables not in the config will be deleted.
# Partial push - only update variables in the config
logfire.variables_push_config(config, mode='merge')

# Full push - delete server variables not in config
logfire.variables_push_config(config, mode='replace')

# Preview changes without applying
logfire.variables_push_config(config, dry_run=True)

Pulling existing config:

from pathlib import Path

# Fetch current config from server
server_config = logfire.variables_pull_config()

# Save for backup or migration
Path('backup.json').write_text(server_config.model_dump_json(indent=2))

# Merge with local changes
merged = server_config.merge(local_config)

VariablesConfig methods:

MethodDescription
config.merge(other)Merge with another config (other takes precedence)
VariablesConfig.from_variables(vars)Create minimal config from Variable instances