Global Variables

EDDI includes a built-in global variable store for managing configuration values like default LLM model names, API base URLs, temperature settings, and feature flags. Variables are scoped per tenant — single-tenant deployments use "default" implicitly, while multi-tenant deployments can maintain separate variable sets per tenant. Unlike secrets, global variables are not encrypted and are fully visible in the UI and logs.

Architecture

┌─────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│  Configuration   │────>│ GlobalVariable   │────>│  GlobalVariable  │
│  (JSON configs)  │     │ Resolver         │     │  Store           │
│  ${vars:..}   │     │ (regex + cache)  │     │  (MongoDB/PG)    │
└─────────────────┘     └──────────────────┘     └──────────────────┘


┌─────────────────┐
│  Template Layer  │
│  {{vars.<key>}}  │
│  (Jinja2/Qute)   │
└─────────────────┘

Core Components

Component
Package
Purpose

GlobalVariable

configs.variables.model

Record: tenantId, key, value, description, exportable

IGlobalVariableStore

configs.variables

Persistence interface (non-versioned, tenant-scoped key-value)

GlobalVariableStore

configs.variables.mongo

MongoDB implementation (globalvariables collection, composite _id: tenantId/key)

PostgresGlobalVariableStore

datastore.postgres

PostgreSQL implementation (global_variables table, PK: (tenant_id, key))

GlobalVariableResolver

configs.variables

Resolves ${vars:...} references with Caffeine cache

IRestGlobalVariableStore

configs.variables.rest

JAX-RS REST interface

RestGlobalVariableStore

configs.variables.rest

REST implementation with key validation and cache invalidation

Two Access Syntaxes

Global variables are available through two complementary syntaxes:

1. Template Syntax: {{vars.<key>}}

Available in LLM task system prompts and other template contexts. Resolved by the Jinja2/Qute template engine at template processing time.

2. Late-Binding Syntax: ${vars:<key>} / ${vars:tenantId/<key>}

Available everywhere — in LLM task parameters, HTTP call configurations, MCP/A2A settings, embedding configs, Slack configs, and even the type field that selects the LLM provider. Resolved by GlobalVariableResolver at runtime, after template processing but before vault secret resolution.

Supports two forms, mirroring the vault's pattern:

Form
Syntax
Behavior

Short form

${vars:key}

Uses the context tenant (defaults to "default")

Full form

${vars:tenantId/key}

Uses the explicit tenant

Multi-tenant example:

When to Use Which

Syntax
Where It Works
When to Use

{{vars.<key>}}

System prompts, template-processed strings

Dynamic prompt content that changes per-deployment

${vars:<key>}

Everywhere (params, URLs, headers, type)

Operational config that affects infrastructure

Resolution Order

EDDI resolves configuration values in a strict three-step order:

This ordering is important: vault secrets can contain global variable references, and global variables can be composed with template expressions.

⚠️ Nesting is not supported. ${vars:${vault:x}} and ${vault:${vars:x}} will NOT work. Each resolution layer operates independently on the fully-resolved output of the previous layer.

Where References Work

${vars:...} references are resolved in these pipeline callsites:

Configuration Type
Fields Resolved

LLM Task (langchain.json)

type (provider selection), all template parameters

Chat Model Registry

All model parameters before model creation

HTTP Calls (httpcalls.json)

URL, request body, headers, query parameters

MCP Tool Providers

API keys, server URLs

A2A Tool Providers

API keys, agent URLs

Embedding Model Factory

All embedding model parameters

Embedding Store Factory

All embedding store parameters

Slack Channel Router

Channel configuration values (bot tokens, etc.)

REST API

All endpoints require the eddi-admin or eddi-editor role.

Endpoints

Method
Path
Description

GET

/variablestore/variables/{tenantId}

List all variables for a tenant

GET

/variablestore/variables/{tenantId}/{key}

Get a single variable by tenant and key

PUT

/variablestore/variables/{tenantId}/{key}

Create or update a variable

DELETE

/variablestore/variables/{tenantId}/{key}

Delete a variable

Single-tenant shortcut: Use default as the tenantId for single-tenant deployments.

