Enable Dynamic Agent Configuration Through Admin UI
Context
The Swiss AI Hub platform supports multiple agent types (RAG agents, specialized assistants, etc.) that each have runtime configuration requirements. Previously, modifying agent behavior required:
- Code Changes: Developers had to modify
AgentConfigclasses and redeploy services to change configuration values. - Technical Barrier: Non-technical users (administrators, business analysts) could not customize agent behavior to meet specific use cases without developer involvement.
While agents already had a configuration system through Pydantic-based AgentConfig classes, there was no way to expose these configurations to end users through the UI or to persist user-customized configurations separately from the default values.
Agent Blueprint vs Agent Profile
The platform now distinguishes between two concepts:
- Agent Blueprint (Agent Class): The code-level definition containing workflow steps, form schema, event specifications, and default configuration. Discovered automatically when agents come online and stored in the
agent_classescollection. - Agent Profile (Agent Instance): A user-created configuration of a blueprint. Has a unique ID, name, description, icon, and specific settings. Multiple profiles can be created from one blueprint. Stored in the
agent_configscollection.
This separation enables self-service deployment: developers define blueprints, while administrators create and configure profiles through the UI without code changes.
Decision Drivers
- User Self-Service: Enable administrators and business users to customize agent configurations without code changes or developer involvement.
- Type Safety Preservation: Configuration changes should still be validated against the agent's expected schema, preventing invalid configurations.
- Single Source of Truth: Form schema and data model should be defined once to avoid desynchronization.
- Developer Experience: Minimal boilerplate for exposing configuration through the UI.
Decision
We will implement dynamic agent configuration through the Admin UI using the Form Duality Pattern:
1. Form Duality Pattern
A single Pydantic model serves two purposes through type unions:
- Form Mode: Fields contain
FormkitElementinstances (e.g.,InputText,InputNumber,Select) for UI rendering - Data Mode: Fields contain primitive values (e.g.,
str,float,bool) for validated configuration
class MyAgentConfig(AgentConfig):
# Form duality: float for data mode, InputNumber for form mode
temperature: Annotated[float | InputNumber, Field(description="LLM temperature"), Ge(0.0), Le(1.0)] = 0.7
@classmethod
def as_form(cls) -> "MyAgentConfig":
"""Create form-mode config with FormKit elements for UI rendering."""
base = AgentConfig.as_form()
return cls(
agent_id=base.agent_id,
name=base.name,
description=base.description,
icon=base.icon,
temperature=InputNumber(label=LocaleString(en="Temperature"), min=0.0, max=1.0),
)2. FormKit Element Aliases
FormKit elements use Pydantic aliases to handle MongoDB's $ prefix restriction:
formkitstored in DB →$formkitin JSON responsecondition_ifstored in DB →ifin JSON response (for conditional visibility)refstored in DB →idin JSON response
This enables storing form schemas in MongoDB while generating valid FormKit schemas for the frontend.
3. Non-Configurable vs Configurable Fields
Fields can be marked as non-configurable (deployment-specific) by omitting the FormKit element alternative in the type union:
class MyAgentConfig(AgentConfig):
# Configurable (appears in form) - has FormKit alternative
model_name: str | InputText = "gpt-4"
# Non-configurable (set at deployment) - no FormKit alternative
channel_config: TeamsConfigNon-configurable values are merged with user-submitted configuration at runtime.
4. Form-Safe Constraints
Custom constraint validators (Ge, Le, Pattern, MinLen, MaxLen) that skip validation when the field contains a FormkitElement, enabling Pydantic validation to work with the duality pattern.
5. Agent Discovery and Registration
When agents register via AgentRunner, the form-mode config is used:
runner = AgentRunner(
agent_type=MyAgent,
agent_config=MyAgentConfig.as_form(), # Form mode for discovery
)The discovery response includes:
- Form schema (extracted via
to_formkit_form()) - JSON schema for validation (via
AgentConfigSpecs.from_model()) - Start/stop event specifications
- Workflow network graph
6. Configuration Injection
At runtime, the AgentDispatcher fetches the configuration for the specific agent profile via RPC, merges it with non-configurable defaults, validates it against the Pydantic model, and injects it into step methods via type annotation:
@step()
async def my_step(self, event: MyEvent, agent_config: MyAgentConfig):
model = agent_config.model_name # Validated configuration value7. Two-Collection Persistence
agent_classes(AgentClassEntity): One document per agent type, updated by discovery. Contains form schema, event specs, workflow graph, online status.agent_configs(AgentConfigEntityDocument): Many documents per agent type. Contains instance-specific configuration values, name, description, icon.
8. REST API Endpoints
Agent Classes (blueprints):
GET /agents/classes- List all agent classesGET /agents/classes/{agent_class}- Get specific class with form schema
Agent Instances (profiles):
GET /agents/classes/{agent_class}/instances- List instances of a classPOST /agents/classes/{agent_class}/instances- Create new instanceGET /agents/classes/{agent_class}/instances/{agent_id}- Get instancePUT /agents/classes/{agent_class}/instances/{agent_id}- Update instance configurationDELETE /agents/classes/{agent_class}/instances/{agent_id}- Delete instance
Cross-class:
GET /agents/instances- List all instances across all classes
Consequences
Positive Outcomes
- Self-Service Configuration: Administrators can create and customize agent profiles (prompts, RAG parameters, model selection) directly through the UI without developer involvement.
- Single Source of Truth: Form schema and data model are defined in one place, preventing desynchronization.
- Type-Safe End-to-End: Pydantic validates both form submission and runtime configuration access.
- Conditional Field Visibility: FormKit's
condition_if(aliased fromif) enables dynamic form sections based on other field values. - Multi-Language Support:
LocaleInputelement enables multi-language names and descriptions. - Nested Configuration: Forms can contain nested
Groupelements andRepeaterarrays for complex configuration structures. - Dynamic Model Selection:
ModelSelectelement populates from the LiteLLM model registry at runtime.
Trade-offs and Considerations
- Form Definition Required: Agents must implement
as_form()class method and use type unions to expose configuration in the UI. This provides full control but requires understanding the duality pattern. - Schema Evolution: When agent developers change
AgentConfigfields, existing persisted configurations may use stale schemas until agents re-register. - Discovery Dependency: Form schemas are captured at agent discovery time; if an agent never comes online after deployment, its configuration form won't be available.
- Constraint Validators: Standard Pydantic constraints (
ge=,le=) don't work with duality pattern; developers must use SDK-provided constraints (Ge(),Le()).