ID Validation

Both tenantId and key must match the pattern [a-zA-Z0-9_.\-]+:

Valid ✅
Invalid ❌

default

has space

tenant-a

key=value

api.base-url

key/path

feature_flag_v2

special!char

Response Examples

GET /variablestore/variables/default:

PUT /variablestore/variables/default/default-model — request body:

GET /variablestore/variables/default/missing-key — returns 404 Not Found.

Caching

Global variables are cached in a Caffeine cache with a 2-minute TTL (configurable). This means:

  • Variables load once from the database, then serve from cache

  • After creating/updating/deleting a variable via the REST API, changes take effect immediately (the REST endpoint invalidates the cache)

  • If the database is updated directly (outside the REST API), changes appear within 2 minutes

Configuration

Downstream Cache Invalidation

When global variables change, downstream caches that were built with old variable values need to be evicted. The GlobalVariableResolver supports an invalidation listener pattern:

  • ChatModelRegistry registers a listener that clears all cached model instances when variables change

  • This ensures that if you change ${vars:default-model} from gpt-4.1 to gpt-4.1-mini, all agents pick up the new model on their next request

Use Cases

Fleet-Wide Model Switching

Set a default model for all agents:

Reference it in all agents' langchain.json:

Switch all agents at once:

Multi-Tenant Model Switching

Set a different default model per tenant:

Reference in agent config:

Environment-Specific API Endpoints

Feature Flags

System Prompt Injection

Comparison: Global Variables vs Secrets vs Properties vs Snippets

Aspect
Global Variables
Secrets Vault
Properties
Snippets

Purpose

Operational config

Sensitive credentials

Per-user/conversation state

Reusable prompt text

Scope

Per-tenant (all agents)

Per-tenant

Per-user or per-conversation

Deployment-wide

Encryption

None

AES-256-GCM

None

None

Visibility

Fully visible

Write-only

Fully visible

Fully visible

Template syntax

{{vars.<key>}}

{{properties.<key>}}

{{snippets.<name>}}

Late-binding

${vars:<key>} or ${vars:tenantId/<key>}

${vault:<key>} or ${vault:tenantId/<key>}

Versioned

No

No

No

Yes

REST path

/variablestore/variables/{tenantId}

/secretstore/secrets/{tenantId}

via PropertySetter

/snippetstore/snippets

Caching

2 min (per-tenant)

5 min

No cache

5 min

Export

Configurable (exportable)

Always scrubbed

Per-user

Included

Decision Guide

  • Need to store an API key? → Use the Secrets Vault (${vault:...})

  • Need to change the LLM model for all agents? → Use a Global Variable (${vars:...})

  • Need to remember a user's name across conversations? → Use Properties with scope: longTerm

  • Need reusable system prompt instructions? → Use Prompt Snippets ({{snippets.<name>}})

Testing

Backend Tests

Test Class
Tests
Coverage

GlobalVariableTest

9

Model record, tenant defaults, JSON serialization, equality

GlobalVariableResolverTest

16

Short/full form resolution, tenant caching, invalidation, edge cases

RestGlobalVariableStoreTest

11

CRUD, tenant+key validation, cache invalidation, patterns

GlobalVariableStoreTest

9

MongoDB adapter CRUD with mocked MongoCollection, tenant scoping

PostgresGlobalVariableStoreTest

14

PostgreSQL adapter CRUD with mocked JDBC, tenant isolation, error paths

GlobalVariableCrudIT

8

Full CRUD lifecycle against MongoDB (Testcontainers)

PostgresGlobalVariableCrudIT

8

Full CRUD lifecycle against PostgreSQL (Testcontainers)

Integration Tests

The integration tests verify the complete REST API lifecycle:

  1. List variables (initially empty)

  2. Create a variable via PUT

  3. Read the variable by key

  4. Update the variable value

  5. Verify the variable appears in the list

  6. Verify 404 for non-existent keys

  7. Verify validation rejects invalid key patterns

  8. Delete the variable and verify removal

Tests run against both MongoDB and PostgreSQL via Quarkus DevServices (Testcontainers).

Last updated

Was this helpful?